feat: 动态筛选&UP主筛选
This commit is contained in:
@ -134,6 +134,7 @@ class Api {
|
||||
|
||||
// 正在直播的up & 关注的up
|
||||
// https://api.bilibili.com/x/polymer/web-dynamic/v1/portal
|
||||
static const String followUp = '/x/polymer/web-dynamic/v1/portal';
|
||||
|
||||
// 关注的up动态
|
||||
// https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all
|
||||
|
@ -1,19 +1,26 @@
|
||||
import 'package:pilipala/http/index.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
|
||||
class DynamicsHttp {
|
||||
static Future followDynamic({
|
||||
String? type,
|
||||
int? page,
|
||||
String? offset,
|
||||
int? mid,
|
||||
}) async {
|
||||
var res = await Request().get(Api.followDynamic, data: {
|
||||
Map<String, dynamic> data = {
|
||||
'type': type ?? 'all',
|
||||
'page': page ?? 1,
|
||||
'timezone_offset': '-480',
|
||||
'offset': page == 1 ? '' : offset,
|
||||
'features': 'itemOpusStyle'
|
||||
});
|
||||
};
|
||||
if (mid != -1) {
|
||||
data['host_mid'] = mid;
|
||||
data.remove('timezone_offset');
|
||||
}
|
||||
var res = await Request().get(Api.followDynamic, data: data);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
@ -27,4 +34,20 @@ class DynamicsHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future followUp() async {
|
||||
var res = await Request().get(Api.followUp);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': FollowUpModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,5 +7,5 @@ enum DynamicsType {
|
||||
|
||||
extension BusinessTypeExtension on DynamicsType {
|
||||
String get values => ['all', 'video', 'pgc', 'article'][index];
|
||||
String get labels => ['全部', '视频投稿', '追番追剧', '专栏'][index];
|
||||
String get labels => ['全部', '视频', '追番', '专栏'][index];
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ class OpusPicsModel {
|
||||
OpusPicsModel.fromJson(Map<String, dynamic> json) {
|
||||
width = json['width'];
|
||||
height = json['height'];
|
||||
size = json['size'].toInt();
|
||||
size = json['size'] != null ? json['size'].toInt() : 0;
|
||||
src = json['src'];
|
||||
url = json['url'];
|
||||
}
|
||||
|
93
lib/models/dynamics/up.dart
Normal file
93
lib/models/dynamics/up.dart
Normal file
@ -0,0 +1,93 @@
|
||||
class FollowUpModel {
|
||||
FollowUpModel({
|
||||
this.liveUsers,
|
||||
this.upList,
|
||||
});
|
||||
|
||||
LiveUsers? liveUsers;
|
||||
List<UpItem>? upList;
|
||||
|
||||
FollowUpModel.fromJson(Map<String, dynamic> json) {
|
||||
liveUsers = LiveUsers.fromJson(json['live_users']);
|
||||
upList = json['up_list'] != null
|
||||
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
class LiveUsers {
|
||||
LiveUsers({
|
||||
this.count,
|
||||
this.group,
|
||||
this.items,
|
||||
});
|
||||
|
||||
int? count;
|
||||
String? group;
|
||||
List<LiveUserItem>? items;
|
||||
|
||||
LiveUsers.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
group = json['group'];
|
||||
items = json['items']
|
||||
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class LiveUserItem {
|
||||
LiveUserItem({
|
||||
this.face,
|
||||
this.isReserveRecall,
|
||||
this.jumpUrl,
|
||||
this.mid,
|
||||
this.roomId,
|
||||
this.title,
|
||||
this.uname,
|
||||
});
|
||||
|
||||
String? face;
|
||||
bool? isReserveRecall;
|
||||
String? jumpUrl;
|
||||
int? mid;
|
||||
int? roomId;
|
||||
String? title;
|
||||
String? uname;
|
||||
bool hasUpdate = false;
|
||||
String type = 'live';
|
||||
|
||||
LiveUserItem.fromJson(Map<String, dynamic> json) {
|
||||
face = json['face'];
|
||||
isReserveRecall = json['is_reserve_recall'];
|
||||
jumpUrl = json['jump_url'];
|
||||
mid = json['mid'];
|
||||
roomId = json['room_id'];
|
||||
title = json['title'];
|
||||
uname = json['uname'];
|
||||
}
|
||||
}
|
||||
|
||||
class UpItem {
|
||||
UpItem({
|
||||
this.face,
|
||||
this.hasUpdate,
|
||||
this.isReserveRecall,
|
||||
this.mid,
|
||||
this.uname,
|
||||
});
|
||||
|
||||
String? face;
|
||||
bool? hasUpdate;
|
||||
bool? isReserveRecall;
|
||||
int? mid;
|
||||
String? uname;
|
||||
String type = 'up';
|
||||
|
||||
UpItem.fromJson(Map<String, dynamic> json) {
|
||||
face = json['face'];
|
||||
hasUpdate = json['has_update'];
|
||||
isReserveRecall = json['is_reserve_recall'];
|
||||
mid = json['mid'];
|
||||
uname = json['uname'];
|
||||
}
|
||||
}
|
@ -3,22 +3,53 @@ 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/common/dynamics_type.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class DynamicsController extends GetxController {
|
||||
int page = 1;
|
||||
String? offset = '';
|
||||
RxList<DynamicItemModel>? dynamicsList = [DynamicItemModel()].obs;
|
||||
RxString dynamicsType = 'all'.obs;
|
||||
Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs;
|
||||
RxString dynamicsTypeLabel = '全部'.obs;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Rx<FollowUpModel> upData = FollowUpModel().obs;
|
||||
// 默认获取全部动态
|
||||
int mid = -1;
|
||||
List filterTypeList = [
|
||||
{
|
||||
'label': DynamicsType.all.labels,
|
||||
'value': DynamicsType.all,
|
||||
'enabled': true
|
||||
},
|
||||
{
|
||||
'label': DynamicsType.video.labels,
|
||||
'value': DynamicsType.video,
|
||||
'enabled': true
|
||||
},
|
||||
{
|
||||
'label': DynamicsType.pgc.labels,
|
||||
'value': DynamicsType.pgc,
|
||||
'enabled': true
|
||||
},
|
||||
{
|
||||
'label': DynamicsType.article.labels,
|
||||
'value': DynamicsType.article,
|
||||
'enabled': true
|
||||
},
|
||||
];
|
||||
|
||||
Future queryFollowDynamic({type = 'init'}) async {
|
||||
// if (type == 'init') {
|
||||
// dynamicsList!.value = [];
|
||||
// }
|
||||
var res = await DynamicsHttp.followDynamic(
|
||||
page: type == 'init' ? 1 : page,
|
||||
type: dynamicsType.value,
|
||||
type: dynamicsType.value.values,
|
||||
offset: offset,
|
||||
mid: mid,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
@ -32,12 +63,10 @@ class DynamicsController extends GetxController {
|
||||
return res;
|
||||
}
|
||||
|
||||
onSelectType(value, label) async {
|
||||
onSelectType(value) async {
|
||||
dynamicsType.value = value;
|
||||
dynamicsTypeLabel.value = label;
|
||||
await queryFollowDynamic();
|
||||
scrollController.animateTo(0,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
scrollController.jumpTo(0);
|
||||
}
|
||||
|
||||
pushDetail(item, floor, {action = 'all'}) async {
|
||||
@ -86,4 +115,18 @@ class DynamicsController extends GetxController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future queryFollowUp() async {
|
||||
var res = await DynamicsHttp.followUp();
|
||||
if (res['status']) {
|
||||
upData.value = res['data'];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
onSelectUp(mid) async {
|
||||
dynamicsType.value = DynamicsType.values[0];
|
||||
|
||||
queryFollowDynamic();
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,9 @@ class DynamicDetailController extends GetxController {
|
||||
} else {
|
||||
replyList.addAll(replies);
|
||||
}
|
||||
if (replyList.length == acount.value) {
|
||||
noMore.value = '没有更多了';
|
||||
}
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
@ -7,6 +8,7 @@ import 'package:pilipala/models/dynamics/result.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
import 'widgets/up_panel.dart';
|
||||
|
||||
class DynamicsPage extends StatefulWidget {
|
||||
const DynamicsPage({super.key});
|
||||
@ -19,7 +21,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
Future? _futureBuilderFuture;
|
||||
// final ScrollController scrollController = ScrollController();
|
||||
bool _isLoadingMore = false;
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@ -48,81 +49,140 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: const Text('动态'),
|
||||
actions: [
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
titleSpacing: 0,
|
||||
title: SizedBox(
|
||||
height: 36,
|
||||
child: Stack(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Obx(
|
||||
() => PopupMenuButton(
|
||||
initialValue: _dynamicsController.dynamicsType.value,
|
||||
position: PopupMenuPosition.under,
|
||||
itemBuilder: (context) => [
|
||||
for (var i in DynamicsType.values) ...[
|
||||
PopupMenuItem(
|
||||
value: i.values,
|
||||
onTap: () =>
|
||||
_dynamicsController.onSelectType(i.values, i.labels),
|
||||
child: Text(i.labels),
|
||||
() => SegmentedButton<DynamicsType>(
|
||||
showSelectedIcon: false,
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 10)),
|
||||
side: MaterialStateProperty.all(
|
||||
BorderSide(
|
||||
color: Theme.of(context).hintColor, width: 0.5),
|
||||
),
|
||||
),
|
||||
segments: <ButtonSegment<DynamicsType>>[
|
||||
for (var i in _dynamicsController.filterTypeList) ...[
|
||||
ButtonSegment<DynamicsType>(
|
||||
value: i['value'],
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Text(i['label']),
|
||||
),
|
||||
enabled: i['enabled'],
|
||||
),
|
||||
]
|
||||
],
|
||||
selected: <DynamicsType>{
|
||||
_dynamicsController.dynamicsType.value
|
||||
},
|
||||
onSelectionChanged: (Set<DynamicsType> newSelection) {
|
||||
_dynamicsController.dynamicsType.value =
|
||||
newSelection.first;
|
||||
_dynamicsController.onSelectType(newSelection.first);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
_dynamicsController.mid = -1;
|
||||
_dynamicsController.dynamicsType.value =
|
||||
DynamicsType.values[0];
|
||||
SmartDialog.showToast('还原默认加载',
|
||||
alignment: Alignment.topCenter);
|
||||
_dynamicsController.queryFollowDynamic();
|
||||
},
|
||||
icon: const Icon(Icons.history),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
_dynamicsController.dynamicsTypeLabel.value,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4)
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
_dynamicsController.page = 1;
|
||||
_dynamicsController.queryFollowUp();
|
||||
await _dynamicsController.queryFollowDynamic();
|
||||
},
|
||||
child: FutureBuilder(
|
||||
child: CustomScrollView(
|
||||
controller: _dynamicsController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _dynamicsController.queryFollowUp(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 80));
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 115,
|
||||
child: UpPanelSkeleton(),
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
List<DynamicItemModel> list = _dynamicsController.dynamicsList!;
|
||||
List<DynamicItemModel> list =
|
||||
_dynamicsController.dynamicsList!;
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
controller: _dynamicsController.scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: list.length,
|
||||
itemBuilder: (BuildContext context, index) {
|
||||
() => SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return DynamicPanel(item: list[index]);
|
||||
},
|
||||
}, childCount: list.length),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => _dynamicsController.queryFollowDynamic(),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 5,
|
||||
itemBuilder: ((context, index) => const DynamicCardSkeleton()),
|
||||
);
|
||||
return skeleton();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget skeleton() {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
263
lib/pages/dynamics/widgets/up_panel.dart
Normal file
263
lib/pages/dynamics/widgets/up_panel.dart
Normal file
@ -0,0 +1,263 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_meedu_media_kit/meedu_player.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/common/dynamics_type.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/pages/dynamics/controller.dart';
|
||||
|
||||
class UpPanel extends StatefulWidget {
|
||||
FollowUpModel? upData;
|
||||
UpPanel(this.upData, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UpPanel> createState() => _UpPanelState();
|
||||
}
|
||||
|
||||
class _UpPanelState extends State<UpPanel> {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
int currentMid = -1;
|
||||
late double contentWidth = 56;
|
||||
List<UpItem> upList = [];
|
||||
List<LiveUserItem> liveList = [];
|
||||
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
upList = widget.upData!.upList!;
|
||||
liveList = widget.upData!.liveUsers!.items!;
|
||||
upList.insert(0, UpItem(face: '', uname: '全部动态', mid: -1));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPersistentHeader(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
delegate: _SliverHeaderDelegate(
|
||||
height: 115,
|
||||
child: Container(
|
||||
height: 115,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
],
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 5, left: 12, right: 12, bottom: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
Text(
|
||||
'最常访问',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: scrollController,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
for (int i = 0; i < liveList.length; i++) ...[
|
||||
upItemBuild(liveList[i], i)
|
||||
],
|
||||
VerticalDivider(
|
||||
indent: 15,
|
||||
endIndent: 35,
|
||||
width: 26,
|
||||
color: Theme.of(context).primaryColor.withOpacity(0.5),
|
||||
),
|
||||
for (int i = 0; i < upList.length; i++) ...[
|
||||
upItemBuild(upList[i], i)
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget upItemBuild(data, i) {
|
||||
bool isCurrent = currentMid == data.mid || currentMid == -1;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (data.type == 'up') {
|
||||
currentMid = data.mid;
|
||||
Get.find<DynamicsController>().mid = data.mid;
|
||||
Get.find<DynamicsController>().onSelectUp(data.mid);
|
||||
int liveLen = liveList.length;
|
||||
int upLen = upList.length;
|
||||
double itemWidth = contentWidth + itemPadding.horizontal;
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
double moveDistance = 0.0;
|
||||
if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||
moveDistance =
|
||||
(i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||
} else {
|
||||
moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
||||
}
|
||||
scrollController.animateTo(
|
||||
moveDistance,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
} else if (data.type == 'live') {
|
||||
SmartDialog.showToast('直播功能暂未开发');
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: itemPadding,
|
||||
child: AnimatedOpacity(
|
||||
opacity: isCurrent ? 1 : 0.3,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Badge(
|
||||
smallSize: 8,
|
||||
label: data.type == 'live' ? const Text('Live') : null,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
isLabelVisible: data.type == 'live' ||
|
||||
(data.type == 'up' && (data.hasUpdate ?? false)),
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
child: NetworkImgLayer(
|
||||
width: 49,
|
||||
height: 49,
|
||||
src: data.face,
|
||||
type: 'avatar',
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: SizedBox(
|
||||
width: contentWidth,
|
||||
child: Text(
|
||||
data.uname,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: currentMid == data.mid
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
_SliverHeaderDelegate({required this.height, required this.child});
|
||||
|
||||
final double height;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return child;
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => height;
|
||||
|
||||
@override
|
||||
double get minExtent => height;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) =>
|
||||
true;
|
||||
}
|
||||
|
||||
class UpPanelSkeleton extends StatelessWidget {
|
||||
const UpPanelSkeleton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5, left: 12, right: 12, bottom: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
Text(
|
||||
'最常访问',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 10,
|
||||
itemBuilder: ((context, index) => Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 49,
|
||||
height: 49,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 6),
|
||||
width: 45,
|
||||
height: 12,
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user