Files
pixes/lib/pages/image_page.dart
2024-05-31 17:44:50 +08:00

286 lines
9.0 KiB
Dart

import 'dart:io';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:pixes/components/md.dart';
import 'package:pixes/components/page_route.dart';
import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/cache_manager.dart';
import 'package:pixes/foundation/image_provider.dart';
import 'package:pixes/pages/main_page.dart';
import 'package:pixes/utils/io.dart';
import 'package:pixes/utils/translation.dart';
import 'package:share_plus/share_plus.dart';
import 'package:window_manager/window_manager.dart';
class ImagePage extends StatefulWidget {
const ImagePage(this.urls, {this.initialPage = 0, super.key});
final List<String> urls;
final int initialPage;
static show(List<String> urls, {int initialPage = 0}) {
App.rootNavigatorKey.currentState?.push(AppPageRoute(
builder: (context) => ImagePage(urls, initialPage: initialPage)));
}
@override
State<ImagePage> createState() => _ImagePageState();
}
class _ImagePageState extends State<ImagePage> with WindowListener {
int windowButtonKey = 0;
@override
void initState() {
windowManager.addListener(this);
super.initState();
}
@override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}
@override
void onWindowMaximize() {
setState(() {
windowButtonKey++;
});
}
@override
void onWindowUnmaximize() {
setState(() {
windowButtonKey++;
});
}
late var controller = PageController(initialPage: widget.initialPage);
late int currentPage = widget.initialPage;
var menuController = FlyoutController();
Future<File?> getFile() async {
var image = widget.urls[currentPage];
if (image.startsWith("file://")) {
return File(image.replaceFirst("file://", ""));
}
var key = image;
if (key.startsWith("novel:")) {
key = key.split(':').last;
}
var file = await CacheManager().findCache(key);
return file == null ? null : File(file);
}
String getExtensionName() {
var fileName = widget.urls[currentPage].split('/').last;
if (fileName.contains('.')) {
return '.${fileName.split('.').last}';
}
return '.jpg';
}
void showMenu() {
menuController.showFlyout(
builder: (context) => MenuFlyout(
items: [
MenuFlyoutItem(
text: Text("Save to".tl),
onPressed: () async {
var file = await getFile();
if (file != null) {
var fileName = file.path.split('/').last;
if (!fileName.contains('.')) {
fileName += getExtensionName();
}
saveFile(file, fileName);
}
}),
MenuFlyoutItem(
text: Text("Share".tl),
onPressed: () async {
var file = await getFile();
if (file != null) {
var ext = getExtensionName();
var fileName = file.path.split('/').last;
if (!fileName.contains('.')) {
fileName += ext;
}
var mediaType = switch (ext) {
'.jpg' => 'image/jpeg',
'.jpeg' => 'image/jpeg',
'.png' => 'image/png',
'.gif' => 'image/gif',
'.webp' => 'image/webp',
_ => 'application/octet-stream'
};
Share.shareXFiles([
XFile.fromData(await file.readAsBytes(),
mimeType: mediaType, name: fileName)
]);
}
}),
],
));
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
color: FluentTheme.of(context).micaBackgroundColor,
child: Listener(
onPointerSignal: (event) {
if (event is PointerScrollEvent &&
!HardwareKeyboard.instance.isControlPressed) {
if (event.scrollDelta.dy > 0 &&
controller.page!.toInt() < widget.urls.length - 1) {
controller.jumpToPage(controller.page!.toInt() + 1);
} else if (event.scrollDelta.dy < 0 &&
controller.page!.toInt() > 0) {
controller.jumpToPage(controller.page!.toInt() - 1);
}
}
},
child: LayoutBuilder(
builder: (context, constrains) {
var height = constrains.maxHeight;
return Stack(
children: [
Positioned.fill(
child: PhotoViewGallery.builder(
pageController: controller,
backgroundDecoration:
const BoxDecoration(color: Colors.transparent),
itemCount: widget.urls.length,
builder: (context, index) {
var image = widget.urls[index];
return PhotoViewGalleryPageOptions(
imageProvider: getImageProvider(image),
);
},
onPageChanged: (index) {
setState(() {
currentPage = index;
});
},
)),
Positioned(
top: 0,
left: 0,
right: 0,
child: SizedBox(
height: 36,
child: Row(
children: [
const SizedBox(
width: 6,
),
IconButton(
icon: const Icon(FluentIcons.back).paddingAll(2),
onPressed: () => context.pop()),
const Expanded(
child: DragToMoveArea(
child: SizedBox.expand(),
),
),
buildActions(),
if (App.isDesktop)
WindowButtons(
key: ValueKey(windowButtonKey),
),
],
),
),
),
Positioned(
left: 0,
top: height / 2 - 9,
child: IconButton(
icon: const Icon(
FluentIcons.chevron_left,
size: 18,
),
onPressed: () {
controller.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
).paddingAll(8),
),
Positioned(
right: 0,
top: height / 2 - 9,
child: IconButton(
icon: const Icon(FluentIcons.chevron_right, size: 18),
onPressed: () {
controller.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
},
).paddingAll(8),
),
Positioned(
left: 12,
bottom: 8,
child: Text(
"${currentPage + 1}/${widget.urls.length}",
),
)
],
);
},
),
),
);
}
Widget buildActions() {
var width = MediaQuery.of(context).size.width;
return FlyoutTarget(
controller: menuController,
child: width > 600
? Button(
onPressed: showMenu,
child: Row(
children: [
const Icon(
MdIcons.menu,
size: 18,
),
const SizedBox(
width: 8,
),
Text('Actions'.tl),
],
))
: IconButton(
icon: const Icon(
MdIcons.more_horiz,
size: 20,
),
onPressed: showMenu),
);
}
ImageProvider getImageProvider(String url) {
if (url.startsWith("file://")) {
return FileImage(File(url.replaceFirst("file://", "")));
} else if (url.startsWith("novel:")) {
var ids = url.split(':').last.split('/');
return CachedNovelImageProvider(ids[0], ids[1]);
}
return CachedImageProvider(url) as ImageProvider;
}
}