mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
169676fd9e | ||
332497cf90 | |||
5f15c08eef | |||
3f6b3152b2 | |||
f5b3b36acb | |||
fd8607777e | |||
fa951cac95 | |||
55ad652191 | |||
533497ead1 | |||
![]() |
00cdc18ddd | ||
![]() |
474d9aa6f1 | ||
ffa0c8f887 | |||
0f3f3ea270 | |||
![]() |
b752caa079 | ||
309df2143b | |||
8e964468ea | |||
ca8f09807b | |||
68b214e295 | |||
00c0a64de0 |
@@ -32,7 +32,7 @@ keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
android {
|
||||
namespace = "com.github.wgh136.venera"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion "25.1.8937393"
|
||||
ndkVersion "28.0.13004108"
|
||||
|
||||
splits{
|
||||
abi {
|
||||
|
@@ -47,6 +47,11 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" android:host="exhentai.org" android:pathPrefix="/g" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="@string/share_text">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
|
@@ -7,6 +7,7 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
@@ -40,6 +41,41 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
|
||||
private val nextLocalRequestCode = AtomicInteger()
|
||||
|
||||
private val sharedTexts = ArrayList<String>()
|
||||
|
||||
private var textShareHandler: ((String) -> Unit)? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (intent?.action == Intent.ACTION_SEND) {
|
||||
if (intent.type == "text/plain") {
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (text != null)
|
||||
handleSharedText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
if (intent.type == "text/plain") {
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
if (text != null)
|
||||
handleSharedText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSharedText(text: String) {
|
||||
if (textShareHandler != null) {
|
||||
textShareHandler?.invoke(text)
|
||||
} else {
|
||||
sharedTexts.add(text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <I, O> startContractForResult(
|
||||
contract: ActivityResultContract<I, O>,
|
||||
input: I,
|
||||
@@ -134,6 +170,26 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
val mimeType = req.arguments<String>()
|
||||
openFile(res, mimeType!!)
|
||||
}
|
||||
|
||||
val shareTextChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, "venera/text_share")
|
||||
shareTextChannel.setStreamHandler(
|
||||
object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||
textShareHandler = {text ->
|
||||
events.success(text)
|
||||
}
|
||||
if (sharedTexts.isNotEmpty()) {
|
||||
for (text in sharedTexts) {
|
||||
events.success(text)
|
||||
}
|
||||
sharedTexts.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
textShareHandler = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getProxy(): String {
|
||||
|
4
android/app/src/main/res/values-zh-rCN/strings.xml
Normal file
4
android/app/src/main/res/values-zh-rCN/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="share_text">搜索</string>
|
||||
</resources>
|
4
android/app/src/main/res/values-zh/strings.xml
Normal file
4
android/app/src/main/res/values-zh/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="share_text">搜尋</string>
|
||||
</resources>
|
4
android/app/src/main/res/values/strings.xml
Normal file
4
android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="share_text">Search</string>
|
||||
</resources>
|
@@ -367,7 +367,12 @@
|
||||
"Home Page": "主页",
|
||||
"Favorites Page": "收藏页面",
|
||||
"Explore Page": "探索页面",
|
||||
"Categories Page": "分类页面"
|
||||
"Categories Page": "分类页面",
|
||||
"Convert to local": "转换为本地",
|
||||
"Refresh": "刷新",
|
||||
"Paging": "分页",
|
||||
"Continuous": "连续",
|
||||
"Display mode of comic list": "漫画列表的显示模式"
|
||||
},
|
||||
"zh_TW": {
|
||||
"Home": "首頁",
|
||||
@@ -725,18 +730,23 @@
|
||||
"All Comics": "全部漫畫",
|
||||
"The comic will be marked as no updates as soon as you read it.": "漫畫將在您閱讀後立即標記為無更新",
|
||||
"Disable": "停用",
|
||||
"Once the operation is successful, app will automatically sync data with the server.": "操作成功後, APP將自動與服務器同步數據",
|
||||
"Cache cleared": "緩存已清除",
|
||||
"Disabled": "已禁用",
|
||||
"Once the operation is successful, app will automatically sync data with the server.": "操作成功後, APP將自動與伺服器同步資料",
|
||||
"Cache cleared": "快取已清除",
|
||||
"Disabled": "已停用",
|
||||
"WebDAV Auto Sync": "WebDAV 自動同步",
|
||||
"Mark all as read": "全部標記為已讀",
|
||||
"Do you want to mark all as read?" : "您要全部標記為已讀嗎?",
|
||||
"Swipe down for previous chapter": "向下滑動查看上一章",
|
||||
"Swipe up for next chapter": "向上滑動查看下一章",
|
||||
"Initial Page": "初始頁面",
|
||||
"Home Page": "主頁",
|
||||
"Home Page": "首頁",
|
||||
"Favorites Page": "收藏頁面",
|
||||
"Explore Page": "探索頁面",
|
||||
"Categories Page": "分類頁面"
|
||||
"Categories Page": "分類頁面",
|
||||
"Convert to local": "轉換為本地",
|
||||
"Refresh": "刷新",
|
||||
"Paging": "分頁",
|
||||
"Continuous": "連續",
|
||||
"Display mode of comic list": "漫畫列表的顯示模式"
|
||||
}
|
||||
}
|
||||
|
@@ -770,7 +770,7 @@ class _SliverGridComicsState extends State<SliverGridComics> {
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SliverGridComics oldWidget) {
|
||||
if (!oldWidget.comics.isEqualTo(widget.comics)) {
|
||||
if (!comics.isEqualTo(widget.comics)) {
|
||||
comics.clear();
|
||||
for (var comic in widget.comics) {
|
||||
if (isBlocked(comic) == null) {
|
||||
@@ -879,6 +879,7 @@ class _SliverGridComics extends StatelessWidget {
|
||||
return comic;
|
||||
}
|
||||
return AnimatedContainer(
|
||||
key: ValueKey(comics[index].id),
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
@@ -1140,7 +1141,7 @@ class ComicListState extends State<ComicList> {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
if (_loading[page] == true) {
|
||||
if (_data[page] != null || _loading[page] == true) {
|
||||
return;
|
||||
}
|
||||
_loading[page] = true;
|
||||
@@ -1150,8 +1151,8 @@ class ComicListState extends State<ComicList> {
|
||||
if (!mounted) return;
|
||||
if (res.success) {
|
||||
if (res.data.isEmpty) {
|
||||
_data[page] = const [];
|
||||
setState(() {
|
||||
_data[page] = const [];
|
||||
_maxPage = page;
|
||||
});
|
||||
} else {
|
||||
@@ -1201,6 +1202,11 @@ class ComicListState extends State<ComicList> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var type = appdata.settings['comicListDisplayMode'];
|
||||
return type == 'paging' ? buildPagingMode() : buildContinuousMode();
|
||||
}
|
||||
|
||||
Widget buildPagingMode() {
|
||||
if (_error != null) {
|
||||
return Column(
|
||||
children: [
|
||||
@@ -1249,6 +1255,85 @@ class ComicListState extends State<ComicList> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildContinuousMode() {
|
||||
if (_error != null && _data.isEmpty) {
|
||||
return Column(
|
||||
children: [
|
||||
if (widget.errorLeading != null) widget.errorLeading!,
|
||||
_buildPageSelector(),
|
||||
Expanded(
|
||||
child: NetworkError(
|
||||
withAppbar: false,
|
||||
message: _error!,
|
||||
retry: () {
|
||||
setState(() {
|
||||
_error = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (_data[_page] == null) {
|
||||
_loadPage(_page);
|
||||
return Column(
|
||||
children: [
|
||||
if (widget.errorLeading != null) widget.errorLeading!,
|
||||
const Expanded(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return SmoothCustomScrollView(
|
||||
key: enablePageStorage ? PageStorageKey('scroll$_page') : null,
|
||||
controller: widget.controller,
|
||||
slivers: [
|
||||
if (widget.leadingSliver != null) widget.leadingSliver!,
|
||||
SliverGridComics(
|
||||
comics: _data.values.expand((element) => element).toList(),
|
||||
menuBuilder: widget.menuBuilder,
|
||||
onLastItemBuild: () {
|
||||
if (_error == null && (_maxPage == null || _page < _maxPage!)) {
|
||||
_loadPage(_data.length + 1);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (_error != null)
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.error_outline),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text(_error!, maxLines: 3)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Center(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_error = null;
|
||||
});
|
||||
},
|
||||
child: Text("Retry".tl),
|
||||
),
|
||||
),
|
||||
],
|
||||
).paddingHorizontal(16).paddingVertical(8),
|
||||
)
|
||||
else if (_maxPage == null || _page < _maxPage!)
|
||||
const SliverListLoadingIndicator(),
|
||||
if (widget.trailingSliver != null) widget.trailingSliver!,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StarRating extends StatelessWidget {
|
||||
@@ -1535,17 +1620,20 @@ class _SMClipper extends CustomClipper<Rect> {
|
||||
}
|
||||
|
||||
class SimpleComicTile extends StatelessWidget {
|
||||
const SimpleComicTile({super.key, required this.comic, this.onTap});
|
||||
const SimpleComicTile(
|
||||
{super.key, required this.comic, this.onTap, this.withTitle = false});
|
||||
|
||||
final Comic comic;
|
||||
|
||||
final void Function()? onTap;
|
||||
|
||||
final bool withTitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var image = _findImageProvider(comic);
|
||||
|
||||
var child = image == null
|
||||
Widget child = image == null
|
||||
? const SizedBox()
|
||||
: AnimatedImage(
|
||||
image: image,
|
||||
@@ -1555,7 +1643,18 @@ class SimpleComicTile extends StatelessWidget {
|
||||
filterQuality: FilterQuality.medium,
|
||||
);
|
||||
|
||||
return AnimatedTapRegion(
|
||||
child = Container(
|
||||
width: 98,
|
||||
height: 136,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: child,
|
||||
);
|
||||
|
||||
child = AnimatedTapRegion(
|
||||
borderRadius: 8,
|
||||
onTap: onTap ??
|
||||
() {
|
||||
@@ -1566,16 +1665,29 @@ class SimpleComicTile extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 92,
|
||||
height: 114,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: child,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (withTitle) {
|
||||
child = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
child,
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
width: 92,
|
||||
child: Center(
|
||||
child: Text(
|
||||
comic.title.replaceAll('\n', ''),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
@@ -99,11 +99,13 @@ class _SmoothScrollProviderState extends State<SmoothScrollProvider> {
|
||||
);
|
||||
if (_futurePosition == old) return;
|
||||
var target = _futurePosition!;
|
||||
_controller.animateTo(
|
||||
_controller
|
||||
.animateTo(
|
||||
_futurePosition!,
|
||||
duration: _fastAnimationDuration,
|
||||
curve: Curves.linear,
|
||||
).then((_) {
|
||||
)
|
||||
.then((_) {
|
||||
var current = _controller.position.pixels;
|
||||
if (current == target && current == _futurePosition) {
|
||||
_futurePosition = null;
|
||||
@@ -144,3 +146,169 @@ class ScrollControllerProvider extends InheritedWidget {
|
||||
return oldWidget.controller != controller;
|
||||
}
|
||||
}
|
||||
|
||||
class AppScrollBar extends StatefulWidget {
|
||||
const AppScrollBar({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.child,
|
||||
this.topPadding = 0,
|
||||
});
|
||||
|
||||
final ScrollController controller;
|
||||
|
||||
final Widget child;
|
||||
|
||||
final double topPadding;
|
||||
|
||||
@override
|
||||
State<AppScrollBar> createState() => _AppScrollBarState();
|
||||
}
|
||||
|
||||
class _AppScrollBarState extends State<AppScrollBar> {
|
||||
late final ScrollController _scrollController;
|
||||
|
||||
double minExtent = 0;
|
||||
double maxExtent = 0;
|
||||
double position = 0;
|
||||
|
||||
double viewHeight = 0;
|
||||
|
||||
final _scrollIndicatorSize = App.isDesktop ? 42.0 : 64.0;
|
||||
|
||||
late final VerticalDragGestureRecognizer _dragGestureRecognizer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = widget.controller;
|
||||
_scrollController.addListener(onChanged);
|
||||
Future.microtask(onChanged);
|
||||
_dragGestureRecognizer = VerticalDragGestureRecognizer()
|
||||
..onUpdate = onUpdate;
|
||||
}
|
||||
|
||||
void onUpdate(DragUpdateDetails details) {
|
||||
if (maxExtent - minExtent <= 0 ||
|
||||
viewHeight == 0 ||
|
||||
details.primaryDelta == null) {
|
||||
return;
|
||||
}
|
||||
var offset = details.primaryDelta!;
|
||||
var positionOffset =
|
||||
offset / (viewHeight - _scrollIndicatorSize) * (maxExtent - minExtent);
|
||||
_scrollController.jumpTo((position + positionOffset).clamp(
|
||||
minExtent,
|
||||
maxExtent,
|
||||
));
|
||||
}
|
||||
|
||||
void onChanged() {
|
||||
if (_scrollController.positions.isEmpty) return;
|
||||
var position = _scrollController.position;
|
||||
if (position.minScrollExtent != minExtent ||
|
||||
position.maxScrollExtent != maxExtent ||
|
||||
position.pixels != this.position) {
|
||||
setState(() {
|
||||
minExtent = position.minScrollExtent;
|
||||
maxExtent = position.maxScrollExtent;
|
||||
this.position = position.pixels;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constrains) {
|
||||
var scrollHeight = (maxExtent - minExtent);
|
||||
var height = constrains.maxHeight - widget.topPadding;
|
||||
viewHeight = height;
|
||||
var top = scrollHeight == 0
|
||||
? 0.0
|
||||
: (position - minExtent) /
|
||||
scrollHeight *
|
||||
(height - _scrollIndicatorSize);
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: widget.child,
|
||||
),
|
||||
Positioned(
|
||||
top: top + widget.topPadding,
|
||||
right: 0,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerDown: (event) {
|
||||
_dragGestureRecognizer.addPointer(event);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: _scrollIndicatorSize/2,
|
||||
height: _scrollIndicatorSize,
|
||||
child: CustomPaint(
|
||||
painter: _ScrollIndicatorPainter(
|
||||
backgroundColor: context.colorScheme.surface,
|
||||
shadowColor: context.colorScheme.shadow,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Icon(Icons.arrow_drop_up, size: 18),
|
||||
Icon(Icons.arrow_drop_down, size: 18),
|
||||
const Spacer(),
|
||||
],
|
||||
).paddingLeft(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScrollIndicatorPainter extends CustomPainter {
|
||||
final Color backgroundColor;
|
||||
|
||||
final Color shadowColor;
|
||||
|
||||
const _ScrollIndicatorPainter({
|
||||
required this.backgroundColor,
|
||||
required this.shadowColor,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
var path = Path()
|
||||
..moveTo(size.width, 0)
|
||||
..lineTo(size.width, size.height)
|
||||
..arcToPoint(
|
||||
Offset(size.width, 0),
|
||||
radius: Radius.circular(size.width),
|
||||
);
|
||||
canvas.drawShadow(path, shadowColor, 4, true);
|
||||
var backgroundPaint = Paint()
|
||||
..color = backgroundColor
|
||||
..style = PaintingStyle.fill;
|
||||
path = Path()
|
||||
..moveTo(size.width, 0)
|
||||
..lineTo(size.width, size.height)
|
||||
..arcToPoint(
|
||||
Offset(size.width, 0),
|
||||
radius: Radius.circular(size.width),
|
||||
);
|
||||
canvas.drawPath(path, backgroundPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return oldDelegate is! _ScrollIndicatorPainter ||
|
||||
oldDelegate.backgroundColor != backgroundColor ||
|
||||
oldDelegate.shadowColor != shadowColor;
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ export "widget_utils.dart";
|
||||
export "context.dart";
|
||||
|
||||
class _App {
|
||||
final version = "1.3.2";
|
||||
final version = "1.3.3";
|
||||
|
||||
bool get isAndroid => Platform.isAndroid;
|
||||
|
||||
|
@@ -4,9 +4,10 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:venera/foundation/app.dart';
|
||||
import 'package:venera/utils/data_sync.dart';
|
||||
import 'package:venera/utils/init.dart';
|
||||
import 'package:venera/utils/io.dart';
|
||||
|
||||
class Appdata {
|
||||
class Appdata with Init {
|
||||
Appdata._create();
|
||||
|
||||
final Settings settings = Settings._create();
|
||||
@@ -53,28 +54,6 @@ class Appdata {
|
||||
saveData();
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
var dataPath = (await getApplicationSupportDirectory()).path;
|
||||
var file = File(FilePath.join(
|
||||
dataPath,
|
||||
'appdata.json',
|
||||
));
|
||||
if (!await file.exists()) {
|
||||
return;
|
||||
}
|
||||
var json = jsonDecode(await file.readAsString());
|
||||
for (var key in (json['settings'] as Map<String, dynamic>).keys) {
|
||||
if (json['settings'][key] != null) {
|
||||
settings[key] = json['settings'][key];
|
||||
}
|
||||
}
|
||||
searchHistory = List.from(json['searchHistory']);
|
||||
var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json'));
|
||||
if (await implicitDataFile.exists()) {
|
||||
implicitData = jsonDecode(await implicitDataFile.readAsString());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'settings': settings._data,
|
||||
@@ -110,6 +89,29 @@ class Appdata {
|
||||
var file = File(FilePath.join(App.dataPath, 'implicitData.json'));
|
||||
file.writeAsString(jsonEncode(implicitData));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> doInit() async {
|
||||
var dataPath = (await getApplicationSupportDirectory()).path;
|
||||
var file = File(FilePath.join(
|
||||
dataPath,
|
||||
'appdata.json',
|
||||
));
|
||||
if (!await file.exists()) {
|
||||
return;
|
||||
}
|
||||
var json = jsonDecode(await file.readAsString());
|
||||
for (var key in (json['settings'] as Map<String, dynamic>).keys) {
|
||||
if (json['settings'][key] != null) {
|
||||
settings[key] = json['settings'][key];
|
||||
}
|
||||
}
|
||||
searchHistory = List.from(json['searchHistory']);
|
||||
var implicitDataFile = File(FilePath.join(dataPath, 'implicitData.json'));
|
||||
if (await implicitDataFile.exists()) {
|
||||
implicitData = jsonDecode(await implicitDataFile.readAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final appdata = Appdata._create();
|
||||
@@ -160,10 +162,12 @@ class Settings with ChangeNotifier {
|
||||
'customImageProcessing': defaultCustomImageProcessing,
|
||||
'sni': true,
|
||||
'autoAddLanguageFilter': 'none', // none, chinese, english, japanese
|
||||
'comicSourceListUrl': "https://cdn.jsdelivr.net/gh/venera-app/venera-configs@latest/index.json",
|
||||
'comicSourceListUrl':
|
||||
"https://cdn.jsdelivr.net/gh/venera-app/venera-configs@latest/index.json",
|
||||
'preloadImageCount': 4,
|
||||
'followUpdatesFolder': null,
|
||||
'initialPage': '0',
|
||||
'comicListDisplayMode': 'paging', // paging, continuous
|
||||
};
|
||||
|
||||
operator [](String key) {
|
||||
|
@@ -111,6 +111,9 @@ class Comic {
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode ^ sourceKey.hashCode;
|
||||
|
||||
@override
|
||||
toString() => "$sourceKey@$id";
|
||||
}
|
||||
|
||||
class ComicDetails with HistoryMixin {
|
||||
|
@@ -224,7 +224,8 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
source_folder text
|
||||
);
|
||||
""");
|
||||
for (var folder in _getFolderNamesWithDB()) {
|
||||
var folderNames = _getFolderNamesWithDB();
|
||||
for (var folder in folderNames) {
|
||||
var columns = _db.select("""
|
||||
pragma table_info("$folder");
|
||||
""");
|
||||
@@ -246,6 +247,15 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
break;
|
||||
}
|
||||
}
|
||||
await appdata.ensureInit();
|
||||
// Make sure the follow updates folder is ready
|
||||
var followUpdateFolder = appdata.settings['followUpdatesFolder'];
|
||||
if (followUpdateFolder is String &&
|
||||
folderNames.contains(followUpdateFolder)) {
|
||||
prepareTableForFollowUpdates(followUpdateFolder, false);
|
||||
} else {
|
||||
appdata.settings['followUpdatesFolder'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> find(String id, ComicType type) {
|
||||
@@ -849,7 +859,7 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void prepareTableForFollowUpdates(String table) {
|
||||
void prepareTableForFollowUpdates(String table, [bool clearData = true]) {
|
||||
// check if the table has the column "last_update_time" "has_new_update" "last_check_time"
|
||||
var columns = _db.select("""
|
||||
pragma table_info("$table");
|
||||
@@ -866,10 +876,12 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
add column has_new_update int;
|
||||
""");
|
||||
}
|
||||
_db.execute("""
|
||||
if (clearData) {
|
||||
_db.execute("""
|
||||
update "$table"
|
||||
set has_new_update = 0;
|
||||
""");
|
||||
}
|
||||
if (!columns.any((element) => element["name"] == "last_check_time")) {
|
||||
_db.execute("""
|
||||
alter table "$table"
|
||||
|
@@ -422,12 +422,30 @@ class LocalManager with ChangeNotifier {
|
||||
return files.map((e) => "file://${e.path}").toList();
|
||||
}
|
||||
|
||||
bool isDownloaded(String id, ComicType type, [int? ep]) {
|
||||
bool isDownloaded(String id, ComicType type,
|
||||
[int? ep, ComicChapters? chapters]) {
|
||||
var comic = find(id, type);
|
||||
if (comic == null) return false;
|
||||
if (comic.chapters == null || ep == null) return true;
|
||||
if (chapters != null) {
|
||||
if (comic.chapters?.length != chapters.length) {
|
||||
// update
|
||||
add(LocalComic(
|
||||
id: comic.id,
|
||||
title: comic.title,
|
||||
subtitle: comic.subtitle,
|
||||
tags: comic.tags,
|
||||
directory: comic.directory,
|
||||
chapters: chapters,
|
||||
cover: comic.cover,
|
||||
comicType: comic.comicType,
|
||||
downloadedChapters: comic.downloadedChapters,
|
||||
createdAt: comic.createdAt,
|
||||
));
|
||||
}
|
||||
}
|
||||
return comic.downloadedChapters
|
||||
.contains(comic.chapters!.ids.elementAt(ep - 1));
|
||||
.contains((chapters ?? comic.chapters)!.ids.elementAtOrNull(ep - 1));
|
||||
}
|
||||
|
||||
List<DownloadTask> downloadingTasks = [];
|
||||
|
@@ -11,6 +11,7 @@ import 'package:venera/pages/comic_source_page.dart';
|
||||
import 'package:venera/pages/follow_updates_page.dart';
|
||||
import 'package:venera/pages/settings/settings_page.dart';
|
||||
import 'package:venera/utils/app_links.dart';
|
||||
import 'package:venera/utils/handle_text_share.dart';
|
||||
import 'package:venera/utils/tags_translation.dart';
|
||||
import 'package:venera/utils/translations.dart';
|
||||
import 'foundation/appdata.dart';
|
||||
@@ -45,6 +46,7 @@ Future<void> init() async {
|
||||
_checkOldConfigs();
|
||||
if (App.isAndroid) {
|
||||
handleLinks();
|
||||
handleTextShare();
|
||||
}
|
||||
FlutterError.onError = (details) {
|
||||
Log.error("Unhandled Exception", "${details.exception}\n${details.stack}");
|
||||
|
@@ -141,24 +141,15 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
||||
) {
|
||||
String? font;
|
||||
List<String>? fallback;
|
||||
if (App.isWindows) {
|
||||
font = 'Segoe UI';
|
||||
fallback = [
|
||||
'Segoe UI',
|
||||
'Microsoft YaHei',
|
||||
'PingFang SC',
|
||||
'Noto Sans CJK',
|
||||
'Arial',
|
||||
'sans-serif'
|
||||
];
|
||||
}
|
||||
if (App.isLinux) {
|
||||
if (App.isLinux || App.isWindows) {
|
||||
font = 'Noto Sans CJK';
|
||||
fallback = [
|
||||
'Segoe UI',
|
||||
'Noto Sans SC',
|
||||
'Noto Sans TC',
|
||||
'Noto Sans',
|
||||
'Microsoft YaHei',
|
||||
'PingFang SC',
|
||||
'Noto Sans CJK',
|
||||
'Arial',
|
||||
'sans-serif'
|
||||
];
|
||||
|
@@ -282,9 +282,27 @@ class RHttpAdapter implements HttpClientAdapter {
|
||||
return ResponseBody(
|
||||
res.body,
|
||||
res.statusCode,
|
||||
statusMessage: null,
|
||||
statusMessage: _getStatusMessage(res.statusCode),
|
||||
isRedirect: false,
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
|
||||
static String _getStatusMessage(int statusCode) {
|
||||
return switch(statusCode) {
|
||||
200 => "OK",
|
||||
201 => "Created",
|
||||
202 => "Accepted",
|
||||
204 => "No Content",
|
||||
206 => "Partial Content",
|
||||
301 => "Moved Permanently",
|
||||
302 => "Found",
|
||||
400 => "Invalid Status Code 400: The Request is invalid.",
|
||||
401 => "Invalid Status Code 401: The Request is unauthorized.",
|
||||
403 => "Invalid Status Code 403: No permission to access the resource. Check your account or network.",
|
||||
404 => "Invalid Status Code 404: Not found.",
|
||||
429 => "Invalid Status Code 429: Too many requests. Please try again later.",
|
||||
_ => "Invalid Status Code $statusCode",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -90,7 +90,7 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
bool isLoading = true;
|
||||
|
||||
static const _kComicHeight = 132.0;
|
||||
static const _kComicHeight = 162.0;
|
||||
|
||||
get _comicWidth => _kComicHeight * 0.7;
|
||||
|
||||
@@ -152,7 +152,7 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
||||
}
|
||||
|
||||
Widget buildComic(Comic c) {
|
||||
return SimpleComicTile(comic: c)
|
||||
return SimpleComicTile(comic: c, withTitle: true)
|
||||
.paddingLeft(_kLeftPadding)
|
||||
.paddingBottom(2);
|
||||
}
|
||||
|
@@ -186,12 +186,17 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
||||
|
||||
late TabController tabController;
|
||||
|
||||
int index = 0;
|
||||
late int index;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
history = widget.history;
|
||||
if (history?.group != null) {
|
||||
index = history!.group! - 1;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -199,6 +204,7 @@ class _GroupedComicChaptersState extends State<_GroupedComicChapters>
|
||||
state = context.findAncestorStateOfType<_ComicPageState>()!;
|
||||
chapters = state.comic.chapters!;
|
||||
tabController = TabController(
|
||||
initialIndex: index,
|
||||
length: chapters.ids.length,
|
||||
vsync: this,
|
||||
);
|
||||
|
@@ -518,11 +518,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
||||
),
|
||||
],
|
||||
);
|
||||
body = Scrollbar(
|
||||
body = AppScrollBar(
|
||||
topPadding: 48,
|
||||
controller: scrollController,
|
||||
thickness: App.isDesktop ? 8 : 12,
|
||||
radius: const Radius.circular(8),
|
||||
interactive: true,
|
||||
child: ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
child: body,
|
||||
|
@@ -110,6 +110,15 @@ class _NormalFavoritePageState extends State<_NormalFavoritePage> {
|
||||
child: Text(widget.data.title),
|
||||
),
|
||||
actions: [
|
||||
Tooltip(
|
||||
message: "Refresh".tl,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
comicListKey.currentState!.refresh();
|
||||
},
|
||||
),
|
||||
),
|
||||
MenuButton(entries: [
|
||||
MenuEntry(
|
||||
icon: Icons.sync,
|
||||
|
@@ -297,7 +297,7 @@ class _HistoryState extends State<_History> {
|
||||
).paddingHorizontal(16),
|
||||
if (history.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 128,
|
||||
height: 136,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: history.length,
|
||||
@@ -400,13 +400,14 @@ class _LocalState extends State<_Local> {
|
||||
).paddingHorizontal(16),
|
||||
if (local.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 128,
|
||||
height: 136,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: local.length,
|
||||
itemBuilder: (context, index) {
|
||||
return SimpleComicTile(comic: local[index])
|
||||
.paddingHorizontal(8);
|
||||
.paddingHorizontal(8)
|
||||
.paddingVertical(2);
|
||||
},
|
||||
),
|
||||
).paddingHorizontal(8),
|
||||
|
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:venera/components/components.dart';
|
||||
import 'package:venera/foundation/app.dart';
|
||||
import 'package:venera/foundation/appdata.dart';
|
||||
import 'package:venera/foundation/comic_type.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
import 'package:venera/foundation/log.dart';
|
||||
import 'package:venera/pages/comic_details_page/comic_page.dart';
|
||||
@@ -304,7 +305,9 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
(c as LocalComic).read();
|
||||
// prevent dirty data
|
||||
var comic = LocalManager().find(c.id, ComicType(c.sourceKey.hashCode))!;
|
||||
comic.read();
|
||||
}
|
||||
},
|
||||
menuBuilder: (c) {
|
||||
|
@@ -26,7 +26,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
||||
inProgress = true;
|
||||
if (reader.type == ComicType.local ||
|
||||
(LocalManager()
|
||||
.isDownloaded(reader.cid, reader.type, reader.chapter))) {
|
||||
.isDownloaded(reader.cid, reader.type, reader.chapter, reader.widget.chapters))) {
|
||||
try {
|
||||
var images = await LocalManager()
|
||||
.getImages(reader.cid, reader.type, reader.chapter);
|
||||
|
@@ -115,7 +115,7 @@ class _ReaderState extends State<Reader>
|
||||
|
||||
String get cid => widget.cid;
|
||||
|
||||
String get eid => widget.chapters?.ids.elementAt(chapter - 1) ?? '0';
|
||||
String get eid => widget.chapters?.ids.elementAtOrNull(chapter - 1) ?? '0';
|
||||
|
||||
List<String>? images;
|
||||
|
||||
|
@@ -90,6 +90,14 @@ class _ExploreSettingsState extends State<ExploreSettings> {
|
||||
'3': "Categories Page".tl,
|
||||
},
|
||||
).toSliver(),
|
||||
SelectSetting(
|
||||
title: "Display mode of comic list".tl,
|
||||
settingKey: "comicListDisplayMode",
|
||||
optionTranslation: {
|
||||
"paging": "Paging".tl,
|
||||
"Continuous": "Continuous".tl,
|
||||
},
|
||||
).toSliver(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@@ -95,11 +95,13 @@ Future<void> importAppData(File file, [bool checkVersion = false]) async {
|
||||
}
|
||||
var comicSourceDir = FilePath.join(cacheDirPath, "comic_source");
|
||||
if (Directory(comicSourceDir).existsSync()) {
|
||||
Directory(FilePath.join(App.dataPath, "comic_source"))
|
||||
.deleteIfExistsSync(recursive: true);
|
||||
Directory(FilePath.join(App.dataPath, "comic_source")).createSync();
|
||||
for (var file in Directory(comicSourceDir).listSync()) {
|
||||
if (file is File) {
|
||||
var targetFile =
|
||||
FilePath.join(App.dataPath, "comic_source", file.name);
|
||||
File(targetFile).deleteIfExistsSync();
|
||||
await file.copy(targetFile);
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import 'package:venera/network/app_dio.dart';
|
||||
import 'package:venera/utils/data.dart';
|
||||
import 'package:venera/utils/ext.dart';
|
||||
import 'package:webdav_client/webdav_client.dart' hide File;
|
||||
import 'package:rhttp/rhttp.dart' as rhttp;
|
||||
|
||||
import 'io.dart';
|
||||
|
||||
@@ -89,11 +90,18 @@ class DataSync with ChangeNotifier {
|
||||
String user = config[1];
|
||||
String pass = config[2];
|
||||
|
||||
var proxy = await AppDio.getProxy();
|
||||
|
||||
var client = newClient(
|
||||
url,
|
||||
user: user,
|
||||
password: pass,
|
||||
adapter: RHttpAdapter(),
|
||||
adapter: RHttpAdapter(
|
||||
rhttp.ClientSettings(
|
||||
proxySettings:
|
||||
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -154,11 +162,18 @@ class DataSync with ChangeNotifier {
|
||||
String user = config[1];
|
||||
String pass = config[2];
|
||||
|
||||
var proxy = await AppDio.getProxy();
|
||||
|
||||
var client = newClient(
|
||||
url,
|
||||
user: user,
|
||||
password: pass,
|
||||
adapter: RHttpAdapter(),
|
||||
adapter: RHttpAdapter(
|
||||
rhttp.ClientSettings(
|
||||
proxySettings:
|
||||
proxy == null ? null : rhttp.ProxySettings.proxy(proxy),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
|
22
lib/utils/handle_text_share.dart
Normal file
22
lib/utils/handle_text_share.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:venera/foundation/app.dart';
|
||||
import 'package:venera/pages/aggregated_search_page.dart';
|
||||
|
||||
bool _isHandling = false;
|
||||
|
||||
/// Handle text share event.
|
||||
/// App will navigate to [AggregatedSearchPage] with the shared text as keyword.
|
||||
void handleTextShare() async {
|
||||
if (_isHandling) return;
|
||||
_isHandling = true;
|
||||
|
||||
var channel = EventChannel('venera/text_share');
|
||||
await for (var event in channel.receiveBroadcastStream()) {
|
||||
if (App.mainNavigatorKey == null) {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
}
|
||||
if (event is String) {
|
||||
App.rootContext.to(() => AggregatedSearchPage(keyword: event));
|
||||
}
|
||||
}
|
||||
}
|
39
pubspec.lock
39
pubspec.lock
@@ -408,8 +408,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "5978d0c7784fbbefcacc573547f0ab01ba59b7b3"
|
||||
resolved-ref: "5978d0c7784fbbefcacc573547f0ab01ba59b7b3"
|
||||
ref: "8feae95df7fb00455df129ad7a0dfec1d0e8d8e4"
|
||||
resolved-ref: "8feae95df7fb00455df129ad7a0dfec1d0e8d8e4"
|
||||
url: "https://github.com/wgh136/flutter_qjs"
|
||||
source: git
|
||||
version: "0.3.7"
|
||||
@@ -425,17 +425,17 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_rust_bridge
|
||||
sha256: "3292ad6085552987b8b3b9a7e5805567f4013372d302736b702801acb001ee00"
|
||||
sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.1"
|
||||
version: "2.9.0"
|
||||
flutter_saf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "7637b8b67d0a831f3cd7e702b8173e300880d32e"
|
||||
resolved-ref: "7637b8b67d0a831f3cd7e702b8173e300880d32e"
|
||||
url: "https://github.com/pkuislm/flutter_saf.git"
|
||||
ref: "690a03a954f1603e0149cfd479c8961b88f21336"
|
||||
resolved-ref: "690a03a954f1603e0149cfd479c8961b88f21336"
|
||||
url: "https://github.com/venera-app/flutter_saf"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
flutter_test:
|
||||
@@ -612,8 +612,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "9a784b193af5d55b2a35e58fa390bda3e4f35d00"
|
||||
resolved-ref: "9a784b193af5d55b2a35e58fa390bda3e4f35d00"
|
||||
ref: ac7d05dde32e8d728102a9ff66e6b55f05d94ba1
|
||||
resolved-ref: ac7d05dde32e8d728102a9ff66e6b55f05d94ba1
|
||||
url: "https://github.com/venera-app/lodepng_flutter"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@@ -757,12 +757,11 @@ packages:
|
||||
rhttp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: rhttp
|
||||
ref: HEAD
|
||||
resolved-ref: "18d430cc45fd4f0114885c5235090abf65106257"
|
||||
url: "https://github.com/wgh136/rhttp"
|
||||
source: git
|
||||
version: "0.10.0"
|
||||
name: rhttp
|
||||
sha256: "037e9b59a68bb4ba664db1cbb4601e878cf5a2fc1cb3d0a9c58e3776609dec4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.0"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1045,8 +1044,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "285f87f15bccd2d5d5ff443761348c6ee47b98d1"
|
||||
resolved-ref: "285f87f15bccd2d5d5ff443761348c6ee47b98d1"
|
||||
ref: "2f669c98fb81cff1c64fee93466a1475c77e4273"
|
||||
resolved-ref: "2f669c98fb81cff1c64fee93466a1475c77e4273"
|
||||
url: "https://github.com/wgh136/webdav_client"
|
||||
source: git
|
||||
version: "1.2.2"
|
||||
@@ -1094,10 +1093,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: zip_flutter
|
||||
sha256: bbf3160062610a43901b7ebbc6f6dd46519540f03a84027dc7b1fff399dda1ac
|
||||
sha256: c4d5a34c5803def866bc550926bb16fe89717c9b7304695d5b2ede30964eb8a8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.10"
|
||||
version: "0.0.12"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
flutter: ">=3.29.2"
|
||||
|
21
pubspec.yaml
21
pubspec.yaml
@@ -2,11 +2,11 @@ name: venera
|
||||
description: "A comic app."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.3.2+132
|
||||
version: 1.3.3+133
|
||||
|
||||
environment:
|
||||
sdk: '>=3.6.0 <4.0.0'
|
||||
flutter: 3.29.0
|
||||
flutter: 3.29.2
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@@ -19,7 +19,7 @@ dependencies:
|
||||
flutter_qjs:
|
||||
git:
|
||||
url: https://github.com/wgh136/flutter_qjs
|
||||
ref: 5978d0c7784fbbefcacc573547f0ab01ba59b7b3
|
||||
ref: 8feae95df7fb00455df129ad7a0dfec1d0e8d8e4
|
||||
crypto: ^3.0.6
|
||||
dio: ^5.8.0+1
|
||||
html: ^0.15.5
|
||||
@@ -52,25 +52,22 @@ dependencies:
|
||||
sliver_tools: ^0.2.12
|
||||
flutter_file_dialog: ^3.0.2
|
||||
file_selector: ^1.0.3
|
||||
zip_flutter: ^0.0.10
|
||||
zip_flutter: ^0.0.12
|
||||
lodepng_flutter:
|
||||
git:
|
||||
url: https://github.com/venera-app/lodepng_flutter
|
||||
ref: 9a784b193af5d55b2a35e58fa390bda3e4f35d00
|
||||
rhttp:
|
||||
git:
|
||||
url: https://github.com/wgh136/rhttp
|
||||
path: rhttp
|
||||
ref: ac7d05dde32e8d728102a9ff66e6b55f05d94ba1
|
||||
rhttp: ^0.11.0
|
||||
webdav_client:
|
||||
git:
|
||||
url: https://github.com/wgh136/webdav_client
|
||||
ref: 285f87f15bccd2d5d5ff443761348c6ee47b98d1
|
||||
ref: 2f669c98fb81cff1c64fee93466a1475c77e4273
|
||||
battery_plus: ^6.2.1
|
||||
local_auth: ^2.3.0
|
||||
flutter_saf:
|
||||
git:
|
||||
url: https://github.com/pkuislm/flutter_saf.git
|
||||
ref: 7637b8b67d0a831f3cd7e702b8173e300880d32e
|
||||
url: https://github.com/venera-app/flutter_saf
|
||||
ref: 690a03a954f1603e0149cfd479c8961b88f21336
|
||||
dynamic_color: ^1.7.0
|
||||
shimmer_animation: ^2.1.0
|
||||
flutter_memory_info: ^0.0.1
|
||||
|
Reference in New Issue
Block a user