improve mobile ui

This commit is contained in:
wgh19
2024-05-14 16:43:41 +08:00
parent 0ec700c835
commit c4b50a3fb1
10 changed files with 212 additions and 111 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:pixes/components/md.dart';
void showToast(BuildContext context, {required String message, IconData? icon}) { void showToast(BuildContext context, {required String message, IconData? icon}) {
var newEntry = OverlayEntry( var newEntry = OverlayEntry(
@@ -27,7 +28,7 @@ class ToastOverlay extends StatelessWidget {
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: PhysicalModel( child: PhysicalModel(
color: Colors.white, color: ColorScheme.of(context).surface,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
elevation: 1, elevation: 1,
child: Container( child: Container(
@@ -42,8 +43,11 @@ class ToastOverlay extends StatelessWidget {
), ),
Text( Text(
message, message,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500), fontSize: 16,
fontWeight: FontWeight.w500,
color: ColorScheme.of(context).onSurface
),
maxLines: 3, maxLines: 3,
), ),
], ],

View File

@@ -307,7 +307,7 @@ class SideBarRoute<T> extends PopupRoute<T> {
final Widget child; final Widget child;
@override @override
Color? get barrierColor => const Color.fromARGB(64, 205, 205, 205); Color? get barrierColor => Colors.transparent;
@override @override
bool get barrierDismissible => true; bool get barrierDismissible => true;
@@ -331,7 +331,8 @@ class SideBarRoute<T> extends PopupRoute<T> {
color: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.98), color: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.98),
borderRadius: const BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4)) borderRadius: const BorderRadius.only(topLeft: Radius.circular(4), bottomLeft: Radius.circular(4))
), ),
constraints: const BoxConstraints(maxWidth: _kSideBarWidth), constraints: BoxConstraints(maxWidth: min(_kSideBarWidth,
MediaQuery.of(context).size.width)),
width: double.infinity, width: double.infinity,
child: child, child: child,
), ),

View File

@@ -31,7 +31,11 @@ class _BookMarkedArtworkPageState extends State<BookMarkedArtworkPage>{
} }
Widget buildTab() { Widget buildTab() {
return SegmentedButton( return Row(
children: [
Text("Following".tl, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
const Spacer(),
SegmentedButton(
options: [ options: [
SegmentedButtonOption("public", "Public".tl), SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl), SegmentedButtonOption("private", "Private".tl),
@@ -44,7 +48,9 @@ class _BookMarkedArtworkPageState extends State<BookMarkedArtworkPage>{
} }
}, },
value: restrict, value: restrict,
).padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 8)); )
],
).paddingHorizontal(16).paddingVertical(4);
} }
} }
@@ -62,6 +68,7 @@ class _OneBookmarkedPageState extends MultiPageLoadingState<_OneBookmarkedPage,
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),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240, maxCrossAxisExtent: 240,
), ),

View File

@@ -31,7 +31,12 @@ class _FollowingArtworksPageState extends State<FollowingArtworksPage> {
} }
Widget buildTab() { Widget buildTab() {
return SegmentedButton( return Row(
children: [
Text("Following".tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
const Spacer(),
SegmentedButton(
options: [ options: [
SegmentedButtonOption("all", "All".tl), SegmentedButtonOption("all", "All".tl),
SegmentedButtonOption("public", "Public".tl), SegmentedButtonOption("public", "Public".tl),
@@ -45,7 +50,9 @@ class _FollowingArtworksPageState extends State<FollowingArtworksPage> {
} }
}, },
value: restrict, value: restrict,
).padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 8)); )
],
).paddingHorizontal(16).paddingBottom(4);
} }
} }
@@ -63,6 +70,7 @@ class _OneFollowingPageState extends MultiPageLoadingState<_OneFollowingPage, Il
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),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240, maxCrossAxisExtent: 240,
), ),

View File

@@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart'; import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show Icons; import 'package:flutter/material.dart' show Icons;
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';
@@ -11,6 +12,7 @@ import 'package:pixes/foundation/image_provider.dart';
import 'package:pixes/network/download.dart'; import 'package:pixes/network/download.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
import 'package:pixes/pages/image_page.dart'; import 'package:pixes/pages/image_page.dart';
import 'package:pixes/pages/search_page.dart';
import 'package:pixes/pages/user_info_page.dart'; import 'package:pixes/pages/user_info_page.dart';
import 'package:pixes/utils/translation.dart'; import 'package:pixes/utils/translation.dart';
@@ -65,6 +67,7 @@ class _IllustPageState extends State<IllustPage> {
Widget buildBody(double width, double height) { Widget buildBody(double width, double height) {
return ListView.builder( return ListView.builder(
itemCount: widget.illust.images.length + 2, itemCount: widget.illust.images.length + 2,
padding: EdgeInsets.zero,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return buildImage(width, height, index); return buildImage(width, height, index);
}); });
@@ -155,9 +158,7 @@ class _BottomBar extends StatefulWidget {
State<_BottomBar> createState() => _BottomBarState(); State<_BottomBar> createState() => _BottomBarState();
} }
class _BottomBarState extends State<_BottomBar> { class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin{
double? top;
double pageHeight = 0; double pageHeight = 0;
double widgetHeight = 48; double widgetHeight = 48;
@@ -166,36 +167,77 @@ class _BottomBarState extends State<_BottomBar> {
double _width = 0; double _width = 0;
late VerticalDragGestureRecognizer _recognizer;
late final AnimationController animationController;
double get minValue => pageHeight - widgetHeight;
double get maxValue => pageHeight - _kBottomBarHeight;
@override @override
void initState() { void initState() {
_width = widget.width; _width = widget.width;
pageHeight = widget.height; pageHeight = widget.height;
top = pageHeight - _kBottomBarHeight;
Future.delayed(const Duration(milliseconds: 200), () { Future.delayed(const Duration(milliseconds: 200), () {
final box = key.currentContext?.findRenderObject() as RenderBox?; final box = key.currentContext?.findRenderObject() as RenderBox?;
widgetHeight = (box?.size.height) ?? 0; widgetHeight = (box?.size.height) ?? 0;
}); });
_recognizer = VerticalDragGestureRecognizer()
..onStart = _handlePointerDown
..onUpdate = _handlePointerMove
..onEnd = _handlePointerUp
..onCancel = _handlePointerCancel;
animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 180),
value: 1
);
super.initState(); super.initState();
} }
void _handlePointerDown(DragStartDetails details) {}
void _handlePointerMove(DragUpdateDetails details) {
var offset = details.primaryDelta ?? 0;
final minValue = pageHeight - widgetHeight;
final maxValue = pageHeight - _kBottomBarHeight;
var top = animationController.value * (maxValue - minValue) + minValue;
top = (top + offset).clamp(minValue, maxValue);
animationController.value = (top - minValue) / (maxValue - minValue);
}
void _handlePointerUp(DragEndDetails details) {
var speed = details.primaryVelocity ?? 0;
const minShouldTransitionSpeed = 1000;
if(speed > minShouldTransitionSpeed) {
animationController.forward();
} else if(speed < minShouldTransitionSpeed) {
animationController.reverse();
} else {
_handlePointerCancel();
}
}
void _handlePointerCancel() {
if(animationController.value >= 0.5 ) {
animationController.forward();
} else {
animationController.reverse();
}
}
@override @override
void didUpdateWidget(covariant _BottomBar oldWidget) { void didUpdateWidget(covariant _BottomBar oldWidget) {
if (widget.height != pageHeight) { if (widget.height != pageHeight) {
setState(() { setState(() {
pageHeight = widget.height; pageHeight = widget.height;
top = pageHeight - _kBottomBarHeight;
}); });
} }
_recognizer.dispose();
if(_width != widget.width) { if(_width != widget.width) {
_width = widget.width; _width = widget.width;
Future.microtask(() { Future.microtask(() {
final box = key.currentContext?.findRenderObject() as RenderBox?; final box = key.currentContext?.findRenderObject() as RenderBox?;
var oldHeight = widgetHeight; var oldHeight = widgetHeight;
widgetHeight = (box?.size.height) ?? 0; widgetHeight = (box?.size.height) ?? 0;
if(oldHeight != widgetHeight && top != pageHeight - _kBottomBarHeight) { if(oldHeight != widgetHeight) {
setState(() { setState(() {});
top = pageHeight - widgetHeight;
});
} }
}); });
} }
@@ -204,12 +246,21 @@ class _BottomBarState extends State<_BottomBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedPositioned( return AnimatedBuilder(
top: top, animation: CurvedAnimation(
parent: animationController,
curve: Curves.ease,
reverseCurve: Curves.ease,
),
builder: (context, child) {
return Positioned(
top: minValue + (maxValue - minValue) * animationController.value,
left: 0, left: 0,
right: 0, right: 0,
duration: const Duration(milliseconds: 180), child: Listener(
curve: Curves.ease, onPointerDown: (event) {
_recognizer.addPointer(event);
},
child: Card( child: Card(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
backgroundColor: backgroundColor:
@@ -230,6 +281,9 @@ class _BottomBarState extends State<_BottomBar> {
), ),
), ),
), ),
),
);
},
); );
} }
@@ -243,21 +297,17 @@ class _BottomBarState extends State<_BottomBar> {
buildAuthor(), buildAuthor(),
...buildActions(constrains.maxWidth), ...buildActions(constrains.maxWidth),
const Spacer(), const Spacer(),
if (top == pageHeight - _kBottomBarHeight) if (animationController.value == 1)
IconButton( IconButton(
icon: const Icon(FluentIcons.up), icon: const Icon(FluentIcons.up),
onPressed: () { onPressed: () {
setState(() { animationController.reverse();
top = pageHeight - widgetHeight;
});
}) })
else else
IconButton( IconButton(
icon: const Icon(FluentIcons.down), icon: const Icon(FluentIcons.down),
onPressed: () { onPressed: () {
setState(() { animationController.forward();
top = pageHeight - _kBottomBarHeight;
});
}) })
], ],
); );
@@ -287,14 +337,15 @@ class _BottomBarState extends State<_BottomBar> {
}); });
} }
final bool showUserName = MediaQuery.of(context).size.width > 640;
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 8), margin: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
backgroundColor: FluentTheme.of(context).cardColor.withOpacity(0.72),
child: SizedBox( child: SizedBox(
height: double.infinity, height: double.infinity,
width: 246, width: showUserName ? 246 : 128,
child: Row( child: Row(
children: [ children: [
SizedBox( SizedBox(
@@ -325,6 +376,7 @@ class _BottomBarState extends State<_BottomBar> {
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
if(showUserName)
Expanded( Expanded(
child: Text( child: Text(
widget.illust.author.name, widget.illust.author.name,
@@ -343,11 +395,14 @@ class _BottomBarState extends State<_BottomBar> {
), ),
)) ))
else if (!widget.illust.author.isFollowed) else if (!widget.illust.author.isFollowed)
Button(onPressed: follow, child: Text("Follow".tl)) Button(onPressed: follow, child: Text("Follow".tl).fixWidth(56))
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),
).fixWidth(56),
), ),
], ],
), ),
@@ -558,9 +613,17 @@ class _BottomBarState extends State<_BottomBar> {
if(e.translatedName != null && e.name != e.translatedName) { if(e.translatedName != null && e.name != e.translatedName) {
text += "/${e.translatedName}"; text += "/${e.translatedName}";
} }
return Card( return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
context.to(() => SearchResultPage(e.name));
},
child: Card(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
child: Text(text, style: const TextStyle(fontSize: 13),), child: Text(text, style: const TextStyle(fontSize: 13),),
),
),
); );
}).toList(), }).toList(),
), ),
@@ -601,10 +664,11 @@ class _CommentsPageState extends MultiPageLoadingState<_CommentsPage, Comment> {
Widget buildBody(BuildContext context, List<Comment> data) { Widget buildBody(BuildContext context, List<Comment> data) {
return ListView.builder( return ListView.builder(
padding: EdgeInsets.zero,
itemCount: data.length + 2, itemCount: data.length + 2,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if(index == 0) { if(index == 0) {
return Text("Comments".tl, style: const TextStyle(fontSize: 20)).paddingVertical(8).paddingHorizontal(12); return Text("Comments".tl, style: const TextStyle(fontSize: 20)).paddingVertical(16).paddingHorizontal(12);
} else if(index == data.length + 1) { } else if(index == data.length + 1) {
return const SizedBox(height: 64,); return const SizedBox(height: 64,);
} }

View File

@@ -23,7 +23,7 @@ import "package:window_manager/window_manager.dart";
import "../components/page_route.dart"; import "../components/page_route.dart";
import "downloading_page.dart"; import "downloading_page.dart";
const _kAppBarHeight = 36.0; double get _appBarHeight => App.isDesktop ? 36.0 : 48.0;
class MainPage extends StatefulWidget { class MainPage extends StatefulWidget {
const MainPage({super.key}); const MainPage({super.key});
@@ -143,11 +143,14 @@ class _MainPageState extends State<MainPage> with WindowListener {
), ),
], ],
), ),
paneBodyBuilder: (pane, child) => Navigator( paneBodyBuilder: (pane, child) => NavigatorPopHandler(
key: const Key("navigator"),
onPop: () => navigatorKey.currentState?.pop(),
child: Navigator(
key: navigatorKey, key: navigatorKey,
onGenerateRoute: (settings) => AppPageRoute( onGenerateRoute: (settings) => AppPageRoute(
builder: (context) => const RecommendationPage()), builder: (context) => const RecommendationPage()),
)), ))),
)); ));
} }
@@ -176,7 +179,7 @@ class _MainPageState extends State<MainPage> with WindowListener {
BuildContext context, GlobalKey<NavigatorState> navigatorKey) { BuildContext context, GlobalKey<NavigatorState> navigatorKey) {
return NavigationAppBar( return NavigationAppBar(
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
height: _kAppBarHeight, height: _appBarHeight,
title: () { title: () {
if (!App.isDesktop) { if (!App.isDesktop) {
return const Align( return const Align(
@@ -198,9 +201,9 @@ class _MainPageState extends State<MainPage> with WindowListener {
); );
}(), }(),
leading: _BackButton(navigatorKey), leading: _BackButton(navigatorKey),
actions: WindowButtons( actions: App.isDesktop ? WindowButtons(
key: ValueKey(windowButtonKey), key: ValueKey(windowButtonKey),
), ) : null,
); );
} }
} }
@@ -298,7 +301,7 @@ class WindowButtons extends StatelessWidget {
return SizedBox( return SizedBox(
width: 138, width: 138,
height: _kAppBarHeight, height: _appBarHeight,
child: Row( child: Row(
children: [ children: [
WindowButton( WindowButton(

View File

@@ -67,7 +67,7 @@ class _RankingPageState extends State<RankingPage> {
) )
], ],
) )
).padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 12)); ).padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 16));
} }
} }
@@ -85,6 +85,7 @@ class _OneRankingPageState extends MultiPageLoadingState<_OneRankingPage, Illust
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),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240, maxCrossAxisExtent: 240,
), ),

View File

@@ -35,7 +35,13 @@ class _RecommendationPageState extends State<RecommendationPage> {
} }
Widget buildTab() { Widget buildTab() {
return SegmentedButton<int>( return SizedBox(
child: Row(
children: [
Text("Explore".tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
const Spacer(),
SegmentedButton<int>(
options: [ options: [
SegmentedButtonOption(0, "Artworks".tl), SegmentedButtonOption(0, "Artworks".tl),
SegmentedButtonOption(1, "Users".tl), SegmentedButtonOption(1, "Users".tl),
@@ -48,7 +54,10 @@ class _RecommendationPageState extends State<RecommendationPage> {
} }
}, },
value: type, value: type,
).padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 8)); )
],
).paddingHorizontal(16).paddingBottom(4),
);
} }
} }
@@ -65,6 +74,7 @@ class _RecommendationArtworksPageState extends MultiPageLoadingState<_Recommenda
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),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240, maxCrossAxisExtent: 240,
), ),

View File

@@ -58,6 +58,7 @@ class _SearchPageState extends State<SearchPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ScaffoldPage( return ScaffoldPage(
padding: EdgeInsets.zero,
content: Column( content: Column(
children: [ children: [
buildSearchBar(), buildSearchBar(),
@@ -171,6 +172,7 @@ class _TrendingTagsViewState extends LoadingState<_TrendingTagsView, List<Trendi
@override @override
Widget buildContent(BuildContext context, List<TrendingTag> data) { Widget buildContent(BuildContext context, List<TrendingTag> data) {
return MasonryGridView.builder( return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240, maxCrossAxisExtent: 240,
), ),

View File

@@ -77,6 +77,7 @@ class _UserInfoPageState extends LoadingState<UserInfoPage, UserDetails> {
image: CachedImageProvider(data!.avatar), image: CachedImageProvider(data!.avatar),
width: 64, width: 64,
height: 64, height: 64,
fit: BoxFit.cover,
), ),
),), ),),
const SizedBox(height: 8), const SizedBox(height: 8),