feat: coin log

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-06-09 19:30:52 +08:00
parent 63a286056c
commit f1e4130201
28 changed files with 323 additions and 117 deletions

View File

@@ -69,9 +69,7 @@ class ArticleOpus extends StatelessWidget {
return Text(e.toString());
}
},
separatorBuilder: (context, index) {
return const SizedBox(height: 10);
},
separatorBuilder: (context, index) => const SizedBox(height: 10),
);
}
}

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/space/space/data.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member/widget/user_info_card.dart';
import 'package:PiliPlus/pages/member_coin_log/view.dart';
import 'package:PiliPlus/pages/member_contribute/view.dart';
import 'package:PiliPlus/pages/member_dynamics/view.dart';
import 'package:PiliPlus/pages/member_favorite/view.dart';
@@ -14,6 +15,7 @@ import 'package:PiliPlus/pages/member_pgc/view.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
class MemberPage extends StatefulWidget {
@@ -152,6 +154,17 @@ class _MemberPageState extends State<MemberPage> {
],
),
),
PopupMenuItem(
onTap: () => Get.to(const MemberCoinLogPage()),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(FontAwesomeIcons.b, size: 18),
SizedBox(width: 10),
Text('硬币记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.toNamed('/spaceSetting'),
child: const Row(

View File

@@ -4,10 +4,10 @@ import 'package:PiliPlus/models_new/member/coin_like_arc/data.dart';
import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class MemberCoinController
class MemberCoinArcController
extends CommonListController<CoinLikeArcData, CoinLikeArcItem> {
final dynamic mid;
MemberCoinController({this.mid});
MemberCoinArcController({this.mid});
@override
void onInit() {

View File

@@ -3,16 +3,16 @@ import 'package:PiliPlus/common/skeleton/video_card_v.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart';
import 'package:PiliPlus/pages/member_coin/controller.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
import 'package:PiliPlus/pages/member_coin_arc/controller.dart';
import 'package:PiliPlus/pages/member_coin_arc/widgets/item.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MemberCoinPage extends StatefulWidget {
const MemberCoinPage({
class MemberCoinArcPage extends StatefulWidget {
const MemberCoinArcPage({
super.key,
required this.mid,
this.name,
@@ -22,14 +22,14 @@ class MemberCoinPage extends StatefulWidget {
final String? name;
@override
State<MemberCoinPage> createState() => _MemberCoinPageState();
State<MemberCoinArcPage> createState() => _MemberCoinArcPageState();
}
class _MemberCoinPageState extends State<MemberCoinPage> {
class _MemberCoinArcPageState extends State<MemberCoinArcPage> {
late final _ownerMid = Accounts.main.mid;
late final _ctr = Get.put(
MemberCoinController(mid: widget.mid),
MemberCoinArcController(mid: widget.mid),
tag: Utils.makeHeroTag(widget.mid),
);

View File

@@ -0,0 +1,22 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models_new/coin_log/data.dart';
import 'package:PiliPlus/models_new/coin_log/list.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class MemberCoinLogController
extends CommonListController<CoinLogData, CoinLogItem> {
@override
void onInit() {
super.onInit();
queryData();
}
@override
List<CoinLogItem>? getDataList(CoinLogData response) {
return response.list;
}
@override
Future<LoadingState<CoinLogData>> customGetData() => UserHttp.coinLog();
}

View File

@@ -0,0 +1,136 @@
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/coin_log/list.dart';
import 'package:PiliPlus/pages/member_coin_log/controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MemberCoinLogPage extends StatefulWidget {
const MemberCoinLogPage({super.key});
@override
State<MemberCoinLogPage> createState() => _MemberCoinLogPageState();
}
class _MemberCoinLogPageState extends State<MemberCoinLogPage> {
late final _controller = Get.put(MemberCoinLogController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('硬币记录')),
body: SafeArea(
top: false,
bottom: false,
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 680),
child: CustomScrollView(
slivers: [
SliverPadding(
padding: EdgeInsets.only(
left: 10,
right: 10,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildBody(_controller.loadingState.value)),
),
],
),
),
),
),
);
}
Widget _buildBody(LoadingState<List<CoinLogItem>?> loadingState) {
return switch (loadingState) {
Loading() => const SliverToBoxAdapter(child: LinearProgressIndicator()),
Success(:var response) => response?.isNotEmpty == true
? Builder(
builder: (context) {
final them = Theme.of(context);
final outline = them.colorScheme.outline.withValues(alpha: 0.1);
final divider = Divider(
height: 1,
color: outline,
);
final sliverDivider = SliverToBoxAdapter(
child: divider,
);
final dividerV = VerticalDivider(
width: 1,
color: outline,
);
return SliverMainAxisGroup(
slivers: [
sliverDivider,
SliverToBoxAdapter(
child: ColoredBox(
color: them.colorScheme.onInverseSurface,
child: _item(
const CoinLogItem(
time: '时间',
delta: '变化',
reason: '原因',
),
dividerV,
isHeader: true,
),
),
),
sliverDivider,
SliverList.separated(
itemCount: response!.length,
itemBuilder: (context, index) {
return _item(response[index], dividerV);
},
separatorBuilder: (context, index) => divider,
),
sliverDivider,
],
);
},
)
: HttpError(onReload: _controller.onReload),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: _controller.onReload,
),
};
}
Widget _item(CoinLogItem item, Widget divider, {bool isHeader = false}) {
Widget text(int flex, String text) => Expanded(
flex: flex,
child: Padding(
padding: isHeader
? const EdgeInsets.symmetric(vertical: 6)
: const EdgeInsets.symmetric(vertical: 8),
child: Center(
child: Text(
text,
textAlign: TextAlign.center,
style: isHeader
? const TextStyle(fontSize: 13, fontWeight: FontWeight.bold)
: const TextStyle(fontSize: 13),
),
),
),
);
Widget content = Row(
children: [
divider,
text(3, item.time),
divider,
text(1, item.delta),
divider,
text(4, item.reason),
divider,
],
);
return IntrinsicHeight(
child: isHeader ? content : SelectionArea(child: content),
);
}
}

View File

@@ -7,11 +7,11 @@ import 'package:PiliPlus/models_new/space/space/data.dart';
import 'package:PiliPlus/models_new/space/space/tab2.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member_article/widget/item.dart';
import 'package:PiliPlus/pages/member_coin/view.dart';
import 'package:PiliPlus/pages/member_coin_arc/view.dart';
import 'package:PiliPlus/pages/member_contribute/controller.dart';
import 'package:PiliPlus/pages/member_home/widgets/fav_item.dart';
import 'package:PiliPlus/pages/member_home/widgets/video_card_v_member_home.dart';
import 'package:PiliPlus/pages/member_like/view.dart';
import 'package:PiliPlus/pages/member_like_arc/view.dart';
import 'package:PiliPlus/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
@@ -281,7 +281,7 @@ class _MemberHomeState extends State<MemberHome>
_ctr.tabController?.animateTo(index);
} else {
if (param == 'coinArchive') {
Get.to(MemberCoinPage(
Get.to(MemberCoinArcPage(
mid: _ctr.mid,
name: _ctr.username,
));
@@ -289,7 +289,7 @@ class _MemberHomeState extends State<MemberHome>
}
if (param == 'likeArchive') {
Get.to(MemberLikePage(
Get.to(MemberLikeArcPage(
mid: _ctr.mid,
name: _ctr.username,
));

View File

@@ -4,10 +4,10 @@ import 'package:PiliPlus/models_new/member/coin_like_arc/data.dart';
import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
class MemberLikeController
class MemberLikeArcController
extends CommonListController<CoinLikeArcData, CoinLikeArcItem> {
final dynamic mid;
MemberLikeController({this.mid});
MemberLikeArcController({this.mid});
@override
void onInit() {

View File

@@ -3,16 +3,16 @@ import 'package:PiliPlus/common/skeleton/video_card_v.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart';
import 'package:PiliPlus/pages/member_coin/widgets/item.dart';
import 'package:PiliPlus/pages/member_like/controller.dart';
import 'package:PiliPlus/pages/member_coin_arc/widgets/item.dart';
import 'package:PiliPlus/pages/member_like_arc/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MemberLikePage extends StatefulWidget {
const MemberLikePage({
class MemberLikeArcPage extends StatefulWidget {
const MemberLikeArcPage({
super.key,
required this.mid,
this.name,
@@ -22,14 +22,14 @@ class MemberLikePage extends StatefulWidget {
final String? name;
@override
State<MemberLikePage> createState() => _MemberLikePageState();
State<MemberLikeArcPage> createState() => _MemberLikeArcPageState();
}
class _MemberLikePageState extends State<MemberLikePage> {
class _MemberLikeArcPageState extends State<MemberLikeArcPage> {
late final _ownerMid = Accounts.main.mid;
late final _ctr = Get.put(
MemberLikeController(mid: widget.mid),
MemberLikeArcController(mid: widget.mid),
tag: Utils.makeHeroTag(widget.mid),
);

View File

@@ -103,6 +103,7 @@ class _MinePageState extends State<MinePage> {
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
);
final isVip = userInfo.vipStatus != null && userInfo.vipStatus! > 0;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -125,8 +126,7 @@ class _MinePageState extends State<MinePage> {
width: 55,
height: 55,
),
if (userInfo.vipStatus != null &&
userInfo.vipStatus! > 0)
if (isVip)
Positioned(
right: -1,
bottom: -2,
@@ -161,9 +161,7 @@ class _MinePageState extends State<MinePage> {
userInfo.uname ?? '点击头像登录',
style: theme.textTheme.titleMedium!.copyWith(
height: 1,
color: userInfo.vipStatus != null &&
userInfo.vipStatus! > 0 &&
userInfo.vipType == 2
color: isVip && userInfo.vipType == 2
? context.vipColor
: null,
),

View File

@@ -65,6 +65,12 @@ class _AtMePageState extends State<AtMePage> {
Widget _buildBody(
ThemeData theme, LoadingState<List<MsgAtItem>?> loadingState) {
late final divider = Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 12,
@@ -152,14 +158,7 @@ class _AtMePageState extends State<AtMePage> {
: null,
);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
},
separatorBuilder: (context, index) => divider,
)
: HttpError(onReload: _atMeController.onReload),
Error(:var errMsg) => HttpError(

View File

@@ -65,6 +65,12 @@ class _LikeMePageState extends State<LikeMePage> {
}
Widget _buildBody(ThemeData theme, LoadingState loadingState) {
late final divider = Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 12,
@@ -99,14 +105,7 @@ class _LikeMePageState extends State<LikeMePage> {
);
},
itemCount: latest.length,
separatorBuilder: (BuildContext context, int index) {
return Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
},
separatorBuilder: (context, index) => divider,
),
],
if (total.isNotEmpty) ...[
@@ -129,14 +128,7 @@ class _LikeMePageState extends State<LikeMePage> {
);
},
itemCount: total.length,
separatorBuilder: (BuildContext context, int index) {
return Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
},
separatorBuilder: (context, index) => divider,
),
],
],

View File

@@ -65,6 +65,12 @@ class _ReplyMePageState extends State<ReplyMePage> {
Widget _buildBody(
ThemeData theme, LoadingState<List<MsgReplyItem>?> loadingState) {
late final divider = Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 12,
@@ -166,14 +172,7 @@ class _ReplyMePageState extends State<ReplyMePage> {
),
);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
},
separatorBuilder: (context, index) => divider,
)
: HttpError(onReload: _replyMeController.onReload),
Error(:var errMsg) => HttpError(

View File

@@ -50,6 +50,12 @@ class _SysMsgPageState extends State<SysMsgPage> {
Widget _buildBody(
ThemeData theme, LoadingState<List<MsgSysItem>?> loadingState) {
late final divider = Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverSafeArea(
sliver: SliverList.builder(
@@ -107,14 +113,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
),
);
},
separatorBuilder: (BuildContext context, int index) {
return Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withValues(alpha: 0.1),
);
},
separatorBuilder: (context, index) => divider,
)
: HttpError(onReload: _sysMsgController.onReload),
Error(:var errMsg) => HttpError(

View File

@@ -73,6 +73,10 @@ class _PgcReviewChildPageState extends State<PgcReviewChildPage>
Widget _buildBody(
ThemeData theme, LoadingState<List<PgcReviewItemModel>?> loadingState) {
late final divider = Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverToBoxAdapter(
child: ListView.builder(
@@ -93,10 +97,7 @@ class _PgcReviewChildPageState extends State<PgcReviewChildPage>
return _itemWidget(theme, index, response[index]);
},
itemCount: response!.length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
)
: HttpError(onReload: _controller.onReload),
Error(:var errMsg) => HttpError(

View File

@@ -142,6 +142,11 @@ class _SearchTrendingPageState extends State<SearchTrendingPage> {
Widget _buildBody(ThemeData theme,
LoadingState<List<SearchTrendingItemModel>?> loadingState) {
late final divider = Divider(
height: 1,
indent: 48,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => const SliverToBoxAdapter(child: LinearProgressIndicator()),
Success(:var response) => response?.isNotEmpty == true
@@ -206,11 +211,7 @@ class _SearchTrendingPageState extends State<SearchTrendingPage> {
),
);
},
separatorBuilder: (context, index) => Divider(
height: 1,
indent: 48,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
)
: HttpError(
onReload: _controller.onReload,

View File

@@ -417,13 +417,13 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
color: theme.colorScheme.outline,
);
final divider = SliverToBoxAdapter(
child: Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
final divider = Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
);
final sliverDivider = SliverToBoxAdapter(child: divider);
final dividerL = SliverToBoxAdapter(
child: Divider(
thickness: 16,
@@ -440,9 +440,9 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
dividerL,
SliverToBoxAdapter(
child: _blockLimitItem(theme, titleStyle, subTitleStyle)),
divider,
sliverDivider,
SliverToBoxAdapter(child: _blockToastItem(titleStyle)),
divider,
sliverDivider,
SliverToBoxAdapter(child: _blockTrackItem(titleStyle, subTitleStyle)),
dividerL,
SliverList.separated(
@@ -536,15 +536,12 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
),
),
),
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
),
dividerL,
SliverToBoxAdapter(
child: _userIdItem(theme, titleStyle, subTitleStyle)),
divider,
sliverDivider,
SliverToBoxAdapter(
child: _blockServerItem(theme, titleStyle, subTitleStyle)),
dividerL,

View File

@@ -160,6 +160,10 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
Widget _buildBody(
ThemeData theme, LoadingState<List<VideoNoteItemModel>?> loadingState) {
late final divider = Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverToBoxAdapter(
child: ListView.builder(
@@ -180,10 +184,7 @@ class _NoteListPageState extends CommonSlidePageState<NoteListPage> {
return _itemWidget(theme, response[index]);
},
itemCount: response!.length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
)
: HttpError(onReload: _controller.onReload),
Error(:var errMsg) => HttpError(

View File

@@ -87,6 +87,10 @@ class _ViewPointsPageState
@override
Widget buildList(ThemeData theme) {
final divider = Divider(
height: 1,
color: theme.dividerColor.withValues(alpha: 0.1),
);
return ListView.separated(
controller: ScrollController(),
physics: const AlwaysScrollableScrollPhysics(),
@@ -157,10 +161,7 @@ class _ViewPointsPageState
),
);
},
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.dividerColor.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
);
}
}

View File

@@ -86,6 +86,12 @@ class _WhisperPageState extends State<WhisperPage> {
}
Widget _buildBody(LoadingState<List<Session>?> loadingState) {
late final divider = Divider(
indent: 72,
endIndent: 20,
height: 1,
color: Colors.grey.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 12,
@@ -109,12 +115,7 @@ class _WhisperPageState extends State<WhisperPage> {
onRemove: (talkerId) => _controller.onRemove(index, talkerId),
);
},
separatorBuilder: (context, index) => Divider(
indent: 72,
endIndent: 20,
height: 1,
color: Colors.grey.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
)
: HttpError(
onReload: _controller.onReload,

View File

@@ -168,8 +168,7 @@ class _WhisperDetailPageState
: null,
);
},
separatorBuilder: (BuildContext context, int index) =>
const SizedBox(height: 12),
separatorBuilder: (context, index) => const SizedBox(height: 12),
)
: scrollErrorWidget(
onReload: _whisperDetailController.onReload,

View File

@@ -77,6 +77,12 @@ class _WhisperSecPageState extends State<WhisperSecPage> {
}
Widget _buildBody(LoadingState<List<Session>?> loadingState) {
late final divider = Divider(
indent: 72,
endIndent: 20,
height: 1,
color: Colors.grey.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 12,
@@ -100,12 +106,7 @@ class _WhisperSecPageState extends State<WhisperSecPage> {
onRemove: (talkerId) => _controller.onRemove(index, talkerId),
);
},
separatorBuilder: (context, index) => Divider(
indent: 72,
endIndent: 20,
height: 1,
color: Colors.grey.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
)
: HttpError(
onReload: _controller.onReload,

View File

@@ -145,6 +145,10 @@ class _WhisperSettingsPageState extends State<WhisperSettingsPage> {
Widget _buildBody(
ThemeData theme, LoadingState<PbMap<int, Setting>> loadingState) {
late final divider = Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
);
return switch (loadingState) {
Loading() => const SizedBox.shrink(),
Success<PbMap<int, Setting>>(:var response) =>
@@ -163,10 +167,7 @@ class _WhisperSettingsPageState extends State<WhisperSettingsPage> {
onRedirect: () => onRedirect(theme, key, response, item),
);
},
separatorBuilder: (context, index) => Divider(
height: 1,
color: theme.colorScheme.outline.withValues(alpha: 0.1),
),
separatorBuilder: (context, index) => divider,
);
}),
Error(:var errMsg) => scrollErrorWidget(