improve ui

This commit is contained in:
2024-11-13 12:21:57 +08:00
parent 8e99e94620
commit 9bdcba1270
11 changed files with 199 additions and 212 deletions

View File

@@ -156,7 +156,7 @@ class _ButtonState extends State<Button> {
@override
Widget build(BuildContext context) {
var padding = widget.padding ??
const EdgeInsets.symmetric(horizontal: 16, vertical: 4);
const EdgeInsets.symmetric(horizontal: 16);
var width = widget.width;
if (width != null) {
width = width - padding.horizontal;
@@ -206,6 +206,7 @@ class _ButtonState extends State<Button> {
padding: padding,
constraints: const BoxConstraints(
minWidth: 76,
minHeight: 32,
),
decoration: BoxDecoration(
color: buttonColor,

View File

@@ -158,12 +158,16 @@ class ComicTile extends StatelessWidget {
image = FileImage(File(comic.cover.substring(7)));
} else if (comic.sourceKey == 'local') {
var localComic = LocalManager().find(comic.id, ComicType.local);
if(localComic == null) {
if (localComic == null) {
return const SizedBox();
}
image = FileImage(localComic.coverFile);
} else {
image = CachedImageProvider(comic.cover, sourceKey: comic.sourceKey);
image = CachedImageProvider(
comic.cover,
sourceKey: comic.sourceKey,
cid: comic.id,
);
}
return AnimatedImage(
image: image,
@@ -485,12 +489,11 @@ class _ComicDescription extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Center(
child:Text(
child: Text(
"${badge![0].toUpperCase()}${badge!.substring(1).toLowerCase()}",
style: const TextStyle(fontSize: 12),
),
)
),
)),
],
)
],
@@ -574,15 +577,14 @@ class _ReadingHistoryPainter extends CustomPainter {
}
class SliverGridComics extends StatefulWidget {
const SliverGridComics({
super.key,
const SliverGridComics(
{super.key,
required this.comics,
this.onLastItemBuild,
this.badgeBuilder,
this.menuBuilder,
this.onTap,
this.selections
});
this.selections});
final List<Comic> comics;
@@ -681,37 +683,23 @@ class _SliverGridComics extends StatelessWidget {
onLastItemBuild?.call();
}
var badge = badgeBuilder?.call(comics[index]);
return Stack(
children: [
ComicTile(
var isSelected =
selection == null ? false : selection![comics[index]] ?? false;
var comic = ComicTile(
comic: comics[index],
badge: badge,
menuOptions: menuBuilder?.call(comics[index]),
onTap: onTap != null ? () => onTap!(comics[index]) : null,
);
return Container(
decoration: BoxDecoration(
color: isSelected
? Theme.of(context).colorScheme.surfaceContainer
: null,
borderRadius: BorderRadius.circular(12),
),
Positioned(
bottom: 10,
right: 8,
child: Visibility(
visible: selection == null ? false : selection![comics[index]] ?? false,
child: Stack(
children: [
Transform.scale(
scale: 0.9,
child: const Icon(
Icons.circle_rounded,
color: Colors.white,
)
),
const Icon(
Icons.check_circle_rounded,
color: Colors.green,
)
],
)
)
)
],
margin: const EdgeInsets.all(4),
child: comic,
);
},
childCount: comics.length,
@@ -910,7 +898,7 @@ class ComicListState extends State<ComicList> {
try {
if (widget.loadPage != null) {
var res = await widget.loadPage!(page);
if(!mounted) return;
if (!mounted) return;
if (res.success) {
if (res.data.isEmpty) {
_data[page] = const [];

View File

@@ -8,7 +8,7 @@ import 'cached_image.dart' as image_provider;
class CachedImageProvider
extends BaseImageProvider<image_provider.CachedImageProvider> {
/// Image provider for normal image.
const CachedImageProvider(this.url, {this.headers, this.sourceKey});
const CachedImageProvider(this.url, {this.headers, this.sourceKey, this.cid});
final String url;
@@ -16,9 +16,11 @@ class CachedImageProvider
final String? sourceKey;
final String? cid;
@override
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async {
await for (var progress in ImageDownloader.loadThumbnail(url, sourceKey)) {
await for (var progress in ImageDownloader.loadThumbnail(url, sourceKey, cid)) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: progress.currentBytes,
expectedTotalBytes: progress.totalBytes,
@@ -36,5 +38,5 @@ class CachedImageProvider
}
@override
String get key => url;
String get key => url + (sourceKey ?? "") + (cid ?? "");
}

View File

@@ -357,6 +357,7 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
}
LocalManager().completeTask(this);
stopRecorder();
}
@override
@@ -534,6 +535,9 @@ class _ImageDownloadWrapper {
}
}
} catch (e, s) {
if (isCancelled) {
return;
}
Log.error("Download", e.toString(), s);
retry--;
if (retry > 0) {

View File

@@ -10,8 +10,9 @@ import 'app_dio.dart';
class ImageDownloader {
static Stream<ImageDownloadProgress> loadThumbnail(
String url, String? sourceKey) async* {
final cacheKey = "$url@$sourceKey";
String url, String? sourceKey,
[String? cid]) async* {
final cacheKey = "$url@$sourceKey${cid != null ? '@$cid' : ''}";
final cache = await CacheManager().findCache(cacheKey);
if (cache != null) {
@@ -34,6 +35,16 @@ class ImageDownloader {
configs['headers']['user-agent'] = webUA;
}
if (((configs['url'] as String?) ?? url).startsWith('cover.') &&
sourceKey != null) {
var comicSource = ComicSource.find(sourceKey);
if(comicSource != null) {
var comicInfo = await comicSource.loadComicInfo!(cid!);
yield* loadThumbnail(comicInfo.data.cover, sourceKey);
return;
}
}
var dio = AppDio(BaseOptions(
headers: Map<String, dynamic>.from(configs['headers']),
method: configs['method'] ?? 'GET',
@@ -171,9 +182,8 @@ class ImageDownloader {
}
configs = newConfig;
retryLimit--;
}
finally {
if(onLoadFailed != null) {
} finally {
if (onLoadFailed != null) {
(configs['onLoadFailed'] as JSInvokable).free();
}
}

View File

@@ -27,8 +27,10 @@ class _DownloadingPageState extends State<DownloadingPage> {
}
void update() {
if(mounted) {
setState(() {});
}
}
@override
Widget build(BuildContext context) {

View File

@@ -258,6 +258,7 @@ class _HistoryState extends State<_History> {
ImageProvider imageProvider = CachedImageProvider(
cover,
sourceKey: history[index].type.comicSource?.key,
cid: history[index].id,
);
if (!cover.isURL) {
var localComic = LocalManager().find(
@@ -567,7 +568,7 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
onPressed: () {
showDialog(
context: context,
barrierColor: Colors.transparent,
barrierColor: Colors.black.withOpacity(0.2),
builder: (context) {
var help = '';
help +=

View File

@@ -100,7 +100,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
actions: [
FilledButton(
onPressed: () {
appdata.implicitData["local_sort"] =sortType.value;
appdata.implicitData["local_sort"] = sortType.value;
appdata.writeImplicitData();
Navigator.pop(context);
update();
@@ -116,19 +116,18 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
@override
Widget build(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;
final bool isScreenSmall = screenWidth < 500.0;
void selectAll(){
void selectAll() {
setState(() {
selectedComics = comics.asMap().map((k, v) => MapEntry(v, true));
});
}
void deSelect() {
setState(() {
selectedComics.clear();
});
}
void invertSelection() {
setState(() {
comics.asMap().forEach((k, v) {
@@ -137,86 +136,46 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
selectedComics.removeWhere((k, v) => !v);
});
}
void selectRange() {
setState(() {
List<int> l = [];
selectedComics.forEach((k, v) {
l.add(comics.indexOf(k as LocalComic));
});
if(l.isEmpty) {
if (l.isEmpty) {
return;
}
l.sort();
int start = l.first;
int end = l.last;
selectedComics.clear();
selectedComics.addEntries(
List.generate(end - start + 1, (i) {
selectedComics.addEntries(List.generate(end - start + 1, (i) {
return MapEntry(comics[start + i], true);
})
);
}));
});
}
List<Widget> selectActions = [];
if(isScreenSmall) {
selectActions.add(
List<Widget> selectActions = [
IconButton(
onPressed: () {
showMenu(
context: context,
position: RelativeRect.fromLTRB(screenWidth, App.isMobile ? 64 : 96, 0, 0),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: selectAll,
child: Text("Select All".tl),
),
PopupMenuItem(
onTap: deSelect,
child: Text("Deselect".tl),
),
PopupMenuItem(
onTap: invertSelection,
child: Text("Invert Selection".tl),
),
PopupMenuItem(
onTap: selectRange,
child: Text("Select in range".tl),
)
]
);
},
icon: const Icon(
Icons.list
))
);
}else {
selectActions = [
IconButton(
icon: const Icon(Icons.check_box_rounded),
icon: const Icon(Icons.select_all),
tooltip: "Select All".tl,
onPressed: selectAll
),
onPressed: selectAll),
IconButton(
icon: const Icon(Icons.check_box_outline_blank_outlined),
icon: const Icon(Icons.deselect),
tooltip: "Deselect".tl,
onPressed: deSelect
),
onPressed: deSelect),
IconButton(
icon: const Icon(Icons.check_box_outlined),
icon: const Icon(Icons.flip),
tooltip: "Invert Selection".tl,
onPressed: invertSelection
),
onPressed: invertSelection),
IconButton(
icon: const Icon(Icons.indeterminate_check_box_rounded),
icon: const Icon(Icons.border_horizontal_outlined),
tooltip: "Select in range".tl,
onPressed: selectRange
),
onPressed: selectRange),
];
}
return Scaffold(
var body = Scaffold(
body: SmoothCustomScrollView(
slivers: [
if (!searchMode && !multiSelectMode)
@@ -267,12 +226,10 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
)
else if (multiSelectMode)
SliverAppbar(
title: Text("Selected @c comics".tlParams({"c": selectedComics.length})),
actions: [
...selectActions,
IconButton(
leading: Tooltip(
message: "Cancel".tl,
child: IconButton(
icon: const Icon(Icons.close),
tooltip: "Exit Multi-Select".tl,
onPressed: () {
setState(() {
multiSelectMode = false;
@@ -280,11 +237,26 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
});
},
),
],
),
title: Text(
"Selected @c comics".tlParams({"c": selectedComics.length})),
actions: selectActions,
)
else if (searchMode)
SliverAppbar(
leading: Tooltip(
message: "Cancel".tl,
child: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
setState(() {
searchMode = false;
keyword = "";
update();
});
},
),
),
title: TextField(
autofocus: true,
decoration: InputDecoration(
@@ -296,18 +268,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
update();
},
),
actions: [
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
setState(() {
searchMode = false;
keyword = "";
update();
});
},
),
],
),
SliverGridComics(
comics: comics,
@@ -335,50 +295,51 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
context: context,
builder: (context) {
bool removeComicFile = true;
return StatefulBuilder(
builder: (context, state) {
return StatefulBuilder(builder: (context, state) {
return ContentDialog(
title: "Delete".tl,
content: Column(
children: [
Text("Delete selected comics?".tl).paddingVertical(8),
Text("Delete selected comics?".tl)
.paddingVertical(8),
Transform.scale(
scale: 0.9,
child: CheckboxListTile(
title: Text("Also remove files on disk".tl),
title: Text(
"Also remove files on disk".tl),
value: removeComicFile,
onChanged: (v) {
state(() {
removeComicFile = !removeComicFile;
removeComicFile =
!removeComicFile;
});
}
)
),
})),
],
).paddingHorizontal(16).paddingVertical(8),
actions: [
FilledButton(
onPressed: () {
context.pop();
if(multiSelectMode) {
if (multiSelectMode) {
for (var comic in selectedComics.keys) {
LocalManager().deleteComic(comic as LocalComic, removeComicFile);
LocalManager().deleteComic(
comic as LocalComic,
removeComicFile);
}
setState(() {
selectedComics.clear();
});
} else {
LocalManager().deleteComic(c as LocalComic, removeComicFile);
LocalManager().deleteComic(
c as LocalComic, removeComicFile);
}
},
child: Text("Confirm".tl),
),
],
);
}
);
}
);
});
});
}),
MenuEntry(
icon: Icons.outbox_outlined,
@@ -414,5 +375,24 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
],
),
);
return PopScope(
canPop: !multiSelectMode && !searchMode,
onPopInvokedWithResult: (didPop, result) {
if(multiSelectMode) {
setState(() {
multiSelectMode = false;
selectedComics.clear();
});
} else if(searchMode) {
setState(() {
searchMode = false;
keyword = "";
update();
});
}
},
child: body,
);
}
}

View File

@@ -16,12 +16,12 @@ class _AboutSettingsState extends State<AboutSettings> {
slivers: [
SliverAppbar(title: Text("About".tl)),
SizedBox(
height: 136,
height: 112,
width: double.infinity,
child: Center(
child: Container(
width: 136,
height: 136,
width: 112,
height: 112,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(136),
),

View File

@@ -10,7 +10,6 @@ import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.dart';
import 'package:venera/foundation/cache_manager.dart';
import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/consts.dart';
import 'package:venera/foundation/favorites.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/log.dart';
@@ -44,7 +43,7 @@ class _SettingsPageState extends State<SettingsPage> implements PopEntry {
ColorScheme get colors => Theme.of(context).colorScheme;
bool get enableTwoViews => context.width > changePoint;
bool get enableTwoViews => context.width > 720;
final categories = <String>[
"Explore",

View File

@@ -18,11 +18,11 @@ export 'package:flutter_inappwebview/flutter_inappwebview.dart'
extension WebviewExtension on InAppWebViewController {
Future<List<io.Cookie>?> getCookies(String url) async {
if(url.contains("https://")){
if (url.contains("https://")) {
url.replaceAll("https://", "");
}
if(url[url.length-1] == '/'){
url = url.substring(0, url.length-1);
if (url[url.length - 1] == '/') {
url = url.substring(0, url.length - 1);
}
CookieManager cookieManager = CookieManager.instance();
final cookies = await cookieManager.getCookies(url: WebUri(url));
@@ -89,29 +89,29 @@ class _AppWebviewState extends State<AppWebview> {
child: IconButton(
icon: const Icon(Icons.more_horiz),
onPressed: () {
showMenu(
context: context,
position: RelativeRect.fromLTRB(
MediaQuery.of(context).size.width,
0,
MediaQuery.of(context).size.width,
0),
items: [
PopupMenuItem(
child: Text("Open in browser".tl),
onTap: () async =>
showMenuX(
context,
Offset(context.width, context.padding.top),
[
MenuEntry(
icon: Icons.open_in_browser,
text: "Open in browser".tl,
onClick: () async =>
launchUrlString((await controller?.getUrl())!.toString()),
),
PopupMenuItem(
child: Text("Copy link".tl),
onTap: () async => Clipboard.setData(ClipboardData(
MenuEntry(
icon: Icons.copy,
text: "Copy link".tl,
onClick: () async => Clipboard.setData(ClipboardData(
text: (await controller?.getUrl())!.toString())),
),
PopupMenuItem(
child: Text("Reload".tl),
onTap: () => controller?.reload(),
MenuEntry(
icon: Icons.refresh,
text: "Reload".tl,
onClick: () => controller?.reload(),
),
]);
],
);
},
),
)