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 {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
'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:get/get.dart';
|
||||||
import 'package:pilipala/http/dynamics.dart';
|
import 'package:pilipala/http/dynamics.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class DynamicsController extends GetxController {
|
class DynamicsController extends GetxController {
|
||||||
int page = 1;
|
int page = 1;
|
||||||
@ -8,6 +12,7 @@ class DynamicsController extends GetxController {
|
|||||||
RxList<DynamicItemModel>? dynamicsList = [DynamicItemModel()].obs;
|
RxList<DynamicItemModel>? dynamicsList = [DynamicItemModel()].obs;
|
||||||
RxString dynamicsType = 'all'.obs;
|
RxString dynamicsType = 'all'.obs;
|
||||||
RxString dynamicsTypeLabel = '全部'.obs;
|
RxString dynamicsTypeLabel = '全部'.obs;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
Future queryFollowDynamic({type = 'init'}) async {
|
Future queryFollowDynamic({type = 'init'}) async {
|
||||||
var res = await DynamicsHttp.followDynamic(
|
var res = await DynamicsHttp.followDynamic(
|
||||||
@ -27,16 +32,22 @@ class DynamicsController extends GetxController {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectType(value, label) {
|
onSelectType(value, label) async {
|
||||||
dynamicsType.value = value;
|
dynamicsType.value = value;
|
||||||
dynamicsTypeLabel.value = label;
|
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) {
|
switch (item!.type) {
|
||||||
case 'DYNAMIC_TYPE_FORWARD':
|
case 'DYNAMIC_TYPE_FORWARD':
|
||||||
print('转发的动态');
|
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor});
|
arguments: {'item': item, 'floor': floor});
|
||||||
break;
|
break;
|
||||||
@ -45,10 +56,25 @@ class DynamicsController extends GetxController {
|
|||||||
arguments: {'item': item, 'floor': floor});
|
arguments: {'item': item, 'floor': floor});
|
||||||
break;
|
break;
|
||||||
case 'DYNAMIC_TYPE_AV':
|
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;
|
break;
|
||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
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;
|
break;
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
print('番剧');
|
print('番剧');
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
late StreamController<bool> titleStreamC; // appBar title
|
late StreamController<bool> titleStreamC; // appBar title
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
bool _visibleTitle = false;
|
bool _visibleTitle = false;
|
||||||
|
String? action;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -37,11 +38,16 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||||
type = 11;
|
type = 11;
|
||||||
}
|
}
|
||||||
|
action =
|
||||||
|
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||||
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
||||||
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
||||||
titleStreamC = StreamController<bool>();
|
titleStreamC = StreamController<bool>();
|
||||||
scrollController.addListener(_listen);
|
scrollController.addListener(_listen);
|
||||||
|
if (action == 'comment') {
|
||||||
|
_visibleTitle = true;
|
||||||
|
titleStreamC.add(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listen() async {
|
void _listen() async {
|
||||||
@ -90,12 +96,13 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
if (action != 'comment')
|
||||||
child: DynamicPanel(
|
SliverToBoxAdapter(
|
||||||
item: _dynamicDetailController!.item,
|
child: DynamicPanel(
|
||||||
source: 'detail',
|
item: _dynamicDetailController!.item,
|
||||||
|
source: 'detail',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
SliverPersistentHeader(
|
SliverPersistentHeader(
|
||||||
delegate: _MySliverPersistentHeaderDelegate(
|
delegate: _MySliverPersistentHeaderDelegate(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.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/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/models/common/dynamics_type.dart';
|
import 'package:pilipala/models/common/dynamics_type.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
@ -16,9 +17,9 @@ class DynamicsPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _DynamicsPageState extends State<DynamicsPage>
|
class _DynamicsPageState extends State<DynamicsPage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
DynamicsController _dynamicsController = Get.put(DynamicsController());
|
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
final ScrollController scrollController = ScrollController();
|
// final ScrollController scrollController = ScrollController();
|
||||||
bool _isLoadingMore = false;
|
bool _isLoadingMore = false;
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -28,10 +29,11 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||||
|
|
||||||
scrollController.addListener(
|
_dynamicsController.scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
if (scrollController.position.pixels >=
|
if (_dynamicsController.scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
_dynamicsController.scrollController.position.maxScrollExtent -
|
||||||
|
200) {
|
||||||
if (!_isLoadingMore) {
|
if (!_isLoadingMore) {
|
||||||
_isLoadingMore = true;
|
_isLoadingMore = true;
|
||||||
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
|
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
|
||||||
@ -92,7 +94,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
List<DynamicItemModel> list = _dynamicsController.dynamicsList!;
|
List<DynamicItemModel> list = _dynamicsController.dynamicsList!;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => ListView.builder(
|
() => ListView.builder(
|
||||||
controller: scrollController,
|
controller: _dynamicsController.scrollController,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: list.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (BuildContext context, index) {
|
itemBuilder: (BuildContext context, index) {
|
||||||
@ -105,19 +107,18 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
slivers: [
|
slivers: [
|
||||||
HttpError(
|
HttpError(
|
||||||
errMsg: data['msg'],
|
errMsg: data['msg'],
|
||||||
fn: () => setState(() {}),
|
fn: () => _dynamicsController.queryFollowDynamic(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
// 骨架屏
|
||||||
// return SliverList(
|
return ListView.builder(
|
||||||
// delegate: SliverChildBuilderDelegate((context, index) {
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
// return const VideoCardHSkeleton();
|
itemCount: 5,
|
||||||
// }, childCount: 10),
|
itemBuilder: ((context, index) => const DynamicCardSkeleton()),
|
||||||
// );
|
);
|
||||||
return Text('加载中');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -26,7 +26,8 @@ Widget action(item, context) {
|
|||||||
label: Text(stat.forward!.count ?? '转发'),
|
label: Text(stat.forward!.count ?? '转发'),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => _dynamicsController.pushDetail(item, 1),
|
onPressed: () =>
|
||||||
|
_dynamicsController.pushDetail(item, 1, action: 'comment'),
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
FontAwesomeIcons.comment,
|
FontAwesomeIcons.comment,
|
||||||
size: 16,
|
size: 16,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
Widget picWidget(item, context) {
|
Widget picWidget(item, context) {
|
||||||
@ -14,7 +15,6 @@ Widget picWidget(item, context) {
|
|||||||
}
|
}
|
||||||
int len = pictures.length;
|
int len = pictures.length;
|
||||||
List picList = [];
|
List picList = [];
|
||||||
|
|
||||||
List<Widget> list = [];
|
List<Widget> list = [];
|
||||||
for (var i = 0; i < len; i++) {
|
for (var i = 0; i < len; i++) {
|
||||||
picList.add(pictures[i].src ?? pictures[i].url);
|
picList.add(pictures[i].src ?? pictures[i].url);
|
||||||
@ -42,7 +42,8 @@ Widget picWidget(item, context) {
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, BoxConstraints box) {
|
builder: (context, BoxConstraints box) {
|
||||||
double maxWidth = box.maxWidth;
|
double maxWidth = box.maxWidth;
|
||||||
double aspectRatio = 1.1;
|
double aspectRatio = 1.0;
|
||||||
|
double origAspectRatio = 0.0;
|
||||||
double crossCount = len == 1
|
double crossCount = len == 1
|
||||||
? 1
|
? 1
|
||||||
: len < 3
|
: len < 3
|
||||||
@ -51,20 +52,19 @@ Widget picWidget(item, context) {
|
|||||||
|
|
||||||
double height = 0.0;
|
double height = 0.0;
|
||||||
if (len == 1) {
|
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;
|
height = pictures.first.height * maxWidth / pictures.first.width;
|
||||||
if (pictures.first.width != 1920) {
|
if (origAspectRatio < 0.5 || pictures.first.width < 1920) {
|
||||||
crossCount = 2;
|
crossCount = 2;
|
||||||
height = maxWidth / 2 / aspectRatio;
|
height = maxWidth / 2 / aspectRatio;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
aspectRatio = 1;
|
aspectRatio = 1;
|
||||||
height = maxWidth /
|
height = maxWidth / crossCount * ((len / crossCount).ceil()) + 6;
|
||||||
crossCount *
|
|
||||||
(len % crossCount == 0
|
|
||||||
? len ~/ crossCount
|
|
||||||
: len ~/ crossCount + 1) +
|
|
||||||
6;
|
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
@ -73,14 +73,20 @@ Widget picWidget(item, context) {
|
|||||||
),
|
),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
height: height,
|
height: height,
|
||||||
child: GridView.count(
|
child: Stack(
|
||||||
padding: EdgeInsets.zero,
|
children: [
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
GridView.count(
|
||||||
crossAxisCount: crossCount.toInt(),
|
padding: EdgeInsets.zero,
|
||||||
mainAxisSpacing: 4.0,
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisSpacing: 4.0,
|
crossAxisCount: crossCount.toInt(),
|
||||||
childAspectRatio: aspectRatio,
|
mainAxisSpacing: 4.0,
|
||||||
children: list,
|
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