From 56917f22c1182554844648fd262e8cfea0fa217e Mon Sep 17 00:00:00 2001 From: wgh19 Date: Mon, 13 May 2024 19:43:24 +0800 Subject: [PATCH] user artworks; update ui --- lib/components/color_scheme.dart | 38 ----- lib/components/loading.dart | 53 +++--- lib/components/md.dart | 10 +- lib/components/segmented_button.dart | 2 +- lib/components/user_preview.dart | 4 +- lib/network/network.dart | 11 ++ lib/pages/illust_page.dart | 7 +- lib/pages/main_page.dart | 110 +++++++------ lib/pages/search_page.dart | 2 +- lib/pages/user_info_page.dart | 233 ++++++++++++++++++++------- 10 files changed, 291 insertions(+), 179 deletions(-) delete mode 100644 lib/components/color_scheme.dart diff --git a/lib/components/color_scheme.dart b/lib/components/color_scheme.dart deleted file mode 100644 index d2a827d..0000000 --- a/lib/components/color_scheme.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/widgets.dart'; - -class ColorScheme extends InheritedWidget{ - final Brightness brightness; - - const ColorScheme({super.key, required this.brightness, required super.child}); - - static ColorScheme of(BuildContext context){ - return context.dependOnInheritedWidgetOfExactType()!; - } - - bool get _light => brightness == Brightness.light; - - Color get primary => _light ? const Color(0xff00538a) : const Color(0xff9ccaff); - - Color get primaryContainer => _light ? const Color(0xff5fbdff) : const Color(0xff0079c5); - - Color get secondary => _light ? const Color(0xff426182) : const Color(0xffaac9ef); - - Color get secondaryContainer => _light ? const Color(0xffc1dcff) : const Color(0xff1f3f5f); - - Color get tertiary => _light ? const Color(0xff743192) : const Color(0xffebb2ff); - - Color get tertiaryContainer => _light ? const Color(0xffcf9ae8) : const Color(0xff9c58ba); - - Color get outline => _light ? const Color(0xff707883) : const Color(0xff89919d); - - Color get outlineVariant => _light ? const Color(0xffbfc7d3) : const Color(0xff404752); - - Color get errorColor => _light ? const Color(0xffff3131) : const Color(0xfff86a6a); - - @override - bool updateShouldNotify(covariant InheritedWidget oldWidget) { - return oldWidget is!ColorScheme || brightness != oldWidget.brightness; - } -} \ No newline at end of file diff --git a/lib/components/loading.dart b/lib/components/loading.dart index 6521ba4..991b425 100644 --- a/lib/components/loading.dart +++ b/lib/components/loading.dart @@ -89,30 +89,43 @@ abstract class MultiPageLoadingState }); } + @override + void initState() { + loadData(_page).then((value) { + if(value.success) { + _page++; + setState(() { + _isFirstLoading = false; + _data = value.data; + }); + } else { + setState(() { + _isFirstLoading = false; + _error = value.errorMessage!; + }); + } + }); + super.initState(); + } + + Widget buildLoading(BuildContext context) { + return const Center( + child: ProgressRing(), + ); + } + + Widget buildError(BuildContext context, String error) { + return Center( + child: Text(error), + ); + } + @override Widget build(BuildContext context) { if(_isFirstLoading){ - loadData(_page).then((value) { - if(value.success) { - _page++; - setState(() { - _isFirstLoading = false; - _data = value.data; - }); - } else { - setState(() { - _isFirstLoading = false; - _error = value.errorMessage!; - }); - } - }); - return const Center( - child: ProgressRing(), - ); + return buildLoading(context); } else if (_error != null){ - return Center( - child: Text(_error!), - ); + return buildError(context, _error!); } else { return buildContent(context, _data!); } diff --git a/lib/components/md.dart b/lib/components/md.dart index 2c5b65f..34147d4 100644 --- a/lib/components/md.dart +++ b/lib/components/md.dart @@ -1,3 +1,9 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' as md; -typedef MdIcons = Icons; \ No newline at end of file +typedef MdIcons = md.Icons; + +class ColorScheme { + static md.ColorScheme of(md.BuildContext context) { + return md.Theme.of(context).colorScheme; + } +} \ No newline at end of file diff --git a/lib/components/segmented_button.dart b/lib/components/segmented_button.dart index 2c7929d..4a8deba 100644 --- a/lib/components/segmented_button.dart +++ b/lib/components/segmented_button.dart @@ -1,7 +1,7 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:pixes/foundation/app.dart'; -import 'color_scheme.dart'; +import 'md.dart'; class SegmentedButton extends StatelessWidget { const SegmentedButton( diff --git a/lib/components/user_preview.dart b/lib/components/user_preview.dart index c9ea8ea..794c8c3 100644 --- a/lib/components/user_preview.dart +++ b/lib/components/user_preview.dart @@ -1,12 +1,12 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:pixes/components/animated_image.dart'; -import 'package:pixes/components/color_scheme.dart'; import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/image_provider.dart'; import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/utils/translation.dart'; import '../network/network.dart'; +import 'md.dart'; class UserPreviewWidget extends StatefulWidget { const UserPreviewWidget(this.user, {super.key}); @@ -90,7 +90,7 @@ class _UserPreviewWidgetState extends State { else Button( onPressed: follow, - child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).errorColor),), + child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),), ), ], ) diff --git a/lib/network/network.dart b/lib/network/network.dart index 4497fe6..6d5625e 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -313,4 +313,15 @@ class Network { return Res.error(res.errorMessage); } } + + Future>> getUserIllusts(String uid) async { + var res = await apiGet("/v1/user/illusts?filter=for_android&user_id=$uid&type=illust"); + if (res.success) { + return Res( + (res.data["illusts"] as List).map((e) => Illust.fromJson(e)).toList(), + subData: res.data["next_url"]); + } else { + return Res.error(res.errorMessage); + } + } } diff --git a/lib/pages/illust_page.dart b/lib/pages/illust_page.dart index 66282c3..2bdbf52 100644 --- a/lib/pages/illust_page.dart +++ b/lib/pages/illust_page.dart @@ -9,7 +9,8 @@ import 'package:pixes/pages/image_page.dart'; import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/utils/translation.dart'; -import '../components/color_scheme.dart'; +import '../components/md.dart'; + const _kBottomBarHeight = 64.0; @@ -304,7 +305,7 @@ class _BottomBarState extends State<_BottomBar> { else Button( onPressed: follow, - child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).errorColor),), + child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),), ), ], ), @@ -355,7 +356,7 @@ class _BottomBarState extends State<_BottomBar> { else if(widget.illust.isBookmarked) Icon( Icons.favorite, - color: ColorScheme.of(context).errorColor, + color: ColorScheme.of(context).error, size: 18, ) else diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 51976ce..24026d9 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -2,8 +2,8 @@ import "dart:async"; import "package:fluent_ui/fluent_ui.dart"; import "package:flutter/foundation.dart"; +import "package:flutter/material.dart" as md; import "package:pixes/appdata.dart"; -import "package:pixes/components/color_scheme.dart"; import "package:pixes/components/md.dart"; import "package:pixes/foundation/app.dart"; import "package:pixes/network/network.dart"; @@ -73,56 +73,64 @@ class _MainPageState extends State with WindowListener { content: LoginPage(() => setState(() {})), ); } - return ColorScheme( - brightness: FluentTheme.of(context).brightness, - child: NavigationView( - appBar: buildAppBar(context, navigatorKey), - pane: NavigationPane( - selected: index, - onChanged: (value) { - setState(() { - index = value; - }); - navigate(value); - }, - items: [ - UserPane(), - PaneItem( - icon: const Icon(MdIcons.search, size: 20,), - title: Text('Search'.tl), - body: const SizedBox.shrink(), - ), - PaneItemHeader(header: Text("Artwork".tl).paddingVertical(4).paddingLeft(8)), - PaneItem( - icon: const Icon(MdIcons.star_border, size: 20,), - title: Text('Recommendations'.tl), - body: const SizedBox.shrink(), - ), - PaneItem( - icon: const Icon(MdIcons.bookmark_outline, size: 20), - title: Text('Bookmarks'.tl), - body: const SizedBox.shrink(), - ), - PaneItemSeparator(), - PaneItem( - icon: const Icon(MdIcons.explore_outlined, size: 20), - title: Text('Explore'.tl), - body: const SizedBox.shrink(), - ), - ], - footerItems: [ - PaneItem( - icon: const Icon(MdIcons.settings_outlined, size: 20), - title: Text('Settings'.tl), - body: const SizedBox.shrink(), - ), - ], - ), - paneBodyBuilder: (pane, child) => Navigator( - key: navigatorKey, - onGenerateRoute: (settings) => AppPageRoute( - builder: (context) => const RecommendationPage()), - ))); + return md.Theme( + data: md.ThemeData.from( + useMaterial3: true, + colorScheme: md.ColorScheme.fromSeed( + seedColor: FluentTheme.of(context).accentColor.withOpacity(1), + brightness: FluentTheme.of(context).brightness, + )), + child: DefaultSelectionStyle.merge( + selectionColor: FluentTheme.of(context).selectionColor.withOpacity(0.4), + child: NavigationView( + appBar: buildAppBar(context, navigatorKey), + pane: NavigationPane( + selected: index, + onChanged: (value) { + setState(() { + index = value; + }); + navigate(value); + }, + items: [ + UserPane(), + PaneItem( + icon: const Icon(MdIcons.search, size: 20,), + title: Text('Search'.tl), + body: const SizedBox.shrink(), + ), + PaneItemHeader(header: Text("Artwork".tl).paddingVertical(4).paddingLeft(8)), + PaneItem( + icon: const Icon(MdIcons.star_border, size: 20,), + title: Text('Recommendations'.tl), + body: const SizedBox.shrink(), + ), + PaneItem( + icon: const Icon(MdIcons.bookmark_outline, size: 20), + title: Text('Bookmarks'.tl), + body: const SizedBox.shrink(), + ), + PaneItemSeparator(), + PaneItem( + icon: const Icon(MdIcons.explore_outlined, size: 20), + title: Text('Explore'.tl), + body: const SizedBox.shrink(), + ), + ], + footerItems: [ + PaneItem( + icon: const Icon(MdIcons.settings_outlined, size: 20), + title: Text('Settings'.tl), + body: const SizedBox.shrink(), + ), + ], + ), + paneBodyBuilder: (pane, child) => Navigator( + key: navigatorKey, + onGenerateRoute: (settings) => AppPageRoute( + builder: (context) => const RecommendationPage()), + )), + )); } static final pageBuilders = [ diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart index 394419c..05ff0b0 100644 --- a/lib/pages/search_page.dart +++ b/lib/pages/search_page.dart @@ -10,9 +10,9 @@ import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/utils/translation.dart'; import '../components/animated_image.dart'; -import '../components/color_scheme.dart'; import '../components/grid.dart'; import '../components/illust_widget.dart'; +import '../components/md.dart'; import '../foundation/image_provider.dart'; class SearchPage extends StatefulWidget { diff --git a/lib/pages/user_info_page.dart b/lib/pages/user_info_page.dart index 9cbb3f1..aa943c3 100644 --- a/lib/pages/user_info_page.dart +++ b/lib/pages/user_info_page.dart @@ -1,5 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; -import 'package:pixes/components/color_scheme.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:pixes/components/loading.dart'; import 'package:pixes/components/md.dart'; import 'package:pixes/foundation/app.dart'; @@ -8,6 +8,8 @@ import 'package:pixes/network/network.dart'; import 'package:pixes/utils/translation.dart'; import 'package:url_launcher/url_launcher_string.dart'; +import '../components/illust_widget.dart'; + class UserInfoPage extends StatefulWidget { const UserInfoPage(this.id, {super.key}); @@ -20,81 +22,190 @@ class UserInfoPage extends StatefulWidget { class _UserInfoPageState extends LoadingState { @override Widget buildContent(BuildContext context, UserDetails data) { - return SingleChildScrollView( - child: Column( - children: [ - const SizedBox(height: 16), - Container( - width: 64, - height: 64, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(64), - border: Border.all(color: ColorScheme.of(context).outlineVariant, width: 0.6)), - child: ClipRRect( - borderRadius: BorderRadius.circular(64), - child: Image( - image: CachedImageProvider(data.avatar), - width: 64, - height: 64, - ), - ),), - const SizedBox(height: 8), - Text(data.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), - const SizedBox(height: 4), - Text.rich( - TextSpan( - children: [ - TextSpan(text: 'Follows: '.tl), - TextSpan(text: '${data.totalFollowUsers}', style: const TextStyle(fontWeight: FontWeight.w500)), - ], - ), - style: const TextStyle(fontSize: 14), - ), - const SizedBox(height: 8,), - buildHeader("Infomation".tl), - buildItem(icon: MdIcons.comment_outlined, title: "Comment".tl, content: data.comment), - buildItem(icon: MdIcons.cake_outlined, title: "Birthday".tl, content: data.birth), - buildItem(icon: MdIcons.location_city_outlined, title: "Region", content: data.region), - buildItem(icon: MdIcons.work_outline, title: "Job".tl, content: data.job), - buildItem(icon: MdIcons.person_2_outlined, title: "Gender".tl, content: data.gender), - const SizedBox(height: 8,), - buildHeader("Social Network".tl), - buildItem(title: "Webpage", content: data.webpage, onTap: () => launchUrlString(data.webpage!)), - buildItem(title: "Twitter", content: data.twitterUrl, onTap: () => launchUrlString(data.twitterUrl!)), - buildItem(title: "pawoo", content: data.pawooUrl, onTap: () => launchUrlString(data.pawooUrl!)) + return ScaffoldPage( + content: CustomScrollView( + slivers: [ + buildUser(), + buildInformation(), + SliverToBoxAdapter(child: buildHeader("Artworks"),), + _UserArtworks(data.id.toString(), key: ValueKey(data.id),), ], ), ); } - Widget buildItem({IconData? icon, required String title, required String? content, VoidCallback? onTap}) { - if(content == null || content.isEmpty) { - return const SizedBox.shrink(); - } - return Card( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), - padding: EdgeInsets.zero, - child: ListTile( - leading: icon == null ? null : Icon(icon, size: 20,), - title: Text(title), - subtitle: SelectableText(content), - onPressed: onTap, + Widget buildUser() { + return SliverToBoxAdapter( + child: Column( + children: [ + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(64), + border: Border.all(color: ColorScheme.of(context).outlineVariant, width: 0.6)), + child: ClipRRect( + borderRadius: BorderRadius.circular(64), + child: Image( + image: CachedImageProvider(data!.avatar), + width: 64, + height: 64, + ), + ),), + const SizedBox(height: 8), + Text(data!.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), + const SizedBox(height: 4), + Text.rich( + TextSpan( + children: [ + TextSpan(text: 'Follows: '.tl), + TextSpan(text: '${data!.totalFollowUsers}', style: const TextStyle(fontWeight: FontWeight.w500)), + ], + ), + style: const TextStyle(fontSize: 14), + ), + ], ), ); } Widget buildHeader(String title) { return SizedBox( - width: double.infinity, - child: Text( - title, - style: const TextStyle(fontWeight: FontWeight.w600), - ).toAlign(Alignment.centerLeft)).paddingLeft(16).paddingVertical(4); + width: double.infinity, + child: Text( + title, + style: const TextStyle(fontWeight: FontWeight.w600), + ).toAlign(Alignment.centerLeft)).paddingLeft(16).paddingVertical(4); + } + + Widget buildInformation() { + Widget buildItem({IconData? icon, required String title, required String? content, Widget? trailing}) { + if(content == null || content.isEmpty) { + return const SizedBox.shrink(); + } + return Card( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + padding: EdgeInsets.zero, + child: ListTile( + leading: icon == null ? null : Icon(icon, size: 20,), + title: Text(title), + subtitle: SelectableText(content), + trailing: trailing, + ), + ); + } + + return SliverToBoxAdapter( + child: Column( + children: [ + buildHeader("Infomation".tl), + buildItem(icon: MdIcons.comment_outlined, title: "Comment".tl, content: data!.comment), + buildItem(icon: MdIcons.cake_outlined, title: "Birthday".tl, content: data!.birth), + buildItem(icon: MdIcons.location_city_outlined, title: "Region", content: data!.region), + buildItem(icon: MdIcons.work_outline, title: "Job".tl, content: data!.job), + buildItem(icon: MdIcons.person_2_outlined, title: "Gender".tl, content: data!.gender), + const SizedBox(height: 8,), + buildHeader("Social Network".tl), + buildItem(title: "Webpage", + content: data!.webpage, + trailing: IconButton( + icon: const Icon(MdIcons.open_in_new, size: 18), + onPressed: () => launchUrlString(data!.twitterUrl!) + )), + buildItem(title: "Twitter", + content: data!.twitterUrl, + trailing: IconButton( + icon: const Icon(MdIcons.open_in_new, size: 18), + onPressed: () => launchUrlString(data!.twitterUrl!) + )), + buildItem(title: "pawoo", + content: data!.pawooUrl, + trailing: IconButton( + icon: const Icon(MdIcons.open_in_new, size: 18,), + onPressed: () => launchUrlString(data!.pawooUrl!) + )), + ], + ), + ); } @override Future> loadData() { return Network().getUserDetails(widget.id); } - } + +class _UserArtworks extends StatefulWidget { + const _UserArtworks(this.uid, {super.key}); + + final String uid; + + @override + State<_UserArtworks> createState() => _UserArtworksState(); +} + +class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> { + @override + Widget buildLoading(BuildContext context) { + return const SliverToBoxAdapter( + child: SizedBox( + child: Center( + child: ProgressRing(), + ), + ), + ); + } + + @override + Widget buildError(context, error) { + return SliverToBoxAdapter( + child: SizedBox( + child: Center( + child: Row( + children: [ + const Icon(FluentIcons.info), + const SizedBox(width: 4,), + Text(error) + ], + ), + ), + ), + ); + } + + @override + Widget buildContent(BuildContext context, final List data) { + return SliverMasonryGrid( + gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 240, + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + if(index == data.length - 1){ + nextPage(); + } + return IllustWidget(data[index]); + }, + childCount: data.length, + ), + ).sliverPaddingHorizontal(8); + } + + String? nextUrl; + + @override + Future>> loadData(page) async{ + if(nextUrl == "end") { + return Res.error("No more data"); + } + var res = nextUrl == null + ? await Network().getUserIllusts(widget.uid) + : await Network().getIllustsWithNextUrl(nextUrl!); + if(!res.error) { + nextUrl = res.subData; + nextUrl ??= "end"; + } + return res; + } +} +