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

View File

@@ -307,7 +307,7 @@ class SideBarRoute<T> extends PopupRoute<T> {
final Widget child;
@override
Color? get barrierColor => const Color.fromARGB(64, 205, 205, 205);
Color? get barrierColor => Colors.transparent;
@override
bool get barrierDismissible => true;
@@ -331,7 +331,8 @@ class SideBarRoute<T> extends PopupRoute<T> {
color: FluentTheme.of(context).micaBackgroundColor.withOpacity(0.98),
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,
child: child,
),

View File

@@ -31,20 +31,26 @@ class _BookMarkedArtworkPageState extends State<BookMarkedArtworkPage>{
}
Widget buildTab() {
return SegmentedButton(
options: [
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
return Row(
children: [
Text("Following".tl, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
const Spacer(),
SegmentedButton(
options: [
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
],
onPressed: (key) {
if(key != restrict) {
setState(() {
restrict = key;
});
}
},
value: restrict,
)
],
onPressed: (key) {
if(key != restrict) {
setState(() {
restrict = key;
});
}
},
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) {
return LayoutBuilder(builder: (context, constrains){
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),

View File

@@ -31,21 +31,28 @@ class _FollowingArtworksPageState extends State<FollowingArtworksPage> {
}
Widget buildTab() {
return SegmentedButton(
options: [
SegmentedButtonOption("all", "All".tl),
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
return Row(
children: [
Text("Following".tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
const Spacer(),
SegmentedButton(
options: [
SegmentedButtonOption("all", "All".tl),
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
],
onPressed: (key) {
if(key != restrict) {
setState(() {
restrict = key;
});
}
},
value: restrict,
)
],
onPressed: (key) {
if(key != restrict) {
setState(() {
restrict = key;
});
}
},
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) {
return LayoutBuilder(builder: (context, constrains){
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show Icons;
import 'package:pixes/components/animated_image.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/network.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/utils/translation.dart';
@@ -65,6 +67,7 @@ class _IllustPageState extends State<IllustPage> {
Widget buildBody(double width, double height) {
return ListView.builder(
itemCount: widget.illust.images.length + 2,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
return buildImage(width, height, index);
});
@@ -155,9 +158,7 @@ class _BottomBar extends StatefulWidget {
State<_BottomBar> createState() => _BottomBarState();
}
class _BottomBarState extends State<_BottomBar> {
double? top;
class _BottomBarState extends State<_BottomBar> with TickerProviderStateMixin{
double pageHeight = 0;
double widgetHeight = 48;
@@ -166,36 +167,77 @@ class _BottomBarState extends State<_BottomBar> {
double _width = 0;
late VerticalDragGestureRecognizer _recognizer;
late final AnimationController animationController;
double get minValue => pageHeight - widgetHeight;
double get maxValue => pageHeight - _kBottomBarHeight;
@override
void initState() {
_width = widget.width;
pageHeight = widget.height;
top = pageHeight - _kBottomBarHeight;
Future.delayed(const Duration(milliseconds: 200), () {
final box = key.currentContext?.findRenderObject() as RenderBox?;
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();
}
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
void didUpdateWidget(covariant _BottomBar oldWidget) {
if (widget.height != pageHeight) {
setState(() {
pageHeight = widget.height;
top = pageHeight - _kBottomBarHeight;
});
}
_recognizer.dispose();
if(_width != widget.width) {
_width = widget.width;
Future.microtask(() {
final box = key.currentContext?.findRenderObject() as RenderBox?;
var oldHeight = widgetHeight;
widgetHeight = (box?.size.height) ?? 0;
if(oldHeight != widgetHeight && top != pageHeight - _kBottomBarHeight) {
setState(() {
top = pageHeight - widgetHeight;
});
if(oldHeight != widgetHeight) {
setState(() {});
}
});
}
@@ -204,32 +246,44 @@ class _BottomBarState extends State<_BottomBar> {
@override
Widget build(BuildContext context) {
return AnimatedPositioned(
top: top,
left: 0,
right: 0,
duration: const Duration(milliseconds: 180),
curve: Curves.ease,
child: Card(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
backgroundColor:
FluentTheme.of(context).micaBackgroundColor.withOpacity(0.96),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SizedBox(
width: double.infinity,
key: key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTop(),
buildStats(),
buildTags(),
SelectableText("${"Artwork ID".tl}: ${widget.illust.id}\n${"Artist ID".tl}: ${widget.illust.author.id}", style: TextStyle(color: ColorScheme.of(context).outline),).paddingLeft(4),
const SizedBox(height: 8,)
],
),
),
return AnimatedBuilder(
animation: CurvedAnimation(
parent: animationController,
curve: Curves.ease,
reverseCurve: Curves.ease,
),
builder: (context, child) {
return Positioned(
top: minValue + (maxValue - minValue) * animationController.value,
left: 0,
right: 0,
child: Listener(
onPointerDown: (event) {
_recognizer.addPointer(event);
},
child: Card(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
backgroundColor:
FluentTheme.of(context).micaBackgroundColor.withOpacity(0.96),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SizedBox(
width: double.infinity,
key: key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTop(),
buildStats(),
buildTags(),
SelectableText("${"Artwork ID".tl}: ${widget.illust.id}\n${"Artist ID".tl}: ${widget.illust.author.id}", style: TextStyle(color: ColorScheme.of(context).outline),).paddingLeft(4),
const SizedBox(height: 8,)
],
),
),
),
),
);
},
);
}
@@ -243,21 +297,17 @@ class _BottomBarState extends State<_BottomBar> {
buildAuthor(),
...buildActions(constrains.maxWidth),
const Spacer(),
if (top == pageHeight - _kBottomBarHeight)
if (animationController.value == 1)
IconButton(
icon: const Icon(FluentIcons.up),
onPressed: () {
setState(() {
top = pageHeight - widgetHeight;
});
animationController.reverse();
})
else
IconButton(
icon: const Icon(FluentIcons.down),
onPressed: () {
setState(() {
top = pageHeight - _kBottomBarHeight;
});
animationController.forward();
})
],
);
@@ -287,14 +337,15 @@ class _BottomBarState extends State<_BottomBar> {
});
}
final bool showUserName = MediaQuery.of(context).size.width > 640;
return Card(
margin: const EdgeInsets.symmetric(vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 8),
borderRadius: BorderRadius.circular(8),
backgroundColor: FluentTheme.of(context).cardColor.withOpacity(0.72),
child: SizedBox(
height: double.infinity,
width: 246,
width: showUserName ? 246 : 128,
child: Row(
children: [
SizedBox(
@@ -325,12 +376,13 @@ class _BottomBarState extends State<_BottomBar> {
const SizedBox(
width: 8,
),
Expanded(
child: Text(
widget.illust.author.name,
maxLines: 2,
if(showUserName)
Expanded(
child: Text(
widget.illust.author.name,
maxLines: 2,
),
),
),
if(isFollowing)
Button(onPressed: follow, child: const SizedBox(
width: 42,
@@ -343,11 +395,14 @@ class _BottomBarState extends State<_BottomBar> {
),
))
else if (!widget.illust.author.isFollowed)
Button(onPressed: follow, child: Text("Follow".tl))
Button(onPressed: follow, child: Text("Follow".tl).fixWidth(56))
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),
).fixWidth(56),
),
],
),
@@ -558,9 +613,17 @@ class _BottomBarState extends State<_BottomBar> {
if(e.translatedName != null && e.name != e.translatedName) {
text += "/${e.translatedName}";
}
return Card(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
child: Text(text, style: const TextStyle(fontSize: 13),),
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
context.to(() => SearchResultPage(e.name));
},
child: Card(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 6),
child: Text(text, style: const TextStyle(fontSize: 13),),
),
),
);
}).toList(),
),
@@ -601,10 +664,11 @@ class _CommentsPageState extends MultiPageLoadingState<_CommentsPage, Comment> {
Widget buildBody(BuildContext context, List<Comment> data) {
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: data.length + 2,
itemBuilder: (context, index) {
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) {
return const SizedBox(height: 64,);
}

View File

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

View File

@@ -35,20 +35,29 @@ class _RecommendationPageState extends State<RecommendationPage> {
}
Widget buildTab() {
return SegmentedButton<int>(
options: [
SegmentedButtonOption(0, "Artworks".tl),
SegmentedButtonOption(1, "Users".tl),
],
onPressed: (key) {
if(key != type) {
setState(() {
type = key;
});
}
},
value: type,
).padding(const EdgeInsets.symmetric(vertical: 8, horizontal: 8));
return SizedBox(
child: Row(
children: [
Text("Explore".tl,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
const Spacer(),
SegmentedButton<int>(
options: [
SegmentedButtonOption(0, "Artworks".tl),
SegmentedButtonOption(1, "Users".tl),
],
onPressed: (key) {
if(key != type) {
setState(() {
type = key;
});
}
},
value: type,
)
],
).paddingHorizontal(16).paddingBottom(4),
);
}
}
@@ -65,6 +74,7 @@ class _RecommendationArtworksPageState extends MultiPageLoadingState<_Recommenda
Widget buildContent(BuildContext context, final List<Illust> data) {
return LayoutBuilder(builder: (context, constrains){
return MasonryGridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 240,
),

View File

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

View File

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