diff --git a/lib/components/grid.dart b/lib/components/grid.dart index efb9016..aeb9d90 100644 --- a/lib/components/grid.dart +++ b/lib/components/grid.dart @@ -1,48 +1,44 @@ +import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:pixes/foundation/app.dart'; class SliverGridViewWithFixedItemHeight extends StatelessWidget { const SliverGridViewWithFixedItemHeight( {required this.delegate, - required this.maxCrossAxisExtent, - required this.itemHeight, - super.key}); + this.maxCrossAxisExtent = double.infinity, + this.minCrossAxisExtent = 0, + required this.itemHeight, + super.key}); final SliverChildDelegate delegate; final double maxCrossAxisExtent; + final double minCrossAxisExtent; + final double itemHeight; @override Widget build(BuildContext context) { return SliverLayoutBuilder( builder: ((context, constraints) => SliverGrid( - delegate: delegate, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: maxCrossAxisExtent, - childAspectRatio: - calcChildAspectRatio(constraints.crossAxisExtent)), - ).sliverPadding(EdgeInsets.only(bottom: context.padding.bottom)))); - } - - double calcChildAspectRatio(double width) { - var crossItems = width ~/ maxCrossAxisExtent; - if (width % maxCrossAxisExtent != 0) { - crossItems += 1; - } - final itemWidth = width / crossItems; - return itemWidth / itemHeight; + delegate: delegate, + gridDelegate: SliverGridDelegateWithFixedHeight( + itemHeight: itemHeight, + maxCrossAxisExtent: maxCrossAxisExtent, + minCrossAxisExtent: minCrossAxisExtent), + ).sliverPadding(EdgeInsets.only(bottom: context.padding.bottom)))); } } class GridViewWithFixedItemHeight extends StatelessWidget { const GridViewWithFixedItemHeight( - { required this.builder, - required this.itemCount, - required this.maxCrossAxisExtent, - required this.itemHeight, - super.key}); + {required this.builder, + required this.itemCount, + this.maxCrossAxisExtent = double.infinity, + this.minCrossAxisExtent = 0, + required this.itemHeight, + super.key}); final Widget Function(BuildContext, int) builder; @@ -50,28 +46,69 @@ class GridViewWithFixedItemHeight extends StatelessWidget { final double maxCrossAxisExtent; + final double minCrossAxisExtent; + final double itemHeight; @override Widget build(BuildContext context) { return LayoutBuilder( builder: ((context, constraints) => GridView.builder( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: maxCrossAxisExtent, - childAspectRatio: - calcChildAspectRatio(constraints.maxWidth)), - itemBuilder: builder, - itemCount: itemCount, - padding: EdgeInsets.only(bottom: context.padding.bottom), - ))); + gridDelegate: SliverGridDelegateWithFixedHeight( + itemHeight: itemHeight, + maxCrossAxisExtent: maxCrossAxisExtent, + minCrossAxisExtent: minCrossAxisExtent), + itemBuilder: builder, + itemCount: itemCount, + padding: EdgeInsets.only(bottom: context.padding.bottom), + ))); + } +} + +class SliverGridDelegateWithFixedHeight extends SliverGridDelegate { + const SliverGridDelegateWithFixedHeight({ + this.maxCrossAxisExtent = double.infinity, + this.minCrossAxisExtent = 0, + required this.itemHeight, + }); + + final double maxCrossAxisExtent; + + final double minCrossAxisExtent; + + final double itemHeight; + + @override + SliverGridLayout getLayout(SliverConstraints constraints) { + var crossItemsCount = calcCrossItemsCount(constraints.crossAxisExtent); + return SliverGridRegularTileLayout( + crossAxisCount: crossItemsCount, + mainAxisStride: itemHeight, + childMainAxisExtent: itemHeight, + crossAxisStride: constraints.crossAxisExtent / crossItemsCount, + childCrossAxisExtent: constraints.crossAxisExtent / crossItemsCount, + reverseCrossAxis: false); } - double calcChildAspectRatio(double width) { - var crossItems = width ~/ maxCrossAxisExtent; - if (width % maxCrossAxisExtent != 0) { - crossItems += 1; + int calcCrossItemsCount(double width) { + int count = 20; + var itemWidth = width / 20; + while ( + !(itemWidth > minCrossAxisExtent && itemWidth < maxCrossAxisExtent)) { + count--; + itemWidth = width / count; + if (count == 1) { + return 1; + } } - final itemWidth = width / crossItems; - return itemWidth / itemHeight; + return count; } -} \ No newline at end of file + + @override + bool shouldRelayout(covariant SliverGridDelegate oldDelegate) { + return oldDelegate is! SliverGridDelegateWithFixedHeight || + oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent || + oldDelegate.minCrossAxisExtent != minCrossAxisExtent || + oldDelegate.itemHeight != itemHeight; + } +} diff --git a/lib/components/user_preview.dart b/lib/components/user_preview.dart index bbc2303..baa9317 100644 --- a/lib/components/user_preview.dart +++ b/lib/components/user_preview.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:fluent_ui/fluent_ui.dart'; import 'package:pixes/components/animated_image.dart'; import 'package:pixes/foundation/app.dart'; @@ -20,15 +22,15 @@ class UserPreviewWidget extends StatefulWidget { class _UserPreviewWidgetState extends State { bool isFollowing = false; - void follow() async{ - if(isFollowing) return; + void follow() async { + if (isFollowing) return; setState(() { isFollowing = true; }); var method = widget.user.isFollowed ? "delete" : "add"; var res = await Network().follow(widget.user.id.toString(), method); - if(res.error) { - if(mounted) { + if (res.error) { + if (mounted) { context.showToast(message: "Network Error"); } } else { @@ -43,67 +45,120 @@ class _UserPreviewWidgetState extends State { Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), - child: Row( - children: [ - SizedBox( - width: 64, - height: 64, - child: ClipRRect( - borderRadius: BorderRadius.circular(64), - child: ColoredBox( - color: ColorScheme.of(context).secondaryContainer, - child: AnimatedImage( - image: CachedImageProvider(widget.user.avatar), - fit: BoxFit.cover, - filterQuality: FilterQuality.medium, + child: GestureDetector( + onTap: () => context.to(() => UserInfoPage(widget.user.id.toString())), + behavior: HitTestBehavior.translucent, + child: SizedBox.expand( + child: Row( + children: [ + SizedBox( + width: 64, + height: 64, + child: ClipRRect( + borderRadius: BorderRadius.circular(64), + child: ColoredBox( + color: ColorScheme.of(context).secondaryContainer, + child: AnimatedImage( + image: CachedImageProvider(widget.user.avatar), + fit: BoxFit.cover, + filterQuality: FilterQuality.medium, + ), + ), ), ), - ), - ), - const SizedBox(width: 12,), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Spacer(), - Text(widget.user.name, maxLines: 1, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), - const SizedBox(height: 12,), - Row( + const SizedBox( + width: 12, + ), + SizedBox( + width: 96, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Button( - onPressed: () => context.to(() => UserInfoPage(widget.user.id.toString(), followCallback: (v){ - setState(() { - widget.user.isFollowed = v; - }); - },)), - child: Text("View".tl,), + const Spacer(), + Text(widget.user.name, + maxLines: 1, + style: const TextStyle( + fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox( + height: 12, ), - const SizedBox(width: 8,), - if(isFollowing) - Button(onPressed: follow, child: const SizedBox( - width: 42, - height: 24, - child: Center( - child: SizedBox.square( - dimension: 18, - child: ProgressRing(strokeWidth: 2,), + Row( + children: [ + if (isFollowing) + Button( + onPressed: follow, + child: const SizedBox( + width: 42, + height: 24, + child: Center( + child: SizedBox.square( + dimension: 18, + child: ProgressRing( + strokeWidth: 2, + ), + ), + ), + )) + else if (!widget.user.isFollowed) + Button(onPressed: follow, child: Text("Follow".tl)) + else + Button( + onPressed: follow, + child: Text( + "Unfollow".tl, + style: TextStyle( + color: ColorScheme.of(context).error), + ), ), - ), - )) - else if (!widget.user.isFollowed) - Button(onPressed: follow, child: Text("Follow".tl)) - else - Button( - onPressed: follow, - child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),), - ), + ], + ), + const Spacer(), ], ), - const Spacer(), - ], + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + var count = constraints.maxWidth.toInt() ~/ 96; + var images = List.generate( + min(count, widget.user.artworks.length), + (index) => buildIllust(widget.user.artworks[index])); + return Row( + children: images, + ); + }, + ), + ), + const Icon( + FluentIcons.chevron_right, + size: 14, + ) + ], + ), + ), + ), + ); + } + + Widget buildIllust(Illust illust) { + return SizedBox( + width: 96, + height: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: ColoredBox( + color: ColorScheme.of(context).secondaryContainer, + child: AnimatedImage( + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + filterQuality: FilterQuality.medium, + image: CachedImageProvider(illust.images.first.medium), ), - ) - ], + ), + ), ), ); } diff --git a/lib/network/models.dart b/lib/network/models.dart index d0b784c..e5222ac 100644 --- a/lib/network/models.dart +++ b/lib/network/models.dart @@ -128,8 +128,7 @@ class IllustAuthor { final String avatar; bool isFollowed; - IllustAuthor( - this.id, this.name, this.account, this.avatar, this.isFollowed); + IllustAuthor(this.id, this.name, this.account, this.avatar, this.isFollowed); } class Tag { @@ -250,11 +249,11 @@ enum KeywordMatchType { @override toString() => text; - String toParam() => switch(this) { - KeywordMatchType.tagsPartialMatches => "partial_match_for_tags", - KeywordMatchType.tagsExactMatch => "exact_match_for_tags", - KeywordMatchType.titleOrDescriptionSearch => "title_and_caption" - }; + String toParam() => switch (this) { + KeywordMatchType.tagsPartialMatches => "partial_match_for_tags", + KeywordMatchType.tagsExactMatch => "exact_match_for_tags", + KeywordMatchType.titleOrDescriptionSearch => "title_and_caption" + }; } enum FavoriteNumber { @@ -273,9 +272,11 @@ enum FavoriteNumber { const FavoriteNumber(this.number); @override - toString() => this == FavoriteNumber.unlimited ? "Unlimited" : "$number Bookmarks"; + toString() => + this == FavoriteNumber.unlimited ? "Unlimited" : "$number Bookmarks"; - String toParam() => this == FavoriteNumber.unlimited ? "" : " ${number}users入り"; + String toParam() => + this == FavoriteNumber.unlimited ? "" : " ${number}users入り"; } enum SearchSort { @@ -288,37 +289,35 @@ enum SearchSort { bool get isPremium => appdata.account?.user.isPremium == true; static List get availableValues => [ - SearchSort.newToOld, - SearchSort.oldToNew, - SearchSort.popular, - if(appdata.account?.user.isPremium == true) - SearchSort.popularMale, - if(appdata.account?.user.isPremium == true) - SearchSort.popularFemale - ]; + SearchSort.newToOld, + SearchSort.oldToNew, + SearchSort.popular, + if (appdata.account?.user.isPremium == true) SearchSort.popularMale, + if (appdata.account?.user.isPremium == true) SearchSort.popularFemale + ]; @override toString() { - if(this == SearchSort.popular) { + if (this == SearchSort.popular) { return isPremium ? "Popular" : "Popular(limited)"; - } else if(this == SearchSort.newToOld) { + } else if (this == SearchSort.newToOld) { return "New to old"; - } else if(this == SearchSort.oldToNew){ + } else if (this == SearchSort.oldToNew) { return "Old to new"; - } else if(this == SearchSort.popularMale){ + } else if (this == SearchSort.popularMale) { return "Popular(Male)"; } else { return "Popular(Female)"; } } - String toParam() => switch(this) { - SearchSort.newToOld => "date_desc", - SearchSort.oldToNew => "date_asc", - SearchSort.popular => "popular_desc", - SearchSort.popularMale => "popular_male_desc", - SearchSort.popularFemale => "popular_female_desc", - }; + String toParam() => switch (this) { + SearchSort.newToOld => "date_desc", + SearchSort.oldToNew => "date_asc", + SearchSort.popular => "popular_desc", + SearchSort.popularMale => "popular_male_desc", + SearchSort.popularFemale => "popular_female_desc", + }; } enum AgeLimit { @@ -333,11 +332,11 @@ enum AgeLimit { @override toString() => text; - String toParam() => switch(this) { - AgeLimit.unlimited => "", - AgeLimit.allAges => " -R-18", - AgeLimit.r18 => "R-18", - }; + String toParam() => switch (this) { + AgeLimit.unlimited => "", + AgeLimit.allAges => " -R-18", + AgeLimit.r18 => "R-18", + }; } class SearchOptions { @@ -369,17 +368,19 @@ class UserPreview { final String avatar; bool isFollowed; final bool isBlocking; + final List artworks; UserPreview(this.id, this.name, this.account, this.avatar, this.isFollowed, - this.isBlocking); + this.isBlocking, this.artworks); UserPreview.fromJson(Map json) - : id = json['id'], - name = json['name'], - account = json['account'], - avatar = json['profile_image_urls']['medium'], - isFollowed = json['is_followed'], - isBlocking = json['is_access_blocking_user'] ?? false; + : id = json['user']['id'], + name = json['user']['name'], + account = json['user']['account'], + avatar = json['user']['profile_image_urls']['medium'], + isFollowed = json['user']['is_followed'], + isBlocking = json['user']['is_access_blocking_user'] ?? false, + artworks = (json['illusts'] as List).map((e) => Illust.fromJson(e)).toList(); } /* @@ -402,7 +403,7 @@ class UserPreview { } } */ -class Comment{ +class Comment { final String id; final String comment; final DateTime date; diff --git a/lib/network/network.dart b/lib/network/network.dart index 5544354..74ed64f 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -327,7 +327,7 @@ class Network { var res = await apiGet(path); if (res.success) { return Res( - (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e["user"])).toList(), + (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e)).toList(), subData: res.data["next_url"]); } else { return Res.error(res.errorMessage); @@ -350,7 +350,7 @@ class Network { var res = await apiGet(path); if (res.success) { return Res( - (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e["user"])).toList(), + (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e)).toList(), subData: res.data["next_url"]); } else { return Res.error(res.errorMessage); @@ -372,7 +372,7 @@ class Network { var res = await apiGet("/v1/user/recommended?filter=for_android"); if (res.success) { return Res( - (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e["user"])).toList(), + (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e)).toList(), subData: res.data["next_url"]); } else { return Res.error(res.errorMessage); @@ -473,7 +473,7 @@ class Network { var res = await apiGet("/v1/user/related?filter=for_android&seed_user_id=$id"); if (res.success) { return Res( - (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e["user"])).toList()); + (res.data["user_previews"] as List).map((e) => UserPreview.fromJson(e)).toList()); } else { return Res.error(res.errorMessage); } diff --git a/lib/pages/following_users_page.dart b/lib/pages/following_users_page.dart index e8d2546..0a3c994 100644 --- a/lib/pages/following_users_page.dart +++ b/lib/pages/following_users_page.dart @@ -18,7 +18,8 @@ class FollowingUsersPage extends StatefulWidget { State createState() => _FollowingUsersPageState(); } -class _FollowingUsersPageState extends MultiPageLoadingState { +class _FollowingUsersPageState + extends MultiPageLoadingState { String type = "public"; @override @@ -28,11 +29,13 @@ class _FollowingUsersPageState extends MultiPageLoadingState>> loadData(page) async{ - if(nextUrl == "end") { + Future>> loadData(page) async { + if (nextUrl == "end") { return Res.error("No more data"); } var res = await Network().getFollowing(widget.uid, type, nextUrl); - if(!res.error) { + if (!res.error) { nextUrl = res.subData; nextUrl ??= "end"; } diff --git a/lib/pages/recommendation_page.dart b/lib/pages/recommendation_page.dart index 5deeeb5..668a743 100644 --- a/lib/pages/recommendation_page.dart +++ b/lib/pages/recommendation_page.dart @@ -29,8 +29,11 @@ class _RecommendationPageState extends State { buildTab(), Expanded( child: type != 2 - ? _RecommendationArtworksPage(type, key: Key(type.toString()),) - : const _RecommendationUsersPage(), + ? _RecommendationArtworksPage( + type, + key: Key(type.toString()), + ) + : const _RecommendationUsersPage(), ) ], ); @@ -46,7 +49,7 @@ class _RecommendationPageState extends State { SegmentedButtonOption(2, "Users".tl), ], onPressed: (key) { - if(key != type) { + if (key != type) { setState(() { type = key; }); @@ -58,35 +61,42 @@ class _RecommendationPageState extends State { } } - class _RecommendationArtworksPage extends StatefulWidget { const _RecommendationArtworksPage(this.type, {super.key}); final int type; @override - State<_RecommendationArtworksPage> createState() => _RecommendationArtworksPageState(); + State<_RecommendationArtworksPage> createState() => + _RecommendationArtworksPageState(); } -class _RecommendationArtworksPageState extends MultiPageLoadingState<_RecommendationArtworksPage, Illust> { +class _RecommendationArtworksPageState + extends MultiPageLoadingState<_RecommendationArtworksPage, Illust> { @override Widget buildContent(BuildContext context, final List data) { - return LayoutBuilder(builder: (context, constrains){ + return LayoutBuilder(builder: (context, constrains) { return MasonryGridView.builder( - padding: const EdgeInsets.symmetric(horizontal: 8) - + EdgeInsets.only(bottom: context.padding.bottom), + padding: const EdgeInsets.symmetric(horizontal: 8) + + EdgeInsets.only(bottom: context.padding.bottom), gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 240, ), itemCount: data.length, itemBuilder: (context, index) { - if(index == data.length - 1){ + if (index == data.length - 1) { nextPage(); } - return IllustWidget(data[index], onTap: () { - context.to(() => IllustGalleryPage(illusts: data, - initialPage: index, nextUrl: Network.recommendationUrl,)); - },); + return IllustWidget( + data[index], + onTap: () { + context.to(() => IllustGalleryPage( + illusts: data, + initialPage: index, + nextUrl: Network.recommendationUrl, + )); + }, + ); }, ); }); @@ -104,33 +114,32 @@ class _RecommendationUsersPage extends StatefulWidget { const _RecommendationUsersPage(); @override - State<_RecommendationUsersPage> createState() => _RecommendationUsersPageState(); + State<_RecommendationUsersPage> createState() => + _RecommendationUsersPageState(); } -class _RecommendationUsersPageState extends MultiPageLoadingState<_RecommendationUsersPage, UserPreview> { +class _RecommendationUsersPageState + extends MultiPageLoadingState<_RecommendationUsersPage, UserPreview> { @override Widget buildContent(BuildContext context, List data) { return CustomScrollView( slivers: [ SliverGridViewWithFixedItemHeight( - delegate: SliverChildBuilderDelegate( - (context, index) { - if(index == data.length - 1){ - nextPage(); - } - return UserPreviewWidget(data[index]); - }, - childCount: data.length - ), - maxCrossAxisExtent: 520, - itemHeight: 114, + delegate: SliverChildBuilderDelegate((context, index) { + if (index == data.length - 1) { + nextPage(); + } + return UserPreviewWidget(data[index]); + }, childCount: data.length), + minCrossAxisExtent: 440, + itemHeight: 136, ).sliverPaddingHorizontal(8) ], ); } @override - Future>> loadData(page) async{ + Future>> loadData(page) async { var res = await Network().getRecommendationUsers(); return res; } diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart index e921d93..851a4b7 100644 --- a/lib/pages/search_page.dart +++ b/lib/pages/search_page.dart @@ -557,8 +557,8 @@ class _SearchUserResultPageState extends MultiPageLoadingState { _RelatedUsers(widget.id), buildInformation(), buildArtworkHeader(), - _UserArtworks(data.id.toString(), page, key: ValueKey(data.id + page),), - SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom)), + _UserArtworks( + data.id.toString(), + page, + key: ValueKey(data.id + page), + ), + SliverPadding( + padding: EdgeInsets.only(bottom: context.padding.bottom)), ], ), ); @@ -52,23 +57,24 @@ class _UserInfoPageState extends LoadingState { bool isFollowing = false; - void follow() async{ - if(isFollowing) return; + void follow() async { + if (isFollowing) return; String type = ""; - if(!data!.isFollowed) { + if (!data!.isFollowed) { await flyoutController.showFlyout( navigatorKey: App.rootNavigatorKey.currentState, - builder: (context) => - MenuFlyout( + builder: (context) => MenuFlyout( items: [ - MenuFlyoutItem(text: Text("Public".tl), + MenuFlyoutItem( + text: Text("Public".tl), onPressed: () => type = "public"), - MenuFlyoutItem(text: Text("Private".tl), + MenuFlyoutItem( + text: Text("Private".tl), onPressed: () => type = "private"), ], )); } - if(type.isEmpty && !data!.isFollowed) { + if (type.isEmpty && !data!.isFollowed) { return; } setState(() { @@ -76,8 +82,8 @@ class _UserInfoPageState extends LoadingState { }); var method = data!.isFollowed ? "delete" : "add"; var res = await Network().follow(data!.id.toString(), method, type); - if(res.error) { - if(mounted) { + if (res.error) { + if (mounted) { context.showToast(message: "Network Error"); } } else { @@ -100,7 +106,8 @@ class _UserInfoPageState extends LoadingState { height: 64, decoration: BoxDecoration( borderRadius: BorderRadius.circular(64), - border: Border.all(color: ColorScheme.of(context).outlineVariant, width: 0.6)), + border: Border.all( + color: ColorScheme.of(context).outlineVariant, width: 0.6)), child: ClipRRect( borderRadius: BorderRadius.circular(64), child: Image( @@ -109,47 +116,60 @@ class _UserInfoPageState extends LoadingState { height: 64, fit: BoxFit.cover, ), - ),), + ), + ), const SizedBox(height: 8), - Text(data!.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + Text(data!.name, + style: + const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text.rich( TextSpan( children: [ TextSpan(text: 'Follows: '.tl), TextSpan( - text: '${data!.totalFollowUsers}', - recognizer: TapGestureRecognizer() - ..onTap = (() => context.to(() => FollowingUsersPage(widget.id))), - style: TextStyle(fontWeight: FontWeight.bold, color: FluentTheme.of(context).accentColor) - ), + text: '${data!.totalFollowUsers}', + recognizer: TapGestureRecognizer() + ..onTap = (() => + context.to(() => FollowingUsersPage(widget.id))), + style: TextStyle( + fontWeight: FontWeight.bold, + color: FluentTheme.of(context).accentColor)), ], ), style: const TextStyle(fontSize: 14), ), - if(widget.id != appdata.account?.user.id) - const SizedBox(height: 8,), - if(widget.id != appdata.account?.user.id) - if(isFollowing) - Button(onPressed: follow, child: const SizedBox( - width: 42, - height: 24, - child: Center( - child: SizedBox.square( - dimension: 18, - child: ProgressRing(strokeWidth: 2,), - ), - ), - )) + if (widget.id != appdata.account?.user.id) + const SizedBox( + height: 8, + ), + if (widget.id != appdata.account?.user.id) + if (isFollowing) + Button( + onPressed: follow, + child: const SizedBox( + width: 42, + height: 24, + child: Center( + child: SizedBox.square( + dimension: 18, + child: ProgressRing( + strokeWidth: 2, + ), + ), + ), + )) else if (!data!.isFollowed) FlyoutTarget( - controller: flyoutController, - child: Button(onPressed: follow, child: Text("Follow".tl)) - ) + controller: flyoutController, + child: Button(onPressed: follow, child: Text("Follow".tl))) else Button( onPressed: follow, - child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),), + child: Text( + "Unfollow".tl, + style: TextStyle(color: ColorScheme.of(context).error), + ), ), ], ), @@ -158,63 +178,75 @@ class _UserInfoPageState extends LoadingState { Widget buildHeader(String title, {Widget? action}) { return SizedBox( - width: double.infinity, - height: 38, - child: Row( - children: [ - Text( - title, - style: const TextStyle(fontWeight: FontWeight.w600), - ).toAlign(Alignment.centerLeft), - const Spacer(), - if(action != null) - action.toAlign(Alignment.centerRight) - ], - ).paddingHorizontal(16)).paddingTop(8); + width: double.infinity, + height: 38, + child: Row( + children: [ + Text( + title, + style: const TextStyle(fontWeight: FontWeight.w600), + ).toAlign(Alignment.centerLeft), + const Spacer(), + if (action != null) action.toAlign(Alignment.centerRight) + ], + ).paddingHorizontal(16)) + .paddingTop(8); } Widget buildArtworkHeader() { return SliverToBoxAdapter( child: SizedBox( - width: double.infinity, - height: 38, - child: Row( - children: [ - SegmentedButton( - options: [ - SegmentedButtonOption(0, "Artworks".tl), - SegmentedButtonOption(1, "Bookmarks".tl), + width: double.infinity, + height: 38, + child: Row( + children: [ + SegmentedButton( + options: [ + SegmentedButtonOption(0, "Artworks".tl), + SegmentedButtonOption(1, "Bookmarks".tl), + ], + value: page, + onPressed: (value) { + setState(() { + page = value; + }); + }, + ), + const Spacer(), + BatchDownloadButton( + request: () { + if (page == 0) { + return Network().getUserIllusts(data!.id.toString()); + } else { + return Network().getUserBookmarks(data!.id.toString()); + } + }, + ), ], - value: page, - onPressed: (value) { - setState(() { - page = value; - }); - }, - ), - const Spacer(), - BatchDownloadButton(request: () { - if(page == 0) { - return Network().getUserIllusts(data!.id.toString()); - } else { - return Network().getUserBookmarks(data!.id.toString()); - } - },), - ], - ).paddingHorizontal(16)).paddingTop(12), + ).paddingHorizontal(16)) + .paddingTop(12), ); } Widget buildInformation() { - Widget buildItem({IconData? icon, required String title, required String? content, Widget? trailing}) { - if(content == null || content.isEmpty) { + 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,), + leading: icon == null + ? null + : Icon( + icon, + size: 20, + ), title: Text(title), subtitle: SelectableText(content), trailing: trailing, @@ -226,30 +258,46 @@ class _UserInfoPageState extends LoadingState { child: Column( children: [ buildHeader("Information".tl), - buildItem(icon: MdIcons.comment_outlined, title: "Introduction".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), + buildItem( + icon: MdIcons.comment_outlined, + title: "Introduction".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), buildHeader("Social Network".tl), - buildItem(title: "Webpage", + buildItem( + title: "Webpage", content: data!.webpage, trailing: IconButton( icon: const Icon(MdIcons.open_in_new, size: 18), - onPressed: () => launchUrlString(data!.twitterUrl!) - )), - buildItem(title: "Twitter", + 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", + 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!) - )), + icon: const Icon( + MdIcons.open_in_new, + size: 18, + ), + onPressed: () => launchUrlString(data!.pawooUrl!))), ], ), ); @@ -292,7 +340,9 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> { child: Row( children: [ const Icon(FluentIcons.info), - const SizedBox(width: 4,), + const SizedBox( + width: 4, + ), Text(error) ], ), @@ -308,16 +358,13 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> { maxCrossAxisExtent: 240, ), delegate: SliverChildBuilderDelegate( - (context, index) { - if(index == data.length - 1){ + (context, index) { + if (index == data.length - 1) { nextPage(); } return IllustWidget(data[index], onTap: () { context.to(() => IllustGalleryPage( - illusts: data, - initialPage: index, - nextUrl: nextUrl - )); + illusts: data, initialPage: index, nextUrl: nextUrl)); }); }, childCount: data.length, @@ -328,16 +375,16 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> { String? nextUrl; @override - Future>> loadData(page) async{ - if(nextUrl == "end") { + Future>> loadData(page) async { + if (nextUrl == "end") { return Res.error("No more data"); } var res = nextUrl == null ? (widget.type == 0 - ? await Network().getUserIllusts(widget.uid) - : await Network().getUserBookmarks(widget.uid)) + ? await Network().getUserIllusts(widget.uid) + : await Network().getUserBookmarks(widget.uid)) : await Network().getIllustsWithNextUrl(nextUrl!); - if(!res.error) { + if (!res.error) { nextUrl = res.subData; nextUrl ??= "end"; } @@ -354,12 +401,13 @@ class _RelatedUsers extends StatefulWidget { State<_RelatedUsers> createState() => _RelatedUsersState(); } -class _RelatedUsersState extends LoadingState<_RelatedUsers, List> { +class _RelatedUsersState + extends LoadingState<_RelatedUsers, List> { @override Widget buildFrame(BuildContext context, Widget child) { return SliverToBoxAdapter( child: SizedBox( - height: 108, + height: 146, width: double.infinity, child: child, ), @@ -370,18 +418,30 @@ class _RelatedUsersState extends LoadingState<_RelatedUsers, List> @override Widget buildContent(BuildContext context, List data) { - return Scrollbar( - controller: _controller, - child: ListView.builder( + Widget content = Scrollbar( controller: _controller, - padding: const EdgeInsets.only(bottom: 8, left: 8), - primary: false, - scrollDirection: Axis.horizontal, - itemCount: data.length, - itemBuilder: (context, index) { - return UserPreviewWidget(data[index]).fixWidth(264); - }, - )); + child: ListView.builder( + controller: _controller, + padding: const EdgeInsets.only(bottom: 8, left: 8), + primary: false, + scrollDirection: Axis.horizontal, + itemCount: data.length, + itemBuilder: (context, index) { + return UserPreviewWidget(data[index]).fixWidth(342); + }, + )); + if (MediaQuery.of(context).size.width > 500) { + content = ScrollbarTheme.merge( + data: const ScrollbarThemeData( + thickness: 6, + hoveringThickness: 6, + mainAxisMargin: 4, + hoveringPadding: EdgeInsets.zero, + padding: EdgeInsets.zero, + hoveringMainAxisMargin: 4), + child: content); + } + return content; } @override @@ -389,4 +449,3 @@ class _RelatedUsersState extends LoadingState<_RelatedUsers, List> return Network().relatedUsers(widget.uid); } } -