mirror of
https://github.com/wgh136/pixes.git
synced 2025-09-27 12:57:24 +00:00
download and downloading page
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'foundation/app.dart';
|
||||
import 'network/models.dart';
|
||||
|
||||
@@ -9,9 +12,18 @@ class _Appdata {
|
||||
|
||||
var searchOptions = SearchOptions();
|
||||
|
||||
Map<String, dynamic> settings = {
|
||||
"downloadPath": null,
|
||||
"downloadSubPath": r"/${id}-p${index}.${ext}",
|
||||
"tagsWeight": "",
|
||||
"useTranslatedNameForDownload": false,
|
||||
};
|
||||
|
||||
void writeData() async {
|
||||
await File("${App.dataPath}/account.json")
|
||||
.writeAsString(jsonEncode(account));
|
||||
await File("${App.dataPath}/settings.json")
|
||||
.writeAsString(jsonEncode(settings));
|
||||
}
|
||||
|
||||
Future<void> readData() async {
|
||||
@@ -19,6 +31,39 @@ class _Appdata {
|
||||
if (file.existsSync()) {
|
||||
account = Account.fromJson(jsonDecode(await file.readAsString()));
|
||||
}
|
||||
final settingsFile = File("${App.dataPath}/settings.json");
|
||||
if (settingsFile.existsSync()) {
|
||||
var json = jsonDecode(await settingsFile.readAsString());
|
||||
for(var key in json.keys) {
|
||||
settings[key] = json[key];
|
||||
}
|
||||
}
|
||||
if(settings["downloadPath"] == null) {
|
||||
settings["downloadPath"] = await _defaultDownloadPath;
|
||||
}
|
||||
}
|
||||
|
||||
String get downloadPath => settings["downloadPath"];
|
||||
|
||||
Future<String> get _defaultDownloadPath async{
|
||||
if(App.isAndroid) {
|
||||
var externalStoragePaths = await getExternalStorageDirectories(type: StorageDirectory.downloads);
|
||||
var res = externalStoragePaths?.first.path;
|
||||
res ??= (await getExternalStorageDirectory())!.path;
|
||||
return "$res/pixes";
|
||||
} else if (App.isWindows){
|
||||
var res = await const MethodChannel("pixes/picture_folder").invokeMethod("");
|
||||
if(res != "error") {
|
||||
return res + "/pixes";
|
||||
}
|
||||
} else if (App.isMacOS || App.isLinux) {
|
||||
var downloadPath = (await getDownloadsDirectory())?.path;
|
||||
if(downloadPath != null) {
|
||||
return "$downloadPath/pixes";
|
||||
}
|
||||
}
|
||||
|
||||
return "${App.dataPath}/download";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -150,12 +150,16 @@ class CachedImageProvider extends BaseImageProvider<CachedImageProvider> {
|
||||
Future<Uint8List> load(StreamController<ImageChunkEvent> chunkEvents) async{
|
||||
var cached = await CacheManager().findCache(key);
|
||||
if(cached != null) {
|
||||
chunkEvents.add(const ImageChunkEvent(
|
||||
cumulativeBytesLoaded: 1,
|
||||
expectedTotalBytes: 1,
|
||||
));
|
||||
return await File(cached).readAsBytes();
|
||||
}
|
||||
var dio = AppDio();
|
||||
final time = DateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'").format(DateTime.now());
|
||||
final hash = md5.convert(utf8.encode(time + Network.hashSalt)).toString();
|
||||
var res = await dio.get(
|
||||
var res = await dio.get<ResponseBody>(
|
||||
url,
|
||||
options: Options(
|
||||
responseType: ResponseType.stream,
|
||||
@@ -174,12 +178,16 @@ class CachedImageProvider extends BaseImageProvider<CachedImageProvider> {
|
||||
}
|
||||
var data = <int>[];
|
||||
var cachingFile = await CacheManager().openWrite(key);
|
||||
await for (var chunk in res.data.stream) {
|
||||
await for (var chunk in res.data!.stream) {
|
||||
var length = res.data!.contentLength+1;
|
||||
if(length < data.length) {
|
||||
length = data.length + 1;
|
||||
}
|
||||
data.addAll(chunk);
|
||||
await cachingFile.writeBytes(chunk);
|
||||
chunkEvents.add(ImageChunkEvent(
|
||||
cumulativeBytesLoaded: data.length,
|
||||
expectedTotalBytes: res.data.contentLength+1,
|
||||
expectedTotalBytes: length,
|
||||
));
|
||||
}
|
||||
await cachingFile.close();
|
||||
|
@@ -56,4 +56,12 @@ extension WidgetExtension on Widget{
|
||||
Widget sliverPaddingHorizontal(double padding){
|
||||
return SliverPadding(padding: EdgeInsets.symmetric(horizontal: padding), sliver: this);
|
||||
}
|
||||
|
||||
Widget fixWidth(double width){
|
||||
return SizedBox(width: width, child: this);
|
||||
}
|
||||
|
||||
Widget fixHeight(double height){
|
||||
return SizedBox(height: height, child: this);
|
||||
}
|
||||
}
|
@@ -1,5 +1,268 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pixes/appdata.dart';
|
||||
import 'package:pixes/foundation/app.dart';
|
||||
import 'package:pixes/foundation/log.dart';
|
||||
import 'package:pixes/network/app_dio.dart';
|
||||
import 'package:pixes/network/network.dart';
|
||||
import 'package:pixes/utils/io.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
extension IllustExt on Illust {
|
||||
bool get downloaded => false;
|
||||
bool get downloaded => DownloadManager().checkDownloaded(id);
|
||||
|
||||
bool get downloading =>
|
||||
DownloadManager().tasks.any((element) => element.illust.id == id);
|
||||
}
|
||||
|
||||
class DownloadedIllust {
|
||||
final int illustId;
|
||||
final String title;
|
||||
final String author;
|
||||
final int imageCount;
|
||||
|
||||
DownloadedIllust({
|
||||
required this.illustId,
|
||||
required this.title,
|
||||
required this.author,
|
||||
required this.imageCount,
|
||||
});
|
||||
}
|
||||
|
||||
class DownloadingTask {
|
||||
final Illust illust;
|
||||
|
||||
void Function(int)? receiveBytesCallback;
|
||||
|
||||
void Function(DownloadingTask)? onCompleted;
|
||||
|
||||
DownloadingTask(this.illust, {this.receiveBytesCallback, this.onCompleted});
|
||||
|
||||
int _downloadingIndex = 0;
|
||||
|
||||
int get totalImages => illust.images.length;
|
||||
|
||||
int get downloadedImages => _downloadingIndex;
|
||||
|
||||
bool _stop = true;
|
||||
|
||||
String? error;
|
||||
|
||||
void start() {
|
||||
_stop = false;
|
||||
_download();
|
||||
}
|
||||
|
||||
Dio get dio => Network().dio;
|
||||
|
||||
void cancel() {
|
||||
_stop = true;
|
||||
DownloadManager().tasks.remove(this);
|
||||
for(var path in imagePaths) {
|
||||
File(path).deleteIfExists();
|
||||
}
|
||||
}
|
||||
|
||||
List<String> imagePaths = [];
|
||||
|
||||
void _download() async{
|
||||
try{
|
||||
while(_downloadingIndex < illust.images.length) {
|
||||
if(_stop) return;
|
||||
var url = illust.images[_downloadingIndex].original;
|
||||
var ext = url.split('.').last;
|
||||
if(!["jpg", "png", "gif", "webp", "jpeg", "avif"].contains(ext)) {
|
||||
ext = "jpg";
|
||||
}
|
||||
var path = _generateFilePath(illust, _downloadingIndex, ext);
|
||||
final time = DateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'").format(DateTime.now());
|
||||
final hash = md5.convert(utf8.encode(time + Network.hashSalt)).toString();
|
||||
var res = await dio.get<ResponseBody>(url, options: Options(
|
||||
responseType: ResponseType.stream,
|
||||
headers: {
|
||||
"referer": "https://app-api.pixiv.net/",
|
||||
"user-agent": "PixivAndroidApp/5.0.234 (Android 14; Pixes)",
|
||||
"x-client-time": time,
|
||||
"x-client-hash": hash,
|
||||
"accept-enconding": "gzip",
|
||||
},
|
||||
));
|
||||
var file = File(path);
|
||||
if(!file.existsSync()) {
|
||||
file.createSync(recursive: true);
|
||||
}
|
||||
await for (var data in res.data!.stream) {
|
||||
await file.writeAsBytes(data, mode: FileMode.append);
|
||||
receiveBytesCallback?.call(data.length);
|
||||
}
|
||||
imagePaths.add(path);
|
||||
_downloadingIndex++;
|
||||
}
|
||||
onCompleted?.call(this);
|
||||
}
|
||||
catch(e, s) {
|
||||
error = e.toString();
|
||||
_stop = true;
|
||||
Log.error("Download", "Download error: $e\n$s");
|
||||
}
|
||||
}
|
||||
|
||||
static String _generateFilePath(Illust illust, int index, String ext) {
|
||||
final String downloadPath = appdata.settings["downloadPath"];
|
||||
final String subPathPatten = appdata.settings["downloadSubPath"];
|
||||
final tags = appdata.settings["useTranslatedNameForDownload"] == false
|
||||
? illust.tags.map((e) => e.name).toList()
|
||||
: illust.tags.map((e) => e.translatedName ?? e.name).toList();
|
||||
final tagsWeight = (appdata.settings["tagsWeight"] as String).split(' ');
|
||||
tags.sort((a, b) => tagsWeight.indexOf(a) - tagsWeight.indexOf(b));
|
||||
subPathPatten.replaceAll(r"${id}", illust.id.toString());
|
||||
subPathPatten.replaceAll(r"${title}", illust.title);
|
||||
subPathPatten.replaceAll(r"${author}", illust.author.name);
|
||||
subPathPatten.replaceAll(r"${ext}", ext);
|
||||
for(int i=0; i<tags.length; i++) {
|
||||
subPathPatten.replaceAll("\${tag$i}", tags[i]);
|
||||
}
|
||||
return "$downloadPath$subPathPatten";
|
||||
}
|
||||
|
||||
void retry() {
|
||||
error = null;
|
||||
_stop = false;
|
||||
_download();
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadManager {
|
||||
factory DownloadManager() => instance ??= DownloadManager._();
|
||||
|
||||
static DownloadManager? instance;
|
||||
|
||||
DownloadManager._(){
|
||||
init();
|
||||
}
|
||||
|
||||
late Database _db;
|
||||
|
||||
int _currentBytes = 0;
|
||||
int _bytesPerSecond = 0;
|
||||
|
||||
int get bytesPerSecond => _bytesPerSecond;
|
||||
|
||||
Timer? _loop;
|
||||
|
||||
var tasks = <DownloadingTask>[];
|
||||
|
||||
void Function()? uiUpdateCallback;
|
||||
|
||||
void registerUiUpdater(void Function() callback) {
|
||||
uiUpdateCallback = callback;
|
||||
}
|
||||
|
||||
void removeUiUpdater() {
|
||||
uiUpdateCallback = null;
|
||||
}
|
||||
|
||||
void init() {
|
||||
_db = sqlite3.open("${App.dataPath}/download.db");
|
||||
_db.execute('''
|
||||
create table if not exists download (
|
||||
illust_id integer primary key not null,
|
||||
title text not null,
|
||||
author text not null,
|
||||
imageCount int not null
|
||||
);
|
||||
''');
|
||||
_db.execute('''
|
||||
create table if not exists images (
|
||||
illust_id integer not null,
|
||||
image_index integer not null,
|
||||
path text not null,
|
||||
primary key (illust_id, image_index)
|
||||
);
|
||||
''');
|
||||
}
|
||||
|
||||
void saveInfo(Illust illust, List<String> imagePaths) {
|
||||
_db.execute('''
|
||||
insert into download (illust_id, title, author, imageCount)
|
||||
values (?, ?, ?, ?)
|
||||
''', [illust.id, illust.title, illust.author.name, imagePaths.length]);
|
||||
for (var i = 0; i < imagePaths.length; i++) {
|
||||
_db.execute('''
|
||||
insert into images (illust_id, image_index, path)
|
||||
values (?, ?, ?)
|
||||
''', [illust.id, i, imagePaths[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
File? getImage(int illustId, int index) {
|
||||
var res = _db.select('''
|
||||
select * from images
|
||||
where illust_id = ? and image_index = ?;
|
||||
''');
|
||||
if (res.isEmpty) return null;
|
||||
var file = File(res.first["path"] as String);
|
||||
if (!file.existsSync()) return null;
|
||||
return file;
|
||||
}
|
||||
|
||||
bool checkDownloaded(int illustId) {
|
||||
var res = _db.select('''
|
||||
select * from download
|
||||
where illust_id = ?;
|
||||
''', [illustId]);
|
||||
return res.isNotEmpty;
|
||||
}
|
||||
|
||||
List<DownloadedIllust> listAll() {
|
||||
var res = _db.select('''
|
||||
select * from download;
|
||||
''');
|
||||
return res.map((e) =>
|
||||
DownloadedIllust(
|
||||
illustId: e["illust_id"] as int,
|
||||
title: e["title"] as String,
|
||||
author: e["author"] as String,
|
||||
imageCount: e["imageCount"] as int,
|
||||
)).toList();
|
||||
}
|
||||
|
||||
void addDownloadingTask(Illust illust) {
|
||||
var task = DownloadingTask(illust, receiveBytesCallback: receiveBytes, onCompleted: (task) {
|
||||
saveInfo(illust, task.imagePaths);
|
||||
tasks.remove(task);
|
||||
});
|
||||
tasks.add(task);
|
||||
run();
|
||||
}
|
||||
|
||||
void receiveBytes(int bytes) {
|
||||
_currentBytes += bytes;
|
||||
}
|
||||
|
||||
int get maxConcurrentTasks => 3;
|
||||
|
||||
void run() {
|
||||
_loop ??= Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_bytesPerSecond = _currentBytes;
|
||||
_currentBytes = 0;
|
||||
uiUpdateCallback?.call();
|
||||
for(int i=0; i<maxConcurrentTasks; i++) {
|
||||
var task = tasks.elementAtOrNull(i);
|
||||
if(task != null && task._stop && task.error == null) {
|
||||
task.start();
|
||||
}
|
||||
}
|
||||
if(tasks.isEmpty) {
|
||||
timer.cancel();
|
||||
_loop = null;
|
||||
_currentBytes = 0;
|
||||
_bytesPerSecond = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -39,7 +39,7 @@ class Network {
|
||||
|
||||
final dio = AppDio();
|
||||
|
||||
Map<String, String> get _headers {
|
||||
Map<String, String> get headers {
|
||||
final time =
|
||||
DateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'").format(DateTime.now());
|
||||
final hash = md5.convert(utf8.encode(time + hashSalt)).toString();
|
||||
@@ -80,7 +80,7 @@ class Network {
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: _headers));
|
||||
headers: headers));
|
||||
if (res.statusCode != 200) {
|
||||
throw "Invalid Status code ${res.statusCode}";
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class Network {
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: _headers));
|
||||
headers: headers));
|
||||
var account = Account.fromJson(json.decode(res.data!));
|
||||
appdata.account = account;
|
||||
appdata.writeData();
|
||||
@@ -126,7 +126,7 @@ class Network {
|
||||
final res = await dio.get<Map<String, dynamic>>(path,
|
||||
queryParameters: query,
|
||||
options:
|
||||
Options(headers: _headers, validateStatus: (status) => true));
|
||||
Options(headers: headers, validateStatus: (status) => true));
|
||||
if (res.statusCode == 200) {
|
||||
return Res(res.data!);
|
||||
} else if (res.statusCode == 400) {
|
||||
@@ -162,7 +162,7 @@ class Network {
|
||||
queryParameters: query,
|
||||
data: data,
|
||||
options: Options(
|
||||
headers: _headers,
|
||||
headers: headers,
|
||||
validateStatus: (status) => true,
|
||||
contentType: Headers.formUrlEncodedContentType));
|
||||
if (res.statusCode == 200) {
|
||||
|
@@ -1,15 +0,0 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
class DownloadPage extends StatefulWidget {
|
||||
const DownloadPage({super.key});
|
||||
|
||||
@override
|
||||
State<DownloadPage> createState() => _DownloadPageState();
|
||||
}
|
||||
|
||||
class _DownloadPageState extends State<DownloadPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
15
lib/pages/downloaded_page.dart
Normal file
15
lib/pages/downloaded_page.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
|
||||
class DownloadedPage extends StatefulWidget {
|
||||
const DownloadedPage({super.key});
|
||||
|
||||
@override
|
||||
State<DownloadedPage> createState() => _DownloadedPageState();
|
||||
}
|
||||
|
||||
class _DownloadedPageState extends State<DownloadedPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
166
lib/pages/downloading_page.dart
Normal file
166
lib/pages/downloading_page.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:pixes/components/md.dart';
|
||||
import 'package:pixes/foundation/app.dart';
|
||||
import 'package:pixes/foundation/image_provider.dart';
|
||||
import 'package:pixes/network/download.dart';
|
||||
import 'package:pixes/utils/translation.dart';
|
||||
|
||||
import '../utils/io.dart';
|
||||
|
||||
class DownloadingPage extends StatefulWidget {
|
||||
const DownloadingPage({super.key});
|
||||
|
||||
@override
|
||||
State<DownloadingPage> createState() => _DownloadingPageState();
|
||||
}
|
||||
|
||||
class _DownloadingPageState extends State<DownloadingPage> {
|
||||
@override
|
||||
void initState() {
|
||||
DownloadManager().registerUiUpdater(() => setState((){}));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
DownloadManager().removeUiUpdater();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Map<String, FlyoutController> controller = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScaffoldPage(
|
||||
content: CustomScrollView(
|
||||
slivers: [
|
||||
buildTop(),
|
||||
const SliverPadding(padding: EdgeInsets.only(top: 16)),
|
||||
buildContent()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTop() {
|
||||
int bytesPerSecond = DownloadManager().bytesPerSecond;
|
||||
|
||||
return SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text("${"Speed".tl}: ${bytesToText(bytesPerSecond)}/s",
|
||||
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildContent() {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
var task = DownloadManager().tasks[index];
|
||||
return buildItem(task);
|
||||
},
|
||||
childCount: DownloadManager().tasks.length
|
||||
),
|
||||
).sliverPaddingHorizontal(12);
|
||||
}
|
||||
|
||||
Widget buildItem(DownloadingTask task) {
|
||||
controller[task.illust.id.toString()] ??= FlyoutController();
|
||||
|
||||
return Card(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: SizedBox(
|
||||
height: 96,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
height: double.infinity,
|
||||
width: 72,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: ColorScheme.of(context).outlineVariant, width: 0.6),
|
||||
),
|
||||
child: Image(
|
||||
image: CachedImageProvider(task.illust.images.first.medium),
|
||||
fit: BoxFit.cover,
|
||||
filterQuality: FilterQuality.medium,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(task.illust.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
Text(task.illust.author.name, style: const TextStyle(fontSize: 12, color: Colors.grey)),
|
||||
const Spacer(),
|
||||
if(task.error == null)
|
||||
Text("${task.downloadedImages}/${task.totalImages} ${"Downloaded".tl}", style: const TextStyle(fontSize: 12, color: Colors.grey))
|
||||
else
|
||||
Text("Error: ${task.error!.replaceAll("\n", " ")}", style: TextStyle(fontSize: 12, color: ColorScheme.of(context).error), maxLines: 2,),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if(task.error != null)
|
||||
Button(
|
||||
child: Text("Retry".tl).fixWidth(46),
|
||||
onPressed: () {
|
||||
task.retry();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
FlyoutTarget(
|
||||
controller: controller[task.illust.id.toString()]!,
|
||||
child: Button(
|
||||
child: Text("Cancel".tl, style: TextStyle(color: ColorScheme.of(context).error),).fixWidth(46),
|
||||
onPressed: (){
|
||||
controller[task.illust.id.toString()]!.showFlyout(
|
||||
navigatorKey: App.rootNavigatorKey.currentState,
|
||||
builder: (context) {
|
||||
return FlyoutContent(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Are you sure you want to cancel this download?'.tl,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 12.0),
|
||||
Button(
|
||||
onPressed: () {
|
||||
Flyout.of(context).close();
|
||||
task.cancel();
|
||||
setState(() {});
|
||||
},
|
||||
child: Text('Yes'.tl),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:flutter/material.dart' show Icons;
|
||||
import 'package:pixes/components/animated_image.dart';
|
||||
@@ -69,6 +71,11 @@ class _IllustPageState extends State<IllustPage> {
|
||||
}
|
||||
|
||||
Widget buildImage(double width, double height, int index) {
|
||||
File? downloadFile;
|
||||
if(widget.illust.downloaded) {
|
||||
downloadFile = DownloadManager().getImage(widget.illust.id, index);
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
return Text(
|
||||
widget.illust.title,
|
||||
@@ -93,9 +100,13 @@ class _IllustPageState extends State<IllustPage> {
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
child: GestureDetector(
|
||||
onTap: () => ImagePage.show(widget.illust.images[index].original),
|
||||
onTap: () => ImagePage.show(downloadFile == null
|
||||
? widget.illust.images[index].original
|
||||
: "file://${downloadFile.path}"),
|
||||
child: Image(
|
||||
image: CachedImageProvider(widget.illust.images[index].large),
|
||||
image: downloadFile == null
|
||||
? CachedImageProvider(widget.illust.images[index].large) as ImageProvider
|
||||
: FileImage(downloadFile) as ImageProvider,
|
||||
width: imageWidth,
|
||||
fit: BoxFit.cover,
|
||||
height: imageHeight,
|
||||
@@ -123,14 +134,9 @@ class _IllustPageState extends State<IllustPage> {
|
||||
),
|
||||
);
|
||||
|
||||
if (index == 0) {
|
||||
return Hero(
|
||||
tag: "illust_${widget.illust.id}",
|
||||
child: image,
|
||||
);
|
||||
} else {
|
||||
return image;
|
||||
}
|
||||
return Center(
|
||||
child: image,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +380,10 @@ class _BottomBarState extends State<_BottomBar> {
|
||||
});
|
||||
}
|
||||
|
||||
void download() {}
|
||||
void download() {
|
||||
DownloadManager().addDownloadingTask(widget.illust);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
bool showText = width > 640;
|
||||
|
||||
@@ -416,24 +425,47 @@ class _BottomBarState extends State<_BottomBar> {
|
||||
yield const SizedBox(width: 8,);
|
||||
|
||||
if (!widget.illust.downloaded) {
|
||||
yield Button(
|
||||
onPressed: download,
|
||||
child: SizedBox(
|
||||
height: 28,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
FluentIcons.download,
|
||||
size: 18,
|
||||
),
|
||||
if(showText)
|
||||
const SizedBox(width: 8,),
|
||||
if(showText)
|
||||
Text("Download".tl),
|
||||
],
|
||||
if(widget.illust.downloading) {
|
||||
yield Button(
|
||||
onPressed: () => {},
|
||||
child: SizedBox(
|
||||
height: 28,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
FluentIcons.download,
|
||||
color: ColorScheme.of(context).outline,
|
||||
size: 18,
|
||||
),
|
||||
if(showText)
|
||||
const SizedBox(width: 8,),
|
||||
if(showText)
|
||||
Text("Downloading".tl,
|
||||
style: TextStyle(color: ColorScheme.of(context).outline),),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else {
|
||||
yield Button(
|
||||
onPressed: download,
|
||||
child: SizedBox(
|
||||
height: 28,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
FluentIcons.download,
|
||||
size: 18,
|
||||
),
|
||||
if(showText)
|
||||
const SizedBox(width: 8,),
|
||||
if(showText)
|
||||
Text("Download".tl),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
yield const SizedBox(width: 8,);
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:pixes/components/page_route.dart';
|
||||
@@ -60,7 +62,9 @@ class _ImagePageState extends State<ImagePage> with WindowListener{
|
||||
color: Colors.transparent
|
||||
),
|
||||
filterQuality: FilterQuality.medium,
|
||||
imageProvider: CachedImageProvider(widget.url),
|
||||
imageProvider: widget.url.startsWith("file://")
|
||||
? FileImage(File(widget.url.replaceFirst("file://", "")))
|
||||
: CachedImageProvider(widget.url) as ImageProvider,
|
||||
)),
|
||||
Positioned(
|
||||
top: 0,
|
||||
|
@@ -8,6 +8,7 @@ import "package:pixes/components/md.dart";
|
||||
import "package:pixes/foundation/app.dart";
|
||||
import "package:pixes/network/network.dart";
|
||||
import "package:pixes/pages/bookmarks.dart";
|
||||
import "package:pixes/pages/downloaded_page.dart";
|
||||
import "package:pixes/pages/following_artworks.dart";
|
||||
import "package:pixes/pages/ranking.dart";
|
||||
import "package:pixes/pages/recommendation_page.dart";
|
||||
@@ -20,7 +21,7 @@ import "package:pixes/utils/translation.dart";
|
||||
import "package:window_manager/window_manager.dart";
|
||||
|
||||
import "../components/page_route.dart";
|
||||
import "download_page.dart";
|
||||
import "downloading_page.dart";
|
||||
|
||||
const _kAppBarHeight = 36.0;
|
||||
|
||||
@@ -34,7 +35,7 @@ class MainPage extends StatefulWidget {
|
||||
class _MainPageState extends State<MainPage> with WindowListener {
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
int index = 3;
|
||||
int index = 4;
|
||||
|
||||
int windowButtonKey = 0;
|
||||
|
||||
@@ -101,9 +102,14 @@ class _MainPageState extends State<MainPage> with WindowListener {
|
||||
title: Text('Search'.tl),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
PaneItem(
|
||||
icon: const Icon(MdIcons.downloading, size: 20,),
|
||||
title: Text('Downloading'.tl),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
PaneItem(
|
||||
icon: const Icon(MdIcons.download, size: 20,),
|
||||
title: Text('Download'.tl),
|
||||
title: Text('Downloaded'.tl),
|
||||
body: const SizedBox.shrink(),
|
||||
),
|
||||
PaneItemSeparator(),
|
||||
@@ -148,7 +154,8 @@ class _MainPageState extends State<MainPage> with WindowListener {
|
||||
static final pageBuilders = <Widget Function()>[
|
||||
() => UserInfoPage(appdata.account!.user.id),
|
||||
() => const SearchPage(),
|
||||
() => const DownloadPage(),
|
||||
() => const DownloadingPage(),
|
||||
() => const DownloadedPage(),
|
||||
() => const RecommendationPage(),
|
||||
() => const BookMarkedArtworkPage(),
|
||||
() => const FollowingArtworksPage(),
|
||||
|
@@ -19,4 +19,16 @@ extension FSExt on FileSystemEntity {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
String bytesToText(int bytes) {
|
||||
if(bytes < 1024) {
|
||||
return "$bytes B";
|
||||
} else if(bytes < 1024 * 1024) {
|
||||
return "${(bytes / 1024).toStringAsFixed(2)} KB";
|
||||
} else if(bytes < 1024 * 1024 * 1024) {
|
||||
return "${(bytes / 1024 / 1024).toStringAsFixed(2)} MB";
|
||||
} else {
|
||||
return "${(bytes / 1024 / 1024 / 1024).toStringAsFixed(2)} GB";
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user