mirror of
https://github.com/wgh136/pixes.git
synced 2025-09-27 12:57:24 +00:00
improve user preview
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
|
|
||||||
class SliverGridViewWithFixedItemHeight extends StatelessWidget {
|
class SliverGridViewWithFixedItemHeight extends StatelessWidget {
|
||||||
const SliverGridViewWithFixedItemHeight(
|
const SliverGridViewWithFixedItemHeight(
|
||||||
{required this.delegate,
|
{required this.delegate,
|
||||||
required this.maxCrossAxisExtent,
|
this.maxCrossAxisExtent = double.infinity,
|
||||||
|
this.minCrossAxisExtent = 0,
|
||||||
required this.itemHeight,
|
required this.itemHeight,
|
||||||
super.key});
|
super.key});
|
||||||
|
|
||||||
@@ -12,6 +14,8 @@ class SliverGridViewWithFixedItemHeight extends StatelessWidget {
|
|||||||
|
|
||||||
final double maxCrossAxisExtent;
|
final double maxCrossAxisExtent;
|
||||||
|
|
||||||
|
final double minCrossAxisExtent;
|
||||||
|
|
||||||
final double itemHeight;
|
final double itemHeight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -19,28 +23,20 @@ class SliverGridViewWithFixedItemHeight extends StatelessWidget {
|
|||||||
return SliverLayoutBuilder(
|
return SliverLayoutBuilder(
|
||||||
builder: ((context, constraints) => SliverGrid(
|
builder: ((context, constraints) => SliverGrid(
|
||||||
delegate: delegate,
|
delegate: delegate,
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverGridDelegateWithFixedHeight(
|
||||||
|
itemHeight: itemHeight,
|
||||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||||
childAspectRatio:
|
minCrossAxisExtent: minCrossAxisExtent),
|
||||||
calcChildAspectRatio(constraints.crossAxisExtent)),
|
|
||||||
).sliverPadding(EdgeInsets.only(bottom: context.padding.bottom))));
|
).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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridViewWithFixedItemHeight extends StatelessWidget {
|
class GridViewWithFixedItemHeight extends StatelessWidget {
|
||||||
const GridViewWithFixedItemHeight(
|
const GridViewWithFixedItemHeight(
|
||||||
{required this.builder,
|
{required this.builder,
|
||||||
required this.itemCount,
|
required this.itemCount,
|
||||||
required this.maxCrossAxisExtent,
|
this.maxCrossAxisExtent = double.infinity,
|
||||||
|
this.minCrossAxisExtent = 0,
|
||||||
required this.itemHeight,
|
required this.itemHeight,
|
||||||
super.key});
|
super.key});
|
||||||
|
|
||||||
@@ -50,28 +46,69 @@ class GridViewWithFixedItemHeight extends StatelessWidget {
|
|||||||
|
|
||||||
final double maxCrossAxisExtent;
|
final double maxCrossAxisExtent;
|
||||||
|
|
||||||
|
final double minCrossAxisExtent;
|
||||||
|
|
||||||
final double itemHeight;
|
final double itemHeight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: ((context, constraints) => GridView.builder(
|
builder: ((context, constraints) => GridView.builder(
|
||||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: SliverGridDelegateWithFixedHeight(
|
||||||
|
itemHeight: itemHeight,
|
||||||
maxCrossAxisExtent: maxCrossAxisExtent,
|
maxCrossAxisExtent: maxCrossAxisExtent,
|
||||||
childAspectRatio:
|
minCrossAxisExtent: minCrossAxisExtent),
|
||||||
calcChildAspectRatio(constraints.maxWidth)),
|
|
||||||
itemBuilder: builder,
|
itemBuilder: builder,
|
||||||
itemCount: itemCount,
|
itemCount: itemCount,
|
||||||
padding: EdgeInsets.only(bottom: context.padding.bottom),
|
padding: EdgeInsets.only(bottom: context.padding.bottom),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double calcChildAspectRatio(double width) {
|
class SliverGridDelegateWithFixedHeight extends SliverGridDelegate {
|
||||||
var crossItems = width ~/ maxCrossAxisExtent;
|
const SliverGridDelegateWithFixedHeight({
|
||||||
if (width % maxCrossAxisExtent != 0) {
|
this.maxCrossAxisExtent = double.infinity,
|
||||||
crossItems += 1;
|
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);
|
||||||
}
|
}
|
||||||
final itemWidth = width / crossItems;
|
|
||||||
return itemWidth / itemHeight;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRelayout(covariant SliverGridDelegate oldDelegate) {
|
||||||
|
return oldDelegate is! SliverGridDelegateWithFixedHeight ||
|
||||||
|
oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent ||
|
||||||
|
oldDelegate.minCrossAxisExtent != minCrossAxisExtent ||
|
||||||
|
oldDelegate.itemHeight != itemHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:pixes/components/animated_image.dart';
|
import 'package:pixes/components/animated_image.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
@@ -43,6 +45,10 @@ class _UserPreviewWidgetState extends State<UserPreviewWidget> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => context.to(() => UserInfoPage(widget.user.id.toString())),
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: SizedBox.expand(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -60,33 +66,36 @@ class _UserPreviewWidgetState extends State<UserPreviewWidget> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12,),
|
const SizedBox(
|
||||||
Expanded(
|
width: 12,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 96,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(widget.user.name, maxLines: 1, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
Text(widget.user.name,
|
||||||
const SizedBox(height: 12,),
|
maxLines: 1,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Button(
|
|
||||||
onPressed: () => context.to(() => UserInfoPage(widget.user.id.toString(), followCallback: (v){
|
|
||||||
setState(() {
|
|
||||||
widget.user.isFollowed = v;
|
|
||||||
});
|
|
||||||
},)),
|
|
||||||
child: Text("View".tl,),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8,),
|
|
||||||
if (isFollowing)
|
if (isFollowing)
|
||||||
Button(onPressed: follow, child: const SizedBox(
|
Button(
|
||||||
|
onPressed: follow,
|
||||||
|
child: const SizedBox(
|
||||||
width: 42,
|
width: 42,
|
||||||
height: 24,
|
height: 24,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox.square(
|
child: SizedBox.square(
|
||||||
dimension: 18,
|
dimension: 18,
|
||||||
child: ProgressRing(strokeWidth: 2,),
|
child: ProgressRing(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
@@ -95,16 +104,62 @@ class _UserPreviewWidgetState extends State<UserPreviewWidget> {
|
|||||||
else
|
else
|
||||||
Button(
|
Button(
|
||||||
onPressed: follow,
|
onPressed: follow,
|
||||||
child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),),
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -128,8 +128,7 @@ class IllustAuthor {
|
|||||||
final String avatar;
|
final String avatar;
|
||||||
bool isFollowed;
|
bool isFollowed;
|
||||||
|
|
||||||
IllustAuthor(
|
IllustAuthor(this.id, this.name, this.account, this.avatar, this.isFollowed);
|
||||||
this.id, this.name, this.account, this.avatar, this.isFollowed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tag {
|
class Tag {
|
||||||
@@ -273,9 +272,11 @@ enum FavoriteNumber {
|
|||||||
const FavoriteNumber(this.number);
|
const FavoriteNumber(this.number);
|
||||||
|
|
||||||
@override
|
@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 {
|
enum SearchSort {
|
||||||
@@ -291,10 +292,8 @@ enum SearchSort {
|
|||||||
SearchSort.newToOld,
|
SearchSort.newToOld,
|
||||||
SearchSort.oldToNew,
|
SearchSort.oldToNew,
|
||||||
SearchSort.popular,
|
SearchSort.popular,
|
||||||
if(appdata.account?.user.isPremium == true)
|
if (appdata.account?.user.isPremium == true) SearchSort.popularMale,
|
||||||
SearchSort.popularMale,
|
if (appdata.account?.user.isPremium == true) SearchSort.popularFemale
|
||||||
if(appdata.account?.user.isPremium == true)
|
|
||||||
SearchSort.popularFemale
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -369,17 +368,19 @@ class UserPreview {
|
|||||||
final String avatar;
|
final String avatar;
|
||||||
bool isFollowed;
|
bool isFollowed;
|
||||||
final bool isBlocking;
|
final bool isBlocking;
|
||||||
|
final List<Illust> artworks;
|
||||||
|
|
||||||
UserPreview(this.id, this.name, this.account, this.avatar, this.isFollowed,
|
UserPreview(this.id, this.name, this.account, this.avatar, this.isFollowed,
|
||||||
this.isBlocking);
|
this.isBlocking, this.artworks);
|
||||||
|
|
||||||
UserPreview.fromJson(Map<String, dynamic> json)
|
UserPreview.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['user']['id'],
|
||||||
name = json['name'],
|
name = json['user']['name'],
|
||||||
account = json['account'],
|
account = json['user']['account'],
|
||||||
avatar = json['profile_image_urls']['medium'],
|
avatar = json['user']['profile_image_urls']['medium'],
|
||||||
isFollowed = json['is_followed'],
|
isFollowed = json['user']['is_followed'],
|
||||||
isBlocking = json['is_access_blocking_user'] ?? false;
|
isBlocking = json['user']['is_access_blocking_user'] ?? false,
|
||||||
|
artworks = (json['illusts'] as List).map((e) => Illust.fromJson(e)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -327,7 +327,7 @@ class Network {
|
|||||||
var res = await apiGet(path);
|
var res = await apiGet(path);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
return Res(
|
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"]);
|
subData: res.data["next_url"]);
|
||||||
} else {
|
} else {
|
||||||
return Res.error(res.errorMessage);
|
return Res.error(res.errorMessage);
|
||||||
@@ -350,7 +350,7 @@ class Network {
|
|||||||
var res = await apiGet(path);
|
var res = await apiGet(path);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
return Res(
|
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"]);
|
subData: res.data["next_url"]);
|
||||||
} else {
|
} else {
|
||||||
return Res.error(res.errorMessage);
|
return Res.error(res.errorMessage);
|
||||||
@@ -372,7 +372,7 @@ class Network {
|
|||||||
var res = await apiGet("/v1/user/recommended?filter=for_android");
|
var res = await apiGet("/v1/user/recommended?filter=for_android");
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
return Res(
|
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"]);
|
subData: res.data["next_url"]);
|
||||||
} else {
|
} else {
|
||||||
return Res.error(res.errorMessage);
|
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");
|
var res = await apiGet("/v1/user/related?filter=for_android&seed_user_id=$id");
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
return Res(
|
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 {
|
} else {
|
||||||
return Res.error(res.errorMessage);
|
return Res.error(res.errorMessage);
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,8 @@ class FollowingUsersPage extends StatefulWidget {
|
|||||||
State<FollowingUsersPage> createState() => _FollowingUsersPageState();
|
State<FollowingUsersPage> createState() => _FollowingUsersPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FollowingUsersPageState extends MultiPageLoadingState<FollowingUsersPage, UserPreview> {
|
class _FollowingUsersPageState
|
||||||
|
extends MultiPageLoadingState<FollowingUsersPage, UserPreview> {
|
||||||
String type = "public";
|
String type = "public";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -28,9 +29,11 @@ class _FollowingUsersPageState extends MultiPageLoadingState<FollowingUsersPage,
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text("Following".tl,
|
Text(
|
||||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),)
|
"Following".tl,
|
||||||
.paddingVertical(12).paddingLeft(16),
|
style:
|
||||||
|
const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
||||||
|
).paddingVertical(12).paddingLeft(16),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (widget.uid == appdata.account?.user.id)
|
if (widget.uid == appdata.account?.user.id)
|
||||||
SegmentedButton(
|
SegmentedButton(
|
||||||
@@ -44,22 +47,21 @@ class _FollowingUsersPageState extends MultiPageLoadingState<FollowingUsersPage,
|
|||||||
reset();
|
reset();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16,)
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverGridViewWithFixedItemHeight(
|
SliverGridViewWithFixedItemHeight(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
(context, index) {
|
|
||||||
if (index == data.length - 1) {
|
if (index == data.length - 1) {
|
||||||
nextPage();
|
nextPage();
|
||||||
}
|
}
|
||||||
return UserPreviewWidget(data[index]);
|
return UserPreviewWidget(data[index]);
|
||||||
},
|
}, childCount: data.length),
|
||||||
childCount: data.length
|
minCrossAxisExtent: 440,
|
||||||
),
|
itemHeight: 136,
|
||||||
maxCrossAxisExtent: 520,
|
|
||||||
itemHeight: 114,
|
|
||||||
).sliverPaddingHorizontal(8)
|
).sliverPaddingHorizontal(8)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@@ -29,7 +29,10 @@ class _RecommendationPageState extends State<RecommendationPage> {
|
|||||||
buildTab(),
|
buildTab(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: type != 2
|
child: type != 2
|
||||||
? _RecommendationArtworksPage(type, key: Key(type.toString()),)
|
? _RecommendationArtworksPage(
|
||||||
|
type,
|
||||||
|
key: Key(type.toString()),
|
||||||
|
)
|
||||||
: const _RecommendationUsersPage(),
|
: const _RecommendationUsersPage(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -58,23 +61,24 @@ class _RecommendationPageState extends State<RecommendationPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _RecommendationArtworksPage extends StatefulWidget {
|
class _RecommendationArtworksPage extends StatefulWidget {
|
||||||
const _RecommendationArtworksPage(this.type, {super.key});
|
const _RecommendationArtworksPage(this.type, {super.key});
|
||||||
|
|
||||||
final int type;
|
final int type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_RecommendationArtworksPage> createState() => _RecommendationArtworksPageState();
|
State<_RecommendationArtworksPage> createState() =>
|
||||||
|
_RecommendationArtworksPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecommendationArtworksPageState extends MultiPageLoadingState<_RecommendationArtworksPage, Illust> {
|
class _RecommendationArtworksPageState
|
||||||
|
extends MultiPageLoadingState<_RecommendationArtworksPage, Illust> {
|
||||||
@override
|
@override
|
||||||
Widget buildContent(BuildContext context, final List<Illust> data) {
|
Widget buildContent(BuildContext context, final List<Illust> data) {
|
||||||
return LayoutBuilder(builder: (context, constrains) {
|
return LayoutBuilder(builder: (context, constrains) {
|
||||||
return MasonryGridView.builder(
|
return MasonryGridView.builder(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8)
|
padding: const EdgeInsets.symmetric(horizontal: 8) +
|
||||||
+ EdgeInsets.only(bottom: context.padding.bottom),
|
EdgeInsets.only(bottom: context.padding.bottom),
|
||||||
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 240,
|
maxCrossAxisExtent: 240,
|
||||||
),
|
),
|
||||||
@@ -83,10 +87,16 @@ class _RecommendationArtworksPageState extends MultiPageLoadingState<_Recommenda
|
|||||||
if (index == data.length - 1) {
|
if (index == data.length - 1) {
|
||||||
nextPage();
|
nextPage();
|
||||||
}
|
}
|
||||||
return IllustWidget(data[index], onTap: () {
|
return IllustWidget(
|
||||||
context.to(() => IllustGalleryPage(illusts: data,
|
data[index],
|
||||||
initialPage: index, nextUrl: Network.recommendationUrl,));
|
onTap: () {
|
||||||
},);
|
context.to(() => IllustGalleryPage(
|
||||||
|
illusts: data,
|
||||||
|
initialPage: index,
|
||||||
|
nextUrl: Network.recommendationUrl,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -104,26 +114,25 @@ class _RecommendationUsersPage extends StatefulWidget {
|
|||||||
const _RecommendationUsersPage();
|
const _RecommendationUsersPage();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_RecommendationUsersPage> createState() => _RecommendationUsersPageState();
|
State<_RecommendationUsersPage> createState() =>
|
||||||
|
_RecommendationUsersPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecommendationUsersPageState extends MultiPageLoadingState<_RecommendationUsersPage, UserPreview> {
|
class _RecommendationUsersPageState
|
||||||
|
extends MultiPageLoadingState<_RecommendationUsersPage, UserPreview> {
|
||||||
@override
|
@override
|
||||||
Widget buildContent(BuildContext context, List<UserPreview> data) {
|
Widget buildContent(BuildContext context, List<UserPreview> data) {
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverGridViewWithFixedItemHeight(
|
SliverGridViewWithFixedItemHeight(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
(context, index) {
|
|
||||||
if (index == data.length - 1) {
|
if (index == data.length - 1) {
|
||||||
nextPage();
|
nextPage();
|
||||||
}
|
}
|
||||||
return UserPreviewWidget(data[index]);
|
return UserPreviewWidget(data[index]);
|
||||||
},
|
}, childCount: data.length),
|
||||||
childCount: data.length
|
minCrossAxisExtent: 440,
|
||||||
),
|
itemHeight: 136,
|
||||||
maxCrossAxisExtent: 520,
|
|
||||||
itemHeight: 114,
|
|
||||||
).sliverPaddingHorizontal(8)
|
).sliverPaddingHorizontal(8)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@@ -557,8 +557,8 @@ class _SearchUserResultPageState extends MultiPageLoadingState<SearchUserResultP
|
|||||||
},
|
},
|
||||||
childCount: data.length
|
childCount: data.length
|
||||||
),
|
),
|
||||||
maxCrossAxisExtent: 520,
|
minCrossAxisExtent: 440,
|
||||||
itemHeight: 114,
|
itemHeight: 136,
|
||||||
).sliverPaddingHorizontal(8),
|
).sliverPaddingHorizontal(8),
|
||||||
SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom),)
|
SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom),)
|
||||||
],
|
],
|
||||||
|
@@ -43,8 +43,13 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
_RelatedUsers(widget.id),
|
_RelatedUsers(widget.id),
|
||||||
buildInformation(),
|
buildInformation(),
|
||||||
buildArtworkHeader(),
|
buildArtworkHeader(),
|
||||||
_UserArtworks(data.id.toString(), page, key: ValueKey(data.id + page),),
|
_UserArtworks(
|
||||||
SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom)),
|
data.id.toString(),
|
||||||
|
page,
|
||||||
|
key: ValueKey(data.id + page),
|
||||||
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: EdgeInsets.only(bottom: context.padding.bottom)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -58,12 +63,13 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
if (!data!.isFollowed) {
|
if (!data!.isFollowed) {
|
||||||
await flyoutController.showFlyout(
|
await flyoutController.showFlyout(
|
||||||
navigatorKey: App.rootNavigatorKey.currentState,
|
navigatorKey: App.rootNavigatorKey.currentState,
|
||||||
builder: (context) =>
|
builder: (context) => MenuFlyout(
|
||||||
MenuFlyout(
|
|
||||||
items: [
|
items: [
|
||||||
MenuFlyoutItem(text: Text("Public".tl),
|
MenuFlyoutItem(
|
||||||
|
text: Text("Public".tl),
|
||||||
onPressed: () => type = "public"),
|
onPressed: () => type = "public"),
|
||||||
MenuFlyoutItem(text: Text("Private".tl),
|
MenuFlyoutItem(
|
||||||
|
text: Text("Private".tl),
|
||||||
onPressed: () => type = "private"),
|
onPressed: () => type = "private"),
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
@@ -100,7 +106,8 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
height: 64,
|
height: 64,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(64),
|
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(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(64),
|
borderRadius: BorderRadius.circular(64),
|
||||||
child: Image(
|
child: Image(
|
||||||
@@ -109,9 +116,12 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
height: 64,
|
height: 64,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
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),
|
const SizedBox(height: 4),
|
||||||
Text.rich(
|
Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
@@ -120,36 +130,46 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: '${data!.totalFollowUsers}',
|
text: '${data!.totalFollowUsers}',
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = (() => context.to(() => FollowingUsersPage(widget.id))),
|
..onTap = (() =>
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, color: FluentTheme.of(context).accentColor)
|
context.to(() => FollowingUsersPage(widget.id))),
|
||||||
),
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: FluentTheme.of(context).accentColor)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
if (widget.id != appdata.account?.user.id)
|
if (widget.id != appdata.account?.user.id)
|
||||||
const SizedBox(height: 8,),
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
if (widget.id != appdata.account?.user.id)
|
if (widget.id != appdata.account?.user.id)
|
||||||
if (isFollowing)
|
if (isFollowing)
|
||||||
Button(onPressed: follow, child: const SizedBox(
|
Button(
|
||||||
|
onPressed: follow,
|
||||||
|
child: const SizedBox(
|
||||||
width: 42,
|
width: 42,
|
||||||
height: 24,
|
height: 24,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox.square(
|
child: SizedBox.square(
|
||||||
dimension: 18,
|
dimension: 18,
|
||||||
child: ProgressRing(strokeWidth: 2,),
|
child: ProgressRing(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
else if (!data!.isFollowed)
|
else if (!data!.isFollowed)
|
||||||
FlyoutTarget(
|
FlyoutTarget(
|
||||||
controller: flyoutController,
|
controller: flyoutController,
|
||||||
child: Button(onPressed: follow, child: Text("Follow".tl))
|
child: Button(onPressed: follow, child: Text("Follow".tl)))
|
||||||
)
|
|
||||||
else
|
else
|
||||||
Button(
|
Button(
|
||||||
onPressed: follow,
|
onPressed: follow,
|
||||||
child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),),
|
child: Text(
|
||||||
|
"Unfollow".tl,
|
||||||
|
style: TextStyle(color: ColorScheme.of(context).error),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -167,10 +187,10 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
).toAlign(Alignment.centerLeft),
|
).toAlign(Alignment.centerLeft),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if(action != null)
|
if (action != null) action.toAlign(Alignment.centerRight)
|
||||||
action.toAlign(Alignment.centerRight)
|
|
||||||
],
|
],
|
||||||
).paddingHorizontal(16)).paddingTop(8);
|
).paddingHorizontal(16))
|
||||||
|
.paddingTop(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildArtworkHeader() {
|
Widget buildArtworkHeader() {
|
||||||
@@ -193,20 +213,27 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
BatchDownloadButton(request: () {
|
BatchDownloadButton(
|
||||||
|
request: () {
|
||||||
if (page == 0) {
|
if (page == 0) {
|
||||||
return Network().getUserIllusts(data!.id.toString());
|
return Network().getUserIllusts(data!.id.toString());
|
||||||
} else {
|
} else {
|
||||||
return Network().getUserBookmarks(data!.id.toString());
|
return Network().getUserBookmarks(data!.id.toString());
|
||||||
}
|
}
|
||||||
},),
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).paddingHorizontal(16)).paddingTop(12),
|
).paddingHorizontal(16))
|
||||||
|
.paddingTop(12),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildInformation() {
|
Widget buildInformation() {
|
||||||
Widget buildItem({IconData? icon, required String title, required String? content, Widget? trailing}) {
|
Widget buildItem(
|
||||||
|
{IconData? icon,
|
||||||
|
required String title,
|
||||||
|
required String? content,
|
||||||
|
Widget? trailing}) {
|
||||||
if (content == null || content.isEmpty) {
|
if (content == null || content.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@@ -214,7 +241,12 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: icon == null ? null : Icon(icon, size: 20,),
|
leading: icon == null
|
||||||
|
? null
|
||||||
|
: Icon(
|
||||||
|
icon,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
subtitle: SelectableText(content),
|
subtitle: SelectableText(content),
|
||||||
trailing: trailing,
|
trailing: trailing,
|
||||||
@@ -226,30 +258,46 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
buildHeader("Information".tl),
|
buildHeader("Information".tl),
|
||||||
buildItem(icon: MdIcons.comment_outlined, title: "Introduction".tl, content: data!.comment),
|
buildItem(
|
||||||
buildItem(icon: MdIcons.cake_outlined, title: "Birthday".tl, content: data!.birth),
|
icon: MdIcons.comment_outlined,
|
||||||
buildItem(icon: MdIcons.location_city_outlined, title: "Region", content: data!.region),
|
title: "Introduction".tl,
|
||||||
buildItem(icon: MdIcons.work_outline, title: "Job".tl, content: data!.job),
|
content: data!.comment),
|
||||||
buildItem(icon: MdIcons.person_2_outlined, title: "Gender".tl, content: data!.gender),
|
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),
|
buildHeader("Social Network".tl),
|
||||||
buildItem(title: "Webpage",
|
buildItem(
|
||||||
|
title: "Webpage",
|
||||||
content: data!.webpage,
|
content: data!.webpage,
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(MdIcons.open_in_new, size: 18),
|
icon: const Icon(MdIcons.open_in_new, size: 18),
|
||||||
onPressed: () => launchUrlString(data!.twitterUrl!)
|
onPressed: () => launchUrlString(data!.twitterUrl!))),
|
||||||
)),
|
buildItem(
|
||||||
buildItem(title: "Twitter",
|
title: "Twitter",
|
||||||
content: data!.twitterUrl,
|
content: data!.twitterUrl,
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(MdIcons.open_in_new, size: 18),
|
icon: const Icon(MdIcons.open_in_new, size: 18),
|
||||||
onPressed: () => launchUrlString(data!.twitterUrl!)
|
onPressed: () => launchUrlString(data!.twitterUrl!))),
|
||||||
)),
|
buildItem(
|
||||||
buildItem(title: "pawoo",
|
title: "pawoo",
|
||||||
content: data!.pawooUrl,
|
content: data!.pawooUrl,
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(MdIcons.open_in_new, size: 18,),
|
icon: const Icon(
|
||||||
onPressed: () => launchUrlString(data!.pawooUrl!)
|
MdIcons.open_in_new,
|
||||||
)),
|
size: 18,
|
||||||
|
),
|
||||||
|
onPressed: () => launchUrlString(data!.pawooUrl!))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -292,7 +340,9 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(FluentIcons.info),
|
const Icon(FluentIcons.info),
|
||||||
const SizedBox(width: 4,),
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
Text(error)
|
Text(error)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -314,10 +364,7 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> {
|
|||||||
}
|
}
|
||||||
return IllustWidget(data[index], onTap: () {
|
return IllustWidget(data[index], onTap: () {
|
||||||
context.to(() => IllustGalleryPage(
|
context.to(() => IllustGalleryPage(
|
||||||
illusts: data,
|
illusts: data, initialPage: index, nextUrl: nextUrl));
|
||||||
initialPage: index,
|
|
||||||
nextUrl: nextUrl
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
childCount: data.length,
|
childCount: data.length,
|
||||||
@@ -354,12 +401,13 @@ class _RelatedUsers extends StatefulWidget {
|
|||||||
State<_RelatedUsers> createState() => _RelatedUsersState();
|
State<_RelatedUsers> createState() => _RelatedUsersState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RelatedUsersState extends LoadingState<_RelatedUsers, List<UserPreview>> {
|
class _RelatedUsersState
|
||||||
|
extends LoadingState<_RelatedUsers, List<UserPreview>> {
|
||||||
@override
|
@override
|
||||||
Widget buildFrame(BuildContext context, Widget child) {
|
Widget buildFrame(BuildContext context, Widget child) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 108,
|
height: 146,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
@@ -370,7 +418,7 @@ class _RelatedUsersState extends LoadingState<_RelatedUsers, List<UserPreview>>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildContent(BuildContext context, List<UserPreview> data) {
|
Widget buildContent(BuildContext context, List<UserPreview> data) {
|
||||||
return Scrollbar(
|
Widget content = Scrollbar(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
@@ -379,9 +427,21 @@ class _RelatedUsersState extends LoadingState<_RelatedUsers, List<UserPreview>>
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: data.length,
|
itemCount: data.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return UserPreviewWidget(data[index]).fixWidth(264);
|
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
|
@override
|
||||||
@@ -389,4 +449,3 @@ class _RelatedUsersState extends LoadingState<_RelatedUsers, List<UserPreview>>
|
|||||||
return Network().relatedUsers(widget.uid);
|
return Network().relatedUsers(widget.uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user