feat: fav note page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-03-28 21:25:39 +08:00
parent da3f64feab
commit cc774015f9
15 changed files with 487 additions and 91 deletions

View File

@@ -0,0 +1,85 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/fav/note/controller.dart';
import 'package:PiliPlus/pages/fav/note/widget/item.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class FavNoteChildPage extends StatefulWidget {
const FavNoteChildPage({super.key, required this.isPublish});
final bool isPublish;
@override
State<FavNoteChildPage> createState() => _FavNoteChildPageState();
}
class _FavNoteChildPageState extends State<FavNoteChildPage>
with AutomaticKeepAliveClientMixin {
late final FavNoteController _favNoteController =
Get.put(FavNoteController(widget.isPublish), tag: '${widget.isPublish}');
@override
Widget build(BuildContext context) {
super.build(context);
return refreshIndicator(
onRefresh: () async {
await _favNoteController.onRefresh();
},
child: CustomScrollView(
slivers: [
Obx(() => _buildBody(_favNoteController.loadingState.value)),
],
),
);
}
Widget _buildBody(LoadingState loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const VideoCardHSkeleton();
},
childCount: 10,
),
),
Success() => (loadingState.response as List?)?.isNotEmpty == true
? SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.7,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return FavNoteItem(item: loadingState.response[index]);
},
childCount: loadingState.response.length,
),
),
)
: HttpError(callback: _favNoteController.onReload),
Error() => HttpError(
errMsg: loadingState.errMsg,
callback: _favNoteController.onReload,
),
LoadingState() => throw UnimplementedError(),
};
}
@override
bool get wantKeepAlive => true;
}

View File

@@ -0,0 +1,22 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
class FavNoteController extends MultiSelectController {
FavNoteController(this.isPublish);
final bool isPublish;
@override
void onInit() {
super.onInit();
queryData();
}
@override
Future<LoadingState> customGetData() {
return isPublish
? VideoHttp.userNoteList(page: currentPage)
: VideoHttp.noteList(page: currentPage);
}
}

View File

@@ -0,0 +1,87 @@
import 'package:PiliPlus/pages/fav/note/child_view.dart';
import 'package:flutter/material.dart';
class FavNotePage extends StatefulWidget {
const FavNotePage({super.key});
@override
State<FavNotePage> createState() => _FavNotePageState();
}
class _FavNotePageState extends State<FavNotePage>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
late final TabController _tabController =
TabController(length: 2, vsync: this);
@override
bool get wantKeepAlive => true;
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: TabBar(
overlayColor: WidgetStateProperty.all(Colors.transparent),
splashFactory: NoSplash.splashFactory,
isScrollable: true,
tabAlignment: TabAlignment.start,
controller: _tabController,
padding: const EdgeInsets.symmetric(horizontal: 8),
dividerHeight: 0,
indicatorWeight: 0,
indicatorPadding:
const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
indicator: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(20),
),
indicatorSize: TabBarIndicatorSize.tab,
labelStyle: TabBarTheme.of(context)
.labelStyle
?.copyWith(fontSize: 14) ??
const TextStyle(fontSize: 14),
labelColor: Theme.of(context).colorScheme.onSecondaryContainer,
unselectedLabelColor: Theme.of(context).colorScheme.outline,
tabs: [
Tab(text: '未发布笔记'),
Tab(text: '公开笔记'),
],
),
),
TextButton(
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.onSurfaceVariant,
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {},
child: const Text('管理'),
),
const SizedBox(width: 12),
],
),
Expanded(
child: TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
FavNoteChildPage(isPublish: false),
FavNoteChildPage(isPublish: true),
],
),
),
],
);
}
}

View File

@@ -0,0 +1,84 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class FavNoteItem extends StatelessWidget {
const FavNoteItem({super.key, required this.item});
final dynamic item;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Utils.handleWebview(item['web_url'], inApp: true);
},
onLongPress: () {},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
item['title'],
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
height: 1.4,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 1),
Text(
item['summary'],
maxLines: 1,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
const SizedBox(height: 1),
Text(
item['message'],
maxLines: 1,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
],
),
),
if (item['arc']?['pic'] != null) ...[
const SizedBox(width: 10),
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder:
(BuildContext context, BoxConstraints boxConstraints) {
return NetworkImgLayer(
src: item['arc']?['pic'],
width: boxConstraints.maxWidth,
height: boxConstraints.maxHeight,
);
},
),
),
],
],
),
),
);
}
}