mirror of
https://github.com/wgh136/pixes.git
synced 2025-09-27 21:07:24 +00:00
local history
This commit is contained in:
@@ -165,7 +165,8 @@
|
|||||||
"Read": "阅读",
|
"Read": "阅读",
|
||||||
"Error": "错误",
|
"Error": "错误",
|
||||||
"Failed to register URL scheme.": "注册URL协议失败",
|
"Failed to register URL scheme.": "注册URL协议失败",
|
||||||
"Retry": "重试"
|
"Retry": "重试",
|
||||||
|
"Network": "网络"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Search": "搜索",
|
"Search": "搜索",
|
||||||
@@ -333,6 +334,7 @@
|
|||||||
"Read": "閱讀",
|
"Read": "閱讀",
|
||||||
"Error": "錯誤",
|
"Error": "錯誤",
|
||||||
"Failed to register URL scheme.": "註冊URL協議失敗",
|
"Failed to register URL scheme.": "註冊URL協議失敗",
|
||||||
"Retry": "重試"
|
"Retry": "重試",
|
||||||
|
"Network": "網絡"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:pixes/components/animated_image.dart';
|
import 'package:pixes/components/animated_image.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
|
import 'package:pixes/foundation/history.dart';
|
||||||
import 'package:pixes/foundation/image_provider.dart';
|
import 'package:pixes/foundation/image_provider.dart';
|
||||||
import 'package:pixes/network/download.dart';
|
import 'package:pixes/network/download.dart';
|
||||||
import 'package:pixes/utils/translation.dart';
|
import 'package:pixes/utils/translation.dart';
|
||||||
@@ -307,3 +308,162 @@ class _IllustWidgetState extends State<IllustWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IllustHistoryWidget extends StatelessWidget {
|
||||||
|
const IllustHistoryWidget(this.illust, {super.key});
|
||||||
|
|
||||||
|
final IllustHistory illust;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, constrains) {
|
||||||
|
final width = constrains.maxWidth;
|
||||||
|
final height = illust.height * width / illust.width;
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
|
||||||
|
child: Card(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
context.to(() => IllustPageWithId(illust.id.toString()));
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
child: AnimatedImage(
|
||||||
|
image: CachedImageProvider(
|
||||||
|
illust.imgPath),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: width - 16.0,
|
||||||
|
height: height - 16.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
if (illust.imageCount > 1)
|
||||||
|
Positioned(
|
||||||
|
top: 12,
|
||||||
|
left: 12,
|
||||||
|
child: Container(
|
||||||
|
width: 28,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: FluentTheme.of(context)
|
||||||
|
.micaBackgroundColor
|
||||||
|
.withOpacity(0.72),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorScheme.of(context).outlineVariant,
|
||||||
|
width: 0.6),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
"${illust.imageCount}P",
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (illust.isAi)
|
||||||
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
left: 12,
|
||||||
|
child: Container(
|
||||||
|
width: 28,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorScheme.of(context)
|
||||||
|
.errorContainer
|
||||||
|
.withOpacity(0.8),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorScheme.of(context).outlineVariant,
|
||||||
|
width: 0.6),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
"AI",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (illust.isGif)
|
||||||
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
left: 12,
|
||||||
|
child: Container(
|
||||||
|
width: 28,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorScheme.of(context)
|
||||||
|
.primaryContainer
|
||||||
|
.withOpacity(0.8),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorScheme.of(context).outlineVariant,
|
||||||
|
width: 0.6),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
"GIF",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (illust.isR18)
|
||||||
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
right: 12,
|
||||||
|
child: Container(
|
||||||
|
width: 28,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorScheme.of(context).errorContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorScheme.of(context).outlineVariant,
|
||||||
|
width: 0.6),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
"R18",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
if (illust.isR18G)
|
||||||
|
Positioned(
|
||||||
|
bottom: 12,
|
||||||
|
right: 12,
|
||||||
|
child: Container(
|
||||||
|
width: 28,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorScheme.of(context).errorContainer,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorScheme.of(context).outlineVariant,
|
||||||
|
width: 0.6),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
"R18G",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
95
lib/foundation/history.dart
Normal file
95
lib/foundation/history.dart
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import 'package:pixes/foundation/app.dart';
|
||||||
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
|
import 'package:pixes/network/models.dart';
|
||||||
|
|
||||||
|
class IllustHistory {
|
||||||
|
final int id;
|
||||||
|
final String imgPath;
|
||||||
|
final DateTime time;
|
||||||
|
final int imageCount;
|
||||||
|
final bool isR18;
|
||||||
|
final bool isR18G;
|
||||||
|
final bool isAi;
|
||||||
|
final bool isGif;
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
|
||||||
|
IllustHistory(this.id, this.imgPath, this.time, this.imageCount, this.isR18,
|
||||||
|
this.isR18G, this.isAi, this.isGif, this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HistoryManager {
|
||||||
|
static HistoryManager? instance;
|
||||||
|
|
||||||
|
factory HistoryManager() => instance ??= HistoryManager._create();
|
||||||
|
|
||||||
|
HistoryManager._create();
|
||||||
|
|
||||||
|
late Database _db;
|
||||||
|
|
||||||
|
init() {
|
||||||
|
_db = sqlite3.open("${App.dataPath}/history.db");
|
||||||
|
_db.execute('''
|
||||||
|
create table if not exists history (
|
||||||
|
id integer primary key not null,
|
||||||
|
imgPath text not null,
|
||||||
|
time integer not null,
|
||||||
|
imageCount integer not null,
|
||||||
|
isR18 integer not null,
|
||||||
|
isR18g integer not null,
|
||||||
|
isAi integer not null,
|
||||||
|
isGif integer not null,
|
||||||
|
width integer not null,
|
||||||
|
height integer not null
|
||||||
|
)
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
|
||||||
|
void addHistory(Illust illust) {
|
||||||
|
var time = DateTime.now();
|
||||||
|
_db.execute('''
|
||||||
|
insert or replace into history (id, imgPath, time, imageCount, isR18, isR18g, isAi, isGif, width, height)
|
||||||
|
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
''', [
|
||||||
|
illust.id,
|
||||||
|
illust.images.first.medium,
|
||||||
|
time.millisecondsSinceEpoch,
|
||||||
|
illust.pageCount,
|
||||||
|
illust.isR18 ? 1 : 0,
|
||||||
|
illust.isR18G ? 1 : 0,
|
||||||
|
illust.isAi ? 1 : 0,
|
||||||
|
illust.isUgoira ? 1 : 0,
|
||||||
|
illust.width,
|
||||||
|
illust.height
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IllustHistory> getHistories(int page) {
|
||||||
|
var rows = _db.select('''
|
||||||
|
select * from history
|
||||||
|
limit 20 offset ?
|
||||||
|
''', [(page - 1) * 20]);
|
||||||
|
List<IllustHistory> res = [];
|
||||||
|
for (var row in rows) {
|
||||||
|
res.add(IllustHistory(
|
||||||
|
row['id'],
|
||||||
|
row['imgPath'],
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(row['time']),
|
||||||
|
row['imageCount'],
|
||||||
|
row['isR18'] == 1,
|
||||||
|
row['isR18g'] == 1,
|
||||||
|
row['isAi'] == 1,
|
||||||
|
row['isGif'] == 1,
|
||||||
|
row['width'],
|
||||||
|
row['height']));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get length {
|
||||||
|
var rows = _db.select('''
|
||||||
|
select count(*) from history
|
||||||
|
''');
|
||||||
|
return rows.first.values.first! as int;
|
||||||
|
}
|
||||||
|
}
|
@@ -11,6 +11,7 @@ import "package:pixes/components/keyboard.dart";
|
|||||||
import "package:pixes/components/md.dart";
|
import "package:pixes/components/md.dart";
|
||||||
import "package:pixes/components/message.dart";
|
import "package:pixes/components/message.dart";
|
||||||
import "package:pixes/foundation/app.dart";
|
import "package:pixes/foundation/app.dart";
|
||||||
|
import "package:pixes/foundation/history.dart";
|
||||||
import "package:pixes/foundation/log.dart";
|
import "package:pixes/foundation/log.dart";
|
||||||
import "package:pixes/network/app_dio.dart";
|
import "package:pixes/network/app_dio.dart";
|
||||||
import "package:pixes/pages/main_page.dart";
|
import "package:pixes/pages/main_page.dart";
|
||||||
@@ -29,6 +30,7 @@ void main() async {
|
|||||||
await App.init();
|
await App.init();
|
||||||
await appdata.readData();
|
await appdata.readData();
|
||||||
await Translation.init();
|
await Translation.init();
|
||||||
|
HistoryManager().init();
|
||||||
handleLinks();
|
handleLinks();
|
||||||
if (App.isDesktop) {
|
if (App.isDesktop) {
|
||||||
await flutter_acrylic.Window.initialize();
|
await flutter_acrylic.Window.initialize();
|
||||||
|
@@ -573,4 +573,14 @@ class Network {
|
|||||||
return Res.error(res.errorMessage);
|
return Res.error(res.errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Res<bool>> sendHistory(List<int> ids) async{
|
||||||
|
var res = await apiPost("/v2/user/browsing-history/illust/add",
|
||||||
|
data: {"illust_ids": ids});
|
||||||
|
if (res.success) {
|
||||||
|
return const Res(true);
|
||||||
|
} else {
|
||||||
|
return Res.fromErrorRes(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,10 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:pixes/appdata.dart';
|
import 'package:pixes/appdata.dart';
|
||||||
import 'package:pixes/components/loading.dart';
|
import 'package:pixes/components/loading.dart';
|
||||||
|
import 'package:pixes/components/segmented_button.dart';
|
||||||
import 'package:pixes/components/title_bar.dart';
|
import 'package:pixes/components/title_bar.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
|
import 'package:pixes/foundation/history.dart';
|
||||||
import 'package:pixes/network/network.dart';
|
import 'package:pixes/network/network.dart';
|
||||||
import 'package:pixes/utils/translation.dart';
|
import 'package:pixes/utils/translation.dart';
|
||||||
|
|
||||||
@@ -17,17 +19,87 @@ class HistoryPage extends StatefulWidget {
|
|||||||
State<HistoryPage> createState() => _HistoryPageState();
|
State<HistoryPage> createState() => _HistoryPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HistoryPageState extends MultiPageLoadingState<HistoryPage, Illust> {
|
class _HistoryPageState extends State<HistoryPage> {
|
||||||
|
int page = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildContent(BuildContext context, final List<Illust> data) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
TitleBar(title: "History".tl),
|
TitleBar(
|
||||||
|
title: "History".tl,
|
||||||
|
action: SegmentedButton<int>(
|
||||||
|
options: [
|
||||||
|
SegmentedButtonOption(0, "Local".tl,),
|
||||||
|
SegmentedButtonOption(1, "Network".tl,),
|
||||||
|
],
|
||||||
|
value: page,
|
||||||
|
onPressed: (key) {
|
||||||
|
setState(() {
|
||||||
|
page = key;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LayoutBuilder(builder: (context, constrains){
|
child: page == 0
|
||||||
|
? const LocalHistoryPage()
|
||||||
|
: const NetworkHistoryPage(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalHistoryPage extends StatefulWidget {
|
||||||
|
const LocalHistoryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LocalHistoryPage> createState() => _LocalHistoryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LocalHistoryPageState extends State<LocalHistoryPage> {
|
||||||
|
int page = 1;
|
||||||
|
|
||||||
|
var data = <IllustHistory>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, constrains) {
|
||||||
return MasonryGridView.builder(
|
return MasonryGridView.builder(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8)
|
padding: const EdgeInsets.symmetric(horizontal: 8) +
|
||||||
+ EdgeInsets.only(bottom: context.padding.bottom),
|
EdgeInsets.only(bottom: context.padding.bottom),
|
||||||
|
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 240,
|
||||||
|
),
|
||||||
|
itemCount: HistoryManager().length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == data.length) {
|
||||||
|
data.addAll(HistoryManager().getHistories(page));
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
return IllustHistoryWidget(data[index]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetworkHistoryPage extends StatefulWidget {
|
||||||
|
const NetworkHistoryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NetworkHistoryPage> createState() => _NetworkHistoryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NetworkHistoryPageState
|
||||||
|
extends MultiPageLoadingState<NetworkHistoryPage, Illust> {
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, final List<Illust> data) {
|
||||||
|
return LayoutBuilder(builder: (context, constrains) {
|
||||||
|
return MasonryGridView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8) +
|
||||||
|
EdgeInsets.only(bottom: context.padding.bottom),
|
||||||
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
||||||
maxCrossAxisExtent: 240,
|
maxCrossAxisExtent: 240,
|
||||||
),
|
),
|
||||||
@@ -44,10 +116,7 @@ class _HistoryPageState extends MultiPageLoadingState<HistoryPage, Illust> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
});
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@@ -14,6 +14,7 @@ import 'package:pixes/components/page_route.dart';
|
|||||||
import 'package:pixes/components/title_bar.dart';
|
import 'package:pixes/components/title_bar.dart';
|
||||||
import 'package:pixes/components/user_preview.dart';
|
import 'package:pixes/components/user_preview.dart';
|
||||||
import 'package:pixes/foundation/app.dart';
|
import 'package:pixes/foundation/app.dart';
|
||||||
|
import 'package:pixes/foundation/history.dart';
|
||||||
import 'package:pixes/foundation/image_provider.dart';
|
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';
|
||||||
@@ -44,6 +45,8 @@ class IllustGalleryPage extends StatefulWidget {
|
|||||||
|
|
||||||
final String? nextUrl;
|
final String? nextUrl;
|
||||||
|
|
||||||
|
static var cachedHistoryIds = <int>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<IllustGalleryPage> createState() => _IllustGalleryPageState();
|
State<IllustGalleryPage> createState() => _IllustGalleryPageState();
|
||||||
}
|
}
|
||||||
@@ -68,6 +71,16 @@ class _IllustGalleryPageState extends State<IllustGalleryPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if(IllustGalleryPage.cachedHistoryIds.length > 5) {
|
||||||
|
Network().sendHistory(
|
||||||
|
IllustGalleryPage.cachedHistoryIds.toList().reversed.toList());
|
||||||
|
IllustGalleryPage.cachedHistoryIds.clear();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void nextPage() {
|
void nextPage() {
|
||||||
var length = illusts.length;
|
var length = illusts.length;
|
||||||
if (controller.page == length - 1) return;
|
if (controller.page == length - 1) return;
|
||||||
@@ -169,6 +182,10 @@ class _IllustPageState extends State<IllustPage> {
|
|||||||
widget.illust.author.isFollowed = v;
|
widget.illust.author.isFollowed = v;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
HistoryManager().addHistory(widget.illust);
|
||||||
|
if(appdata.account!.user.isPremium) {
|
||||||
|
IllustGalleryPage.cachedHistoryIds.add(widget.illust.id);
|
||||||
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user