feat: 新增动态页面
This commit is contained in:
27
lib/pages/dynamics/controller.dart
Normal file
27
lib/pages/dynamics/controller.dart
Normal file
@ -0,0 +1,27 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/dynamics.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
|
||||
class DynamicsController extends GetxController {
|
||||
int page = 1;
|
||||
String reqType = 'all';
|
||||
String? offset;
|
||||
RxList<DynamicItemModel>? dynamicsList = [DynamicItemModel()].obs;
|
||||
|
||||
Future queryFollowDynamic({type = 'init'}) async {
|
||||
var res = await DynamicsHttp.followDynamic(
|
||||
page: page,
|
||||
type: reqType,
|
||||
offset: offset,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
dynamicsList!.value = res['data'].items;
|
||||
} else {
|
||||
dynamicsList!.addAll(res['data'].items);
|
||||
}
|
||||
offset = res['data'].offset;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
4
lib/pages/dynamics/index.dart
Normal file
4
lib/pages/dynamics/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library dynamics;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
78
lib/pages/dynamics/view.dart
Normal file
78
lib/pages/dynamics/view.dart
Normal file
@ -0,0 +1,78 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
|
||||
class DynamicsPage extends StatefulWidget {
|
||||
const DynamicsPage({super.key});
|
||||
|
||||
@override
|
||||
State<DynamicsPage> createState() => _DynamicsPageState();
|
||||
}
|
||||
|
||||
class _DynamicsPageState extends State<DynamicsPage> {
|
||||
DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
Future? _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: const Text('动态'),
|
||||
actions: [
|
||||
IconButton(onPressed: () {}, icon: const Icon(Icons.more_horiz)),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _dynamicsController.queryFollowDynamic(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
List<DynamicItemModel> list = _dynamicsController.dynamicsList!;
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: list.length,
|
||||
itemBuilder: (BuildContext context, index) {
|
||||
return DynamicPanel(item: list[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
// return SliverList(
|
||||
// delegate: SliverChildBuilderDelegate((context, index) {
|
||||
// return const VideoCardHSkeleton();
|
||||
// }, childCount: 10),
|
||||
// );
|
||||
return Text('加载中');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
272
lib/pages/dynamics/widgets/dynamic_panel.dart
Normal file
272
lib/pages/dynamics/widgets/dynamic_panel.dart
Normal file
@ -0,0 +1,272 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
|
||||
class DynamicPanel extends StatelessWidget {
|
||||
DynamicItemModel? item;
|
||||
DynamicPanel({this.item, Key? key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
width: 8,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
author(item, context),
|
||||
if (item!.modules!.moduleDynamic!.desc != null)
|
||||
content(item, context),
|
||||
Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
// padding: const EdgeInsets.only(left: 15, right: 15),
|
||||
child: forWard(item, context),
|
||||
),
|
||||
action(item, context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget author(item, context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 10, 12, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 40,
|
||||
height: 40,
|
||||
type: 'avatar',
|
||||
src: item.modules.moduleAuthor.face,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.modules.moduleAuthor.name),
|
||||
Text(
|
||||
item.modules.moduleAuthor.pubTime +
|
||||
(item.modules.moduleAuthor.pubAction != ''
|
||||
? ' - ${item.modules.moduleAuthor.pubAction}'
|
||||
: ''),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 内容
|
||||
Widget content(item, context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(15, 0, 15, 10),
|
||||
child: Text(
|
||||
item!.modules!.moduleDynamic!.desc!.text!,
|
||||
maxLines: 5,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 转发
|
||||
Widget forWard(item, context, {floor = 1}) {
|
||||
switch (item.type) {
|
||||
// 图文
|
||||
case 'DYNAMIC_TYPE_DRAW':
|
||||
return const Text('DYNAMIC_TYPE_DRAW');
|
||||
// 视频
|
||||
case 'DYNAMIC_TYPE_AV':
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (floor == 2) ...[
|
||||
Text('@' + item.modules.moduleAuthor.name),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: LayoutBuilder(builder: (context, box) {
|
||||
double width = box.maxWidth;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: floor == 1 ? 'emote' : null,
|
||||
width: width,
|
||||
height: width / StyleString.aspectRatio,
|
||||
src: item.modules.moduleDynamic.major.archive.cover,
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(12, 22, 10, 15),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[
|
||||
Colors.transparent,
|
||||
Colors.black87,
|
||||
],
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
borderRadius: floor == 1
|
||||
? null
|
||||
: const BorderRadius.all(Radius.circular(6))),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
item.modules.moduleDynamic.major.archive
|
||||
.durationText,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
item.modules.moduleDynamic.major.archive
|
||||
.stat.play,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
item.modules.moduleDynamic.major.archive
|
||||
.stat.danmaku,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Colors.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/play.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: floor == 1
|
||||
? const EdgeInsets.only(left: 12, right: 12)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
item.modules.moduleDynamic.major.archive.title,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
);
|
||||
// 文章
|
||||
case 'DYNAMIC_TYPE_ARTICLE':
|
||||
return const Text('DYNAMIC_TYPE_ARTICLE');
|
||||
// 转发
|
||||
case 'DYNAMIC_TYPE_FORWARD':
|
||||
// return const Text('DYNAMIC_TYPE_FORWARD');
|
||||
switch (item.orig.type) {
|
||||
// 递归
|
||||
case 'DYNAMIC_TYPE_AV':
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15, top: 10, right: 15, bottom: 10),
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
child: forWard(item.orig, context, floor: 2),
|
||||
);
|
||||
default:
|
||||
return const Text('渲染出错了');
|
||||
}
|
||||
// 直播
|
||||
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
||||
return const Text('DYNAMIC_TYPE_LIVE_RCMD');
|
||||
// 合集
|
||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
||||
return const Text('DYNAMIC_TYPE_UGC_SEASON');
|
||||
default:
|
||||
return const Text('渲染出错了');
|
||||
}
|
||||
}
|
||||
|
||||
// 操作栏
|
||||
Widget action(item, context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.shareFromSquare,
|
||||
size: 16,
|
||||
),
|
||||
label: const Text('转发'),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.comment,
|
||||
size: 16,
|
||||
),
|
||||
label: Text(item.modules.moduleStat.comment.count),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
FontAwesomeIcons.thumbsUp,
|
||||
size: 16,
|
||||
),
|
||||
label: Text(item.modules.moduleStat.like.count),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user