mirror of
https://github.com/wgh136/pixes.git
synced 2025-09-27 04:57:23 +00:00
related users and related artworks
This commit is contained in:
@@ -125,7 +125,10 @@
|
|||||||
"Pause": "暂停",
|
"Pause": "暂停",
|
||||||
"Resume": "继续",
|
"Resume": "继续",
|
||||||
"Paused": "已暂停",
|
"Paused": "已暂停",
|
||||||
"Delete all": "删除全部"
|
"Delete all": "删除全部",
|
||||||
|
"Related": "相关",
|
||||||
|
"Related artworks": "相关作品",
|
||||||
|
"Related users": "相关用户"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Search": "搜索",
|
"Search": "搜索",
|
||||||
@@ -253,6 +256,9 @@
|
|||||||
"Pause": "暫停",
|
"Pause": "暫停",
|
||||||
"Resume": "繼續",
|
"Resume": "繼續",
|
||||||
"Paused": "已暫停",
|
"Paused": "已暫停",
|
||||||
"Delete all": "刪除全部"
|
"Delete all": "刪除全部",
|
||||||
|
"Related": "相關",
|
||||||
|
"Related artworks": "相關作品",
|
||||||
|
"Related users": "相關用戶"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -13,6 +13,34 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object> extends
|
|||||||
|
|
||||||
Widget buildContent(BuildContext context, S data);
|
Widget buildContent(BuildContext context, S data);
|
||||||
|
|
||||||
|
Widget? buildFrame(BuildContext context, Widget child) => null;
|
||||||
|
|
||||||
|
Widget buildLoading() {
|
||||||
|
return const Center(
|
||||||
|
child: ProgressRing(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void retry() {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
error = null;
|
||||||
|
});
|
||||||
|
loadData().then((value) {
|
||||||
|
if(value.success) {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
data = value.data;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
error = value.errorMessage!;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildError() {
|
Widget buildError() {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -21,25 +49,7 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object> extends
|
|||||||
Text(error!),
|
Text(error!),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Button(
|
Button(
|
||||||
onPressed: () {
|
onPressed: retry,
|
||||||
setState(() {
|
|
||||||
isLoading = true;
|
|
||||||
error = null;
|
|
||||||
});
|
|
||||||
loadData().then((value) {
|
|
||||||
if(value.success) {
|
|
||||||
setState(() {
|
|
||||||
isLoading = false;
|
|
||||||
data = value.data;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
isLoading = false;
|
|
||||||
error = value.errorMessage!;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const Text("Retry"),
|
child: const Text("Retry"),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -69,15 +79,17 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object> extends
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Widget child;
|
||||||
|
|
||||||
if(isLoading){
|
if(isLoading){
|
||||||
return const Center(
|
child = buildLoading();
|
||||||
child: ProgressRing(),
|
|
||||||
);
|
|
||||||
} else if (error != null){
|
} else if (error != null){
|
||||||
return buildError();
|
child = buildError();
|
||||||
} else {
|
} else {
|
||||||
return buildContent(context, data!);
|
child = buildContent(context, data!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buildFrame(context, child) ?? child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +106,8 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
|
|||||||
|
|
||||||
Future<Res<List<S>>> loadData(int page);
|
Future<Res<List<S>>> loadData(int page);
|
||||||
|
|
||||||
|
Widget? buildFrame(BuildContext context, Widget child) => null;
|
||||||
|
|
||||||
Widget buildContent(BuildContext context, final List<S> data);
|
Widget buildContent(BuildContext context, final List<S> data);
|
||||||
|
|
||||||
bool get isLoading => _isLoading || _isFirstLoading;
|
bool get isLoading => _isLoading || _isFirstLoading;
|
||||||
@@ -181,12 +195,16 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Widget child;
|
||||||
|
|
||||||
if(_isFirstLoading){
|
if(_isFirstLoading){
|
||||||
return buildLoading(context);
|
child = buildLoading(context);
|
||||||
} else if (_error != null){
|
} else if (_error != null){
|
||||||
return buildError(context, _error!);
|
child = buildError(context, _error!);
|
||||||
} else {
|
} else {
|
||||||
return buildContent(context, _data!);
|
child = buildContent(context, _data!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buildFrame(context, child) ?? child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -65,8 +65,9 @@ class _UserPreviewWidgetState extends State<UserPreviewWidget> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(widget.user.name, maxLines: 1, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
Text(widget.user.name, maxLines: 1, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
const SizedBox(height: 12,),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Button(
|
Button(
|
||||||
@@ -97,9 +98,10 @@ class _UserPreviewWidgetState extends State<UserPreviewWidget> {
|
|||||||
child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),),
|
child: Text("Unfollow".tl, style: TextStyle(color: ColorScheme.of(context).error),),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
|
const Spacer(),
|
||||||
],
|
],
|
||||||
).paddingVertical(8),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -467,4 +467,24 @@ class Network {
|
|||||||
return Res.fromErrorRes(res);
|
return Res.fromErrorRes(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Res<List<UserPreview>>> relatedUsers(String id) async {
|
||||||
|
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());
|
||||||
|
} else {
|
||||||
|
return Res.error(res.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Res<List<Illust>>> relatedIllusts(String id) async {
|
||||||
|
var res = await apiGet("/v2/illust/related?filter=for_android&illust_id=$id");
|
||||||
|
if (res.success) {
|
||||||
|
return Res(
|
||||||
|
(res.data["illusts"] as List).map((e) => Illust.fromJson(e)).toList());
|
||||||
|
} else {
|
||||||
|
return Res.error(res.errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
|
import 'package:pixes/components/animated_image.dart';
|
||||||
import 'package:pixes/components/grid.dart';
|
import 'package:pixes/components/grid.dart';
|
||||||
import 'package:pixes/components/md.dart';
|
import 'package:pixes/components/md.dart';
|
||||||
import 'package:pixes/components/message.dart';
|
import 'package:pixes/components/message.dart';
|
||||||
@@ -73,9 +74,11 @@ class _DownloadedPageState extends State<DownloadedPage> {
|
|||||||
color: ColorScheme.of(context).secondaryContainer
|
color: ColorScheme.of(context).secondaryContainer
|
||||||
),
|
),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: image == null ? null : Image(
|
child: image == null ? null : AnimatedImage(
|
||||||
image: FileImage(image),
|
image: FileImage(image),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
width: 96,
|
||||||
|
height: double.infinity,
|
||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -4,10 +4,12 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' show Icons;
|
import 'package:flutter/material.dart' show Icons;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:pixes/components/animated_image.dart';
|
import 'package:pixes/components/animated_image.dart';
|
||||||
import 'package:pixes/components/loading.dart';
|
import 'package:pixes/components/loading.dart';
|
||||||
import 'package:pixes/components/message.dart';
|
import 'package:pixes/components/message.dart';
|
||||||
import 'package:pixes/components/page_route.dart';
|
import 'package:pixes/components/page_route.dart';
|
||||||
|
import 'package:pixes/components/title_bar.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
import 'package:pixes/foundation/image_provider.dart';
|
import 'package:pixes/foundation/image_provider.dart';
|
||||||
import 'package:pixes/network/download.dart';
|
import 'package:pixes/network/download.dart';
|
||||||
@@ -18,12 +20,29 @@ import 'package:pixes/pages/user_info_page.dart';
|
|||||||
import 'package:pixes/utils/translation.dart';
|
import 'package:pixes/utils/translation.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
import '../components/illust_widget.dart';
|
||||||
import '../components/md.dart';
|
import '../components/md.dart';
|
||||||
import '../components/ugoira.dart';
|
import '../components/ugoira.dart';
|
||||||
|
|
||||||
|
|
||||||
const _kBottomBarHeight = 64.0;
|
const _kBottomBarHeight = 64.0;
|
||||||
|
|
||||||
|
class IllustGalleryPage extends StatefulWidget {
|
||||||
|
const IllustGalleryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<IllustGalleryPage> createState() => _IllustGalleryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IllustGalleryPageState extends State<IllustGalleryPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// TODO
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class IllustPage extends StatefulWidget {
|
class IllustPage extends StatefulWidget {
|
||||||
const IllustPage(this.illust, {this.favoriteCallback, super.key});
|
const IllustPage(this.illust, {this.favoriteCallback, super.key});
|
||||||
|
|
||||||
@@ -709,7 +728,7 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin{
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8,),
|
const SizedBox(width: 6,),
|
||||||
Button(
|
Button(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Share.share("${widget.illust.title}\nhttps://pixiv.net/artworks/${widget.illust.id}");
|
Share.share("${widget.illust.title}\nhttps://pixiv.net/artworks/${widget.illust.id}");
|
||||||
@@ -718,18 +737,14 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin{
|
|||||||
height: 28,
|
height: 28,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const Icon(Icons.share, size: 18,),
|
||||||
Icons.share,
|
|
||||||
color: ColorScheme.of(context).error,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8,),
|
const SizedBox(width: 8,),
|
||||||
Text("Share".tl)
|
Text("Share".tl)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8,),
|
const SizedBox(width: 6,),
|
||||||
Button(
|
Button(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
var text = "https://pixiv.net/artworks/${widget.illust.id}";
|
var text = "https://pixiv.net/artworks/${widget.illust.id}";
|
||||||
@@ -740,19 +755,31 @@ class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin{
|
|||||||
height: 28,
|
height: 28,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const Icon(Icons.copy, size: 18),
|
||||||
Icons.copy,
|
|
||||||
color: ColorScheme.of(context).error,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8,),
|
const SizedBox(width: 8,),
|
||||||
Text("Link".tl)
|
Text("Link".tl)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 6,),
|
||||||
|
Button(
|
||||||
|
onPressed: () {
|
||||||
|
context.to(() => _RelatedIllustsPage(widget.illust.id.toString()));
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 28,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.stars, size: 18),
|
||||||
|
const SizedBox(width: 8,),
|
||||||
|
Text("Related".tl)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).paddingHorizontal(4).paddingBottom(4);
|
).paddingHorizontal(2).paddingBottom(4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,3 +962,64 @@ class _IllustPageWithIdState extends LoadingState<IllustPageWithId, Illust> {
|
|||||||
return Network().getIllustByID(widget.id);
|
return Network().getIllustByID(widget.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RelatedIllustsPage extends StatefulWidget {
|
||||||
|
const _RelatedIllustsPage(this.id, {super.key});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RelatedIllustsPage> createState() => _RelatedIllustsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RelatedIllustsPageState extends MultiPageLoadingState<_RelatedIllustsPage, Illust> {
|
||||||
|
@override
|
||||||
|
Widget? buildFrame(BuildContext context, Widget child) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
TitleBar(title: "Related artworks".tl),
|
||||||
|
Expanded(
|
||||||
|
child: child,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, final List<Illust> data) {
|
||||||
|
return LayoutBuilder(builder: (context, constrains){
|
||||||
|
return MasonryGridView.builder(
|
||||||
|
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){
|
||||||
|
nextPage();
|
||||||
|
}
|
||||||
|
return IllustWidget(data[index]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String? nextUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Res<List<Illust>>> loadData(page) async{
|
||||||
|
if(nextUrl == "end") {
|
||||||
|
return Res.error("No more data");
|
||||||
|
}
|
||||||
|
var res = nextUrl == null
|
||||||
|
? await Network().relatedIllusts(widget.id)
|
||||||
|
: await Network().getIllustsWithNextUrl(nextUrl!);
|
||||||
|
if(!res.error) {
|
||||||
|
nextUrl = res.subData;
|
||||||
|
nextUrl ??= "end";
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ import 'package:pixes/components/batch_download.dart';
|
|||||||
import 'package:pixes/components/loading.dart';
|
import 'package:pixes/components/loading.dart';
|
||||||
import 'package:pixes/components/md.dart';
|
import 'package:pixes/components/md.dart';
|
||||||
import 'package:pixes/components/segmented_button.dart';
|
import 'package:pixes/components/segmented_button.dart';
|
||||||
|
import 'package:pixes/components/user_preview.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
import 'package:pixes/foundation/image_provider.dart';
|
import 'package:pixes/foundation/image_provider.dart';
|
||||||
import 'package:pixes/network/network.dart';
|
import 'package:pixes/network/network.dart';
|
||||||
@@ -35,6 +36,10 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
|
|||||||
content: CustomScrollView(
|
content: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
buildUser(),
|
buildUser(),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: buildHeader("Related users".tl),
|
||||||
|
),
|
||||||
|
_RelatedUsers(widget.id),
|
||||||
buildInformation(),
|
buildInformation(),
|
||||||
buildArtworkHeader(),
|
buildArtworkHeader(),
|
||||||
_UserArtworks(data.id.toString(), page, key: ValueKey(data.id + page),),
|
_UserArtworks(data.id.toString(), page, key: ValueKey(data.id + page),),
|
||||||
@@ -333,3 +338,48 @@ class _UserArtworksState extends MultiPageLoadingState<_UserArtworks, Illust> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RelatedUsers extends StatefulWidget {
|
||||||
|
const _RelatedUsers(this.uid);
|
||||||
|
|
||||||
|
final String uid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_RelatedUsers> createState() => _RelatedUsersState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RelatedUsersState extends LoadingState<_RelatedUsers, List<UserPreview>> {
|
||||||
|
@override
|
||||||
|
Widget buildFrame(BuildContext context, Widget child) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 108,
|
||||||
|
width: double.infinity,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ScrollController _controller = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, List<UserPreview> data) {
|
||||||
|
return Scrollbar(
|
||||||
|
controller: _controller,
|
||||||
|
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(264);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Res<List<UserPreview>>> loadData() {
|
||||||
|
return Network().relatedUsers(widget.uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user