diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 71c68bd..c11463a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,12 +39,18 @@ jobs: ln -s /Applications dist/dmg_contents/Applications hdiutil create -volname "venera" -srcfolder dist/dmg_contents -ov -format UDZO "dist/venera.dmg" + - name: Add version to filename + run: | + APP_VERSION=$(grep "version:" pubspec.yaml | cut -d':' -f2 | tr -d ' ') + mkdir -p result + mv dist/venera.dmg result/venera-$APP_VERSION.dmg + # Step 4: Attach and upload artifacts (optional) - name: Upload DMG uses: actions/upload-artifact@v4 with: - name: venera.dmg - path: dist/venera.dmg + name: macos_build + path: result/ Build_IOS: runs-on: macos-15 steps: @@ -62,10 +68,15 @@ jobs: mv /Users/runner/work/venera/venera/build/ios/iphoneos/Runner.app /Users/runner/work/venera/venera/build/ios/iphoneos/Payload cd /Users/runner/work/venera/venera/build/ios/iphoneos/ zip -r venera-ios.ipa Payload + - name: Add version to filename + run: | + APP_VERSION=$(grep "version:" pubspec.yaml | cut -d':' -f2 | tr -d ' ') + mkdir -p result + mv build/ios/iphoneos/venera-ios.ipa result/venera-ios-$APP_VERSION.ipa - uses: actions/upload-artifact@v4 with: - name: app-ios.ipa - path: /Users/runner/work/venera/venera/build/ios/iphoneos/venera-ios.ipa + name: ios_build + path: result/ Build_Android: runs-on: ubuntu-22.04 steps: @@ -130,7 +141,7 @@ jobs: sudo apt-get update -y sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1 dart pub global activate flutter_to_debian - - run: python3 debian/build.py + - run: python3 debian/build.py x64 - run: dart run flutter_to_arch - run: | sudo rm -rf build/linux/arch/app.tar.gz @@ -145,19 +156,43 @@ jobs: with: name: arch_build path: build/linux/arch/ + Build_Linux_ARM64: + runs-on: ubuntu-22.04-arm + steps: + - uses: actions/checkout@v4 + - name: Setup Flutter + run: | + FLUTTER_VERSION=$(grep " flutter:" pubspec.yaml | cut -d':' -f2 | tr -d ' ') + sudo apt-get update -y && sudo apt-get upgrade -y; + sudo apt-get install -y curl git unzip xz-utils zip libglu1-mesa clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev + git clone --depth 1 --branch $FLUTTER_VERSION https://github.com/flutter/flutter.git $RUNNER_TEMP/flutter + echo "$RUNNER_TEMP/flutter/bin" >> $GITHUB_PATH + - name: Install Flutter + run: flutter doctor + - name: Install dependencies + run: | + flutter pub get + sudo apt-get update -y + sudo apt-get install -y ninja-build libgtk-3-dev webkit2gtk-4.1 + dart pub global activate flutter_to_debian + - run: python3 debian/build.py arm64 + - uses: actions/upload-artifact@v4 + with: + name: deb_arm64_build + path: build/linux/x64/release/debian # This is a bug related to flutter_to_debian, but it's not a big deal. Release: runs-on: ubuntu-22.04 - needs: [Build_MacOS, Build_IOS, Build_Android, Build_Windows, Build_Linux] + needs: [Build_MacOS, Build_IOS, Build_Android, Build_Windows, Build_Linux, Build_Linux_ARM64] if: github.event_name == 'release' # 仅在 push 事件时执行 steps: - uses: actions/download-artifact@v4 with: - name: venera.dmg + name: macos_build path: outputs - uses: actions/download-artifact@v4 with: - name: app-ios.ipa + name: ios_build path: outputs - uses: actions/download-artifact@v4 with: @@ -175,6 +210,10 @@ jobs: with: name: arch_build path: outputs + - uses: actions/download-artifact@v4 + with: + name: deb_arm64_build + path: outputs - uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.ref_name }} diff --git a/assets/translation.json b/assets/translation.json index 999477e..73d62cc 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -321,7 +321,10 @@ "Manage": "管理", "Verify": "验证", "Cloudflare verification required": "需要Cloudflare验证", - "Success": "成功" + "Success": "成功", + "Compressing": "压缩中", + "Exporting": "导出中", + "Search Sources": "搜索源" }, "zh_TW": { "Home": "首頁", @@ -645,6 +648,9 @@ "Manage": "管理", "Verify": "驗證", "Cloudflare verification required": "需要Cloudflare驗證", - "Success": "成功" + "Success": "成功", + "Compressing": "壓縮中", + "Exporting": "匯出中", + "Search Sources": "搜索源" } } \ No newline at end of file diff --git a/debian/build.py b/debian/build.py index 0547d45..0932f2e 100644 --- a/debian/build.py +++ b/debian/build.py @@ -1,5 +1,7 @@ import subprocess +import sys +arch = sys.argv[1] debianContent = '' desktopContent = '' version = '' @@ -12,7 +14,14 @@ with open('pubspec.yaml', 'r') as f: version = str.split(str.split(f.read(), 'version: ')[1], '+')[0] with open('debian/debian.yaml', 'w') as f: - f.write(debianContent.replace('{{Version}}', version)) + content = debianContent.replace('{{Version}}', version) + if arch == 'x64': + content = content.replace('{{Arch}}', 'x64') + content = content.replace('{{Architecture}}', 'amd64') + elif arch == 'arm64': + content = content.replace('{{Arch}}', 'arm64') + content = content.replace('{{Architecture}}', 'arm64') + f.write(content) with open('debian/gui/venera.desktop', 'w') as f: f.write(desktopContent.replace('{{Version}}', version)) diff --git a/debian/debian.yaml b/debian/debian.yaml index 545a025..a9d0869 100644 --- a/debian/debian.yaml +++ b/debian/debian.yaml @@ -1,13 +1,13 @@ flutter_app: command: venera - arch: x64 + arch: {{Arch}} parent: /usr/local/lib - nonInteractive: false + nonInteractive: true control: Package: venera Version: {{Version}} - Architecture: amd64 + Architecture: {{Architecture}} Priority: optional Depends: libwebkit2gtk-4.1-0, libgtk-3-0 Maintainer: nyne diff --git a/lib/components/comic.dart b/lib/components/comic.dart index 57353a1..8644995 100644 --- a/lib/components/comic.dart +++ b/lib/components/comic.dart @@ -550,7 +550,7 @@ class _ComicDescription extends StatelessWidget { int cnt = (constraints.maxHeight - 22).toInt() ~/ 25; return Container( clipBehavior: Clip.antiAlias, - height: 22 + cnt * 25, + height: 21 + cnt * 24, width: double.infinity, decoration: const BoxDecoration(), child: Wrap( @@ -562,31 +562,30 @@ class _ComicDescription extends StatelessWidget { children: [ for (var s in tags!) Container( - height: 22, - padding: const EdgeInsets.fromLTRB(3, 2, 3, 2), - constraints: BoxConstraints( - maxWidth: constraints.maxWidth * 0.45, + height: 21, + padding: const EdgeInsets.symmetric(horizontal: 4), + constraints: BoxConstraints( + maxWidth: constraints.maxWidth * 0.45, + ), + decoration: BoxDecoration( + color: s == "Unavailable" + ? context.colorScheme.errorContainer + : context.colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + widthFactor: 1, + child: Text( + enableTranslate + ? TagsTranslation.translateTag(s) + : s.split(':').last, + style: const TextStyle(fontSize: 12), + softWrap: true, + overflow: TextOverflow.ellipsis, + maxLines: 1, ), - decoration: BoxDecoration( - color: s == "Unavailable" - ? Theme.of(context).colorScheme.errorContainer - : Theme.of(context) - .colorScheme - .secondaryContainer, - borderRadius: - const BorderRadius.all(Radius.circular(8)), - ), - child: Center( - widthFactor: 1, - child: Text( - enableTranslate - ? TagsTranslation.translateTag(s) - : s.split(':').last, - style: const TextStyle(fontSize: 12), - softWrap: true, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ))), + ), + ), ], ), ).toAlign(Alignment.topCenter); @@ -1520,14 +1519,15 @@ class SimpleComicTile extends StatelessWidget { return AnimatedTapRegion( borderRadius: 8, - onTap: onTap ?? () { - context.to( - () => ComicPage( - id: comic.id, - sourceKey: comic.sourceKey, - ), - ); - }, + onTap: onTap ?? + () { + context.to( + () => ComicPage( + id: comic.id, + sourceKey: comic.sourceKey, + ), + ); + }, child: Container( width: 92, height: 114, diff --git a/lib/components/layout.dart b/lib/components/layout.dart index 51351c2..0238863 100644 --- a/lib/components/layout.dart +++ b/lib/components/layout.dart @@ -148,3 +148,18 @@ class SliverGridDelegateWithComics extends SliverGridDelegate { return false; } } + +class SliverLazyToBoxAdapter extends StatelessWidget { + /// Creates a sliver that contains a single box widget which can be lazy loaded. + const SliverLazyToBoxAdapter({super.key, required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return SliverList.list(children: [ + SizedBox(), + child, + ]); + } +} diff --git a/lib/components/message.dart b/lib/components/message.dart index 42b4374..777ec92 100644 --- a/lib/components/message.dart +++ b/lib/components/message.dart @@ -168,7 +168,15 @@ Future showConfirmDialog({ } class LoadingDialogController { - void Function()? closeDialog; + double? _progress; + + String? _message; + + void Function()? _closeDialog; + + void Function(double? value)? _serProgress; + + void Function(String message)? _setMessage; bool closed = false; @@ -177,63 +185,86 @@ class LoadingDialogController { return; } closed = true; - if (closeDialog == null) { - Future.microtask(closeDialog!); + if (_closeDialog == null) { + Future.microtask(_closeDialog!); } else { - closeDialog!(); + _closeDialog!(); } } + + void setProgress(double? value) { + if (closed) { + return; + } + _serProgress?.call(value); + } + + void setMessage(String message) { + if (closed) { + return; + } + _setMessage?.call(message); + } } -LoadingDialogController showLoadingDialog(BuildContext context, - {void Function()? onCancel, - bool barrierDismissible = true, - bool allowCancel = true, - String? message, - String cancelButtonText = "Cancel"}) { +LoadingDialogController showLoadingDialog( + BuildContext context, { + void Function()? onCancel, + bool barrierDismissible = true, + bool allowCancel = true, + String? message, + String cancelButtonText = "Cancel", + bool withProgress = false, +}) { var controller = LoadingDialogController(); + controller._message = message; + + if (withProgress) { + controller._progress = 0; + } var loadingDialogRoute = DialogRoute( - context: context, - barrierDismissible: barrierDismissible, - builder: (BuildContext context) { - return Dialog( - child: Container( - width: 100, - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - const SizedBox( - width: 30, - height: 30, - child: CircularProgressIndicator(), - ), - const SizedBox( - width: 16, - ), - Text( - message ?? 'Loading', - style: const TextStyle(fontSize: 16), - ), - const Spacer(), - if (allowCancel) - TextButton( - onPressed: () { - controller.close(); - onCancel?.call(); - }, - child: Text(cancelButtonText.tl)) - ], - ), - ), + context: context, + barrierDismissible: barrierDismissible, + builder: (BuildContext context) { + return StatefulBuilder(builder: (context, setState) { + controller._serProgress = (value) { + setState(() { + controller._progress = value; + }); + }; + controller._setMessage = (message) { + setState(() { + controller._message = message; + }); + }; + return ContentDialog( + title: controller._message ?? 'Loading', + content: LinearProgressIndicator( + value: controller._progress, + backgroundColor: context.colorScheme.surfaceContainer, + ).paddingHorizontal(16).paddingVertical(16), + actions: [ + FilledButton( + onPressed: allowCancel + ? () { + controller.close(); + onCancel?.call(); + } + : null, + child: Text(cancelButtonText.tl), + ) + ], ); }); + }, + ); var navigator = Navigator.of(context, rootNavigator: true); navigator.push(loadingDialogRoute).then((value) => controller.closed = true); - controller.closeDialog = () { + controller._closeDialog = () { navigator.removeRoute(loadingDialogRoute); }; @@ -444,9 +475,7 @@ Future showSelectDialog({ child: Text('Cancel'.tl), ), FilledButton( - onPressed: current == null - ? null - : context.pop, + onPressed: current == null ? null : context.pop, child: Text('Confirm'.tl), ), ], diff --git a/lib/foundation/app.dart b/lib/foundation/app.dart index 9ed44e8..56a99be 100644 --- a/lib/foundation/app.dart +++ b/lib/foundation/app.dart @@ -10,7 +10,7 @@ export "widget_utils.dart"; export "context.dart"; class _App { - final version = "1.2.3"; + final version = "1.2.4"; bool get isAndroid => Platform.isAndroid; @@ -52,7 +52,7 @@ class _App { BuildContext get rootContext => rootNavigatorKey.currentContext!; void rootPop() { - rootNavigatorKey.currentState?.pop(); + rootNavigatorKey.currentState?.maybePop(); } void pop() { diff --git a/lib/foundation/appdata.dart b/lib/foundation/appdata.dart index a50a78e..b68c39a 100644 --- a/lib/foundation/appdata.dart +++ b/lib/foundation/appdata.dart @@ -126,6 +126,7 @@ class _Settings with ChangeNotifier { 'explore_pages': [], 'categories': [], 'favorites': [], + 'searchSources': null, 'showFavoriteStatusOnTile': true, 'showHistoryStatusOnTile': false, 'blockedWords': [], diff --git a/lib/init.dart b/lib/init.dart index f3f4c0d..11c4bc3 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -42,11 +42,16 @@ Future init() async { await ComicSource.init().wait(); await LocalManager().init().wait(); CacheManager().setLimitSize(appdata.settings['cacheSize']); + if (appdata.settings['searchSources'] == null) { + appdata.settings['searchSources'] = ComicSource.all() + .where((e) => e.searchPageData != null) + .map((e) => e.key) + .toList(); + } if (App.isAndroid) { handleLinks(); } FlutterError.onError = (details) { - Log.error( - "Unhandled Exception", "${details.exception}\n${details.stack}"); + Log.error("Unhandled Exception", "${details.exception}\n${details.stack}"); }; -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 42c9c82..b823639 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -132,6 +132,38 @@ class _MyAppState extends State with WidgetsBindingObserver { }; } + ThemeData getTheme( + Color primary, + Color? secondary, + Color? tertiary, + Brightness brightness, + ) { + String? font; + List? fallback; + if (App.isWindows) { + font = 'Segoe UI'; + fallback = [ + 'Segoe UI', + 'Microsoft YaHei', + 'PingFang SC', + 'Noto Sans CJK', + 'Arial', + 'sans-serif' + ]; + } + return ThemeData( + colorScheme: SeedColorScheme.fromSeeds( + primaryKey: primary, + secondaryKey: secondary, + tertiaryKey: tertiary, + brightness: brightness, + tones: FlexTones.vividBackground(brightness), + ), + fontFamily: font, + fontFamilyFallback: fallback, + ); + } + @override Widget build(BuildContext context) { Widget home; @@ -158,24 +190,9 @@ class _MyAppState extends State with WidgetsBindingObserver { return MaterialApp( home: home, debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: SeedColorScheme.fromSeeds( - primaryKey: primary, - secondaryKey: secondary, - tertiaryKey: tertiary, - tones: FlexTones.vividBackground(Brightness.light), - ), - ), + theme: getTheme(primary, secondary, tertiary, Brightness.light), navigatorKey: App.rootNavigatorKey, - darkTheme: ThemeData( - colorScheme: SeedColorScheme.fromSeeds( - primaryKey: primary, - secondaryKey: secondary, - tertiaryKey: tertiary, - brightness: Brightness.dark, - tones: FlexTones.vividBackground(Brightness.dark), - ), - ), + darkTheme: getTheme(primary, secondary, tertiary, Brightness.dark), themeMode: switch (appdata.settings['theme_mode']) { 'light' => ThemeMode.light, 'dark' => ThemeMode.dark, diff --git a/lib/network/cloudflare.dart b/lib/network/cloudflare.dart index cf61de1..810bafa 100644 --- a/lib/network/cloudflare.dart +++ b/lib/network/cloudflare.dart @@ -152,6 +152,7 @@ void passCloudflare(CloudflareException e, void Function() onFinished) async { ); webview.open(); } else { + bool success = false; void check(InAppWebViewController controller) async { var head = await controller.evaluateJavascript( source: "document.head.innerHTML") as String; @@ -176,7 +177,10 @@ void passCloudflare(CloudflareException e, void Function() onFinished) async { return; } SingleInstanceCookieJar.instance?.saveFromResponse(uri, cookies); - App.rootPop(); + if (!success) { + App.rootPop(); + success = true; + } } } diff --git a/lib/pages/aggregated_search_page.dart b/lib/pages/aggregated_search_page.dart index 06093a4..48fc849 100644 --- a/lib/pages/aggregated_search_page.dart +++ b/lib/pages/aggregated_search_page.dart @@ -2,6 +2,7 @@ import "package:flutter/material.dart"; import 'package:shimmer_animation/shimmer_animation.dart'; import "package:venera/components/components.dart"; import "package:venera/foundation/app.dart"; +import "package:venera/foundation/appdata.dart"; import "package:venera/foundation/comic_source/comic_source.dart"; import "package:venera/pages/search_result_page.dart"; import "package:venera/utils/translations.dart"; @@ -24,7 +25,18 @@ class _AggregatedSearchPageState extends State { @override void initState() { - sources = ComicSource.all().where((e) => e.searchPageData != null).toList(); + var all = ComicSource.all() + .where((e) => e.searchPageData != null) + .map((e) => e.key) + .toList(); + var settings = appdata.settings['searchSources'] as List; + var sources = []; + for (var source in settings) { + if (all.contains(source)) { + sources.add(source); + } + } + this.sources = sources.map((e) => ComicSource.find(e)!).toList(); _keyword = widget.keyword; controller = SearchBarController( currentText: widget.keyword, diff --git a/lib/pages/comic_page.dart b/lib/pages/comic_page.dart index 7267d8e..6bf09da 100644 --- a/lib/pages/comic_page.dart +++ b/lib/pages/comic_page.dart @@ -206,62 +206,64 @@ class _ComicPageState extends LoadingState yield const SliverPadding(padding: EdgeInsets.only(top: 8)); - yield Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(width: 16), - Hero( - tag: "cover${comic.id}${comic.sourceKey}", - child: Container( - decoration: BoxDecoration( - color: context.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: context.colorScheme.outlineVariant, - blurRadius: 1, - offset: const Offset(0, 1), + yield SliverLazyToBoxAdapter( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(width: 16), + Hero( + tag: "cover${comic.id}${comic.sourceKey}", + child: Container( + decoration: BoxDecoration( + color: context.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: context.colorScheme.outlineVariant, + blurRadius: 1, + offset: const Offset(0, 1), + ), + ], + ), + height: 144, + width: 144 * 0.72, + clipBehavior: Clip.antiAlias, + child: AnimatedImage( + image: CachedImageProvider( + widget.cover ?? comic.cover, + sourceKey: comic.sourceKey, + cid: comic.id, + ), + width: double.infinity, + height: double.infinity, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText(comic.title, style: ts.s18), + if (comic.subTitle != null) + SelectableText(comic.subTitle!, style: ts.s14) + .paddingVertical(4), + Text( + (ComicSource.find(comic.sourceKey)?.name) ?? '', + style: ts.s12, ), ], ), - height: 144, - width: 144 * 0.72, - clipBehavior: Clip.antiAlias, - child: AnimatedImage( - image: CachedImageProvider( - widget.cover ?? comic.cover, - sourceKey: comic.sourceKey, - cid: comic.id, - ), - width: double.infinity, - height: double.infinity, - ), ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText(comic.title, style: ts.s18), - if (comic.subTitle != null) - SelectableText(comic.subTitle!, style: ts.s14) - .paddingVertical(4), - Text( - (ComicSource.find(comic.sourceKey)?.name) ?? '', - style: ts.s12, - ), - ], - ), - ), - ], - ).toSliver(); + ], + ), + ); } Widget buildActions() { bool isMobile = context.width < changePoint; bool hasHistory = history != null && (history!.ep > 1 || history!.page > 1); - return SliverToBoxAdapter( + return SliverLazyToBoxAdapter( child: Column( children: [ ListView( @@ -354,7 +356,7 @@ class _ComicPageState extends LoadingState if (comic.description == null || comic.description!.trim().isEmpty) { return const SliverPadding(padding: EdgeInsets.zero); } - return SliverToBoxAdapter( + return SliverLazyToBoxAdapter( child: Column( children: [ ListTile( @@ -482,7 +484,7 @@ class _ComicPageState extends LoadingState bool enableTranslation = App.locale.languageCode == 'zh' && comicSource.enableTagsTranslate; - return SliverToBoxAdapter( + return SliverLazyToBoxAdapter( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1388,42 +1390,67 @@ class _FavoritePanel extends StatefulWidget { State<_FavoritePanel> createState() => _FavoritePanelState(); } -class _FavoritePanelState extends State<_FavoritePanel> { +class _FavoritePanelState extends State<_FavoritePanel> + with SingleTickerProviderStateMixin { late ComicSource comicSource; + late TabController tabController; + + late bool hasNetwork; + @override void initState() { comicSource = widget.type.comicSource!; localFolders = LocalFavoritesManager().folderNames; added = LocalFavoritesManager().find(widget.cid, widget.type); + hasNetwork = comicSource.favoriteData != null && comicSource.isLogged; + var initIndex = 0; + if (appdata.implicitData['favoritePanelIndex'] is int) { + initIndex = appdata.implicitData['favoritePanelIndex']; + } + initIndex = initIndex.clamp(0, hasNetwork ? 1 : 0); + tabController = TabController( + initialIndex: initIndex, + length: hasNetwork ? 2 : 1, + vsync: this, + ); super.initState(); } + @override + void dispose() { + var currentIndex = tabController.index; + appdata.implicitData['favoritePanelIndex'] = currentIndex; + appdata.writeImplicitData(); + tabController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - var hasNetwork = comicSource.favoriteData != null && comicSource.isLogged; return Scaffold( appBar: Appbar( title: Text("Favorite".tl), ), - body: DefaultTabController( - length: hasNetwork ? 2 : 1, - child: Column( - children: [ - TabBar(tabs: [ + body: Column( + children: [ + TabBar( + controller: tabController, + tabs: [ Tab(text: "Local".tl), if (hasNetwork) Tab(text: "Network".tl), - ]), - Expanded( - child: TabBarView( - children: [ - buildLocal(), - if (hasNetwork) buildNetwork(), - ], - ), + ], + ), + Expanded( + child: TabBarView( + controller: tabController, + children: [ + buildLocal(), + if (hasNetwork) buildNetwork(), + ], ), - ], - ), + ), + ], ), ); } @@ -1850,7 +1877,7 @@ class _CommentsPartState extends State<_CommentsPart> { Widget build(BuildContext context) { return MultiSliver( children: [ - SliverToBoxAdapter( + SliverLazyToBoxAdapter( child: ListTile( title: Text("Comments".tl), trailing: Row( diff --git a/lib/pages/comments_page.dart b/lib/pages/comments_page.dart index 9490620..befc94f 100644 --- a/lib/pages/comments_page.dart +++ b/lib/pages/comments_page.dart @@ -42,7 +42,7 @@ class _CommentsPageState extends State { _error = res.errorMessage; _loading = false; }); - } else { + } else if (mounted) { setState(() { _comments = res.data; _loading = false; diff --git a/lib/pages/favorites/favorites_page.dart b/lib/pages/favorites/favorites_page.dart index 80445b2..e9dc5aa 100644 --- a/lib/pages/favorites/favorites_page.dart +++ b/lib/pages/favorites/favorites_page.dart @@ -16,6 +16,7 @@ import 'package:venera/foundation/res.dart'; import 'package:venera/network/download.dart'; import 'package:venera/pages/comic_page.dart'; import 'package:venera/pages/reader/reader.dart'; +import 'package:venera/pages/settings/settings_page.dart'; import 'package:venera/utils/io.dart'; import 'package:venera/utils/translations.dart'; diff --git a/lib/pages/favorites/side_bar.dart b/lib/pages/favorites/side_bar.dart index 9246836..f2535bb 100644 --- a/lib/pages/favorites/side_bar.dart +++ b/lib/pages/favorites/side_bar.dart @@ -20,22 +20,35 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { var networkFolders = []; + void findNetworkFolders() { + networkFolders.clear(); + var all = ComicSource.all() + .where((e) => e.favoriteData != null) + .map((e) => e.favoriteData!.key) + .toList(); + var settings = appdata.settings['favorites'] as List; + for (var p in settings) { + if (all.contains(p) && !networkFolders.contains(p)) { + networkFolders.add(p); + } + } + } + @override void initState() { favPage = widget.favPage ?? context.findAncestorStateOfType<_FavoritesPageState>()!; favPage.folderList = this; folders = LocalFavoritesManager().folderNames; - networkFolders = ComicSource.all() - .where((e) => e.favoriteData != null && e.isLogged) - .map((e) => e.favoriteData!.key) - .toList(); + findNetworkFolders(); + appdata.settings.addListener(updateFolders); super.initState(); } @override void dispose() { super.dispose(); + appdata.settings.removeListener(updateFolders); } @override @@ -102,7 +115,8 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { onClick: () { newFolder().then((value) { setState(() { - folders = LocalFavoritesManager().folderNames; + folders = + LocalFavoritesManager().folderNames; }); }); }, @@ -113,7 +127,8 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { onClick: () { sortFolders().then((value) { setState(() { - folders = LocalFavoritesManager().folderNames; + folders = + LocalFavoritesManager().folderNames; }); }); }, @@ -143,15 +158,24 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { ), child: Row( children: [ - const SizedBox(width: 16), Icon( Icons.cloud, color: context.colorScheme.secondary, ), const SizedBox(width: 12), Text("Network".tl), + const Spacer(), + IconButton( + icon: const Icon(Icons.settings), + onPressed: () { + showPopUpWidget( + App.rootContext, + setFavoritesPagesWidget(), + ); + }, + ), ], - ), + ).paddingHorizontal(16), ); } index--; @@ -241,10 +265,7 @@ class _LeftBarState extends State<_LeftBar> implements FolderList { if (!mounted) return; setState(() { folders = LocalFavoritesManager().folderNames; - networkFolders = ComicSource.all() - .where((e) => e.favoriteData != null) - .map((e) => e.favoriteData!.key) - .toList(); + findNetworkFolders(); }); } } diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index a8a0794..cf5b6b6 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -12,6 +12,7 @@ import 'package:venera/utils/epub.dart'; import 'package:venera/utils/io.dart'; import 'package:venera/utils/pdf.dart'; import 'package:venera/utils/translations.dart'; +import 'package:zip_flutter/zip_flutter.dart'; class LocalComicsPage extends StatefulWidget { const LocalComicsPage({super.key}); @@ -147,13 +148,13 @@ class _LocalComicsPageState extends State { text: "View Detail".tl, onClick: () { context.to(() => ComicPage( - id: selectedComics.keys.first.id, - sourceKey: selectedComics.keys.first.sourceKey, - )); + id: selectedComics.keys.first.id, + sourceKey: selectedComics.keys.first.sourceKey, + )); }, ), - if (selectedComics.length == 1) - ...exportActions(selectedComics.keys.first), + if (selectedComics.isNotEmpty) + ...exportActions(selectedComics.keys.toList()), ]); } @@ -322,7 +323,7 @@ class _LocalComicsPageState extends State { }); }, ), - ...exportActions(c as LocalComic), + ...exportActions([c as LocalComic]), ]; }, ), @@ -390,79 +391,102 @@ class _LocalComicsPageState extends State { return isDeleted; } - List exportActions(LocalComic c) { + List exportActions(List comics) { return [ MenuEntry( - icon: Icons.outbox_outlined, - text: "Export as cbz".tl, - onClick: () async { - var controller = showLoadingDialog( - context, - allowCancel: false, - ); - try { - var file = await CBZ.export(c); - await saveFile(filename: file.name, file: file); - await file.delete(); - } catch (e, s) { - context.showMessage(message: e.toString()); - Log.error("CBZ Export", e, s); - } - controller.close(); - }), + icon: Icons.outbox_outlined, + text: "Export as cbz".tl, + onClick: () { + exportComics(comics, CBZ.export, ".cbz"); + }, + ), MenuEntry( icon: Icons.picture_as_pdf_outlined, text: "Export as pdf".tl, onClick: () async { - var cache = FilePath.join(App.cachePath, 'temp.pdf'); - var controller = showLoadingDialog( - context, - allowCancel: false, - ); - try { - await createPdfFromComicIsolate( - comic: c, - savePath: cache, - ); - await saveFile( - file: File(cache), - filename: "${c.title}.pdf", - ); - } catch (e, s) { - Log.error("PDF Export", e, s); - context.showMessage(message: e.toString()); - } finally { - controller.close(); - File(cache).deleteIgnoreError(); - } + exportComics(comics, createPdfFromComicIsolate, ".pdf"); }, ), MenuEntry( icon: Icons.import_contacts_outlined, text: "Export as epub".tl, onClick: () async { - var controller = showLoadingDialog( - context, - allowCancel: false, - ); - File? file; - try { - file = await createEpubWithLocalComic( - c, - ); - await saveFile( - file: file, - filename: "${c.title}.epub", - ); - } catch (e, s) { - Log.error("EPUB Export", e, s); - context.showMessage(message: e.toString()); - } finally { - controller.close(); - file?.deleteIgnoreError(); - } + exportComics(comics, createEpubWithLocalComic, ".epub"); }, ) ]; } + + /// Export given comics to a file + void exportComics( + List comics, ExportComicFunc export, String ext) async { + var current = 0; + var cacheDir = FilePath.join(App.cachePath, 'comics_export'); + var outFile = FilePath.join(App.cachePath, 'comics_export.zip'); + bool canceled = false; + if (Directory(cacheDir).existsSync()) { + Directory(cacheDir).deleteSync(recursive: true); + } + Directory(cacheDir).createSync(); + var loadingController = showLoadingDialog( + context, + allowCancel: true, + message: "${"Exporting".tl} $current/${comics.length}", + withProgress: comics.length > 1, + onCancel: () { + canceled = true; + }, + ); + try { + var fileName = ""; + // For each comic, export it to a file + for (var comic in comics) { + fileName = FilePath.join(cacheDir, sanitizeFileName(comic.title) + ext); + await export(comic, fileName); + current++; + if (comics.length > 1) { + loadingController + .setMessage("${"Exporting".tl} $current/${comics.length}"); + loadingController.setProgress(current / comics.length); + } + if (canceled) { + return; + } + } + // For single comic, just save the file + if (comics.length == 1) { + await saveFile( + file: File(fileName), + filename: File(fileName).name, + ); + Directory(cacheDir).deleteSync(recursive: true); + loadingController.close(); + return; + } + // For multiple comics, compress the folder + loadingController.setProgress(null); + loadingController.setMessage("Compressing".tl); + await ZipFile.compressFolderAsync(cacheDir, outFile); + if (canceled) { + File(outFile).deleteIgnoreError(); + return; + } + } catch (e, s) { + Log.error("Export Comics", e, s); + context.showMessage(message: e.toString()); + loadingController.close(); + return; + } finally { + Directory(cacheDir).deleteIgnoreError(recursive: true); + } + await saveFile( + file: File(outFile), + filename: "comics_export.zip", + ); + loadingController.close(); + File(outFile).deleteIgnoreError(); + } } + +typedef ExportComicFunc = Future Function( + LocalComic comic, String outFilePath); diff --git a/lib/pages/search_page.dart b/lib/pages/search_page.dart index 6d59223..6258e0d 100644 --- a/lib/pages/search_page.dart +++ b/lib/pages/search_page.dart @@ -10,12 +10,14 @@ import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/state_controller.dart'; import 'package:venera/pages/aggregated_search_page.dart'; import 'package:venera/pages/search_result_page.dart'; +import 'package:venera/pages/settings/settings_page.dart'; import 'package:venera/utils/app_links.dart'; import 'package:venera/utils/ext.dart'; import 'package:venera/utils/tags_translation.dart'; import 'package:venera/utils/translations.dart'; import 'comic_page.dart'; +import 'comic_source_page.dart'; class SearchPage extends StatefulWidget { const SearchPage({super.key}); @@ -27,8 +29,13 @@ class SearchPage extends StatefulWidget { class _SearchPageState extends State { late final SearchBarController controller; + late List searchSources; + String searchTarget = ""; + SearchPageData get currentSearchPageData => + ComicSource.find(searchTarget)!.searchPageData!; + bool aggregatedSearch = false; var focusNode = FocusNode(); @@ -139,29 +146,85 @@ class _SearchPageState extends State { @override void initState() { + findSearchSources(); var defaultSearchTarget = appdata.settings['defaultSearchTarget']; if (defaultSearchTarget == "_aggregated_") { aggregatedSearch = true; } else if (defaultSearchTarget != null && - ComicSource.find(defaultSearchTarget) != null) { + searchSources.contains(defaultSearchTarget)) { searchTarget = defaultSearchTarget; - } else { - searchTarget = ComicSource.all().first.key; } controller = SearchBarController( onSearch: search, ); + appdata.settings.addListener(updateSearchSourcesIfNeeded); super.initState(); } @override void dispose() { focusNode.dispose(); + appdata.settings.removeListener(updateSearchSourcesIfNeeded); super.dispose(); } + void findSearchSources() { + var all = ComicSource.all() + .where((e) => e.searchPageData != null) + .map((e) => e.key) + .toList(); + var settings = appdata.settings['searchSources'] as List; + var sources = []; + for (var source in settings) { + if (all.contains(source)) { + sources.add(source); + } + } + searchSources = sources; + if (!searchSources.contains(searchTarget)) { + searchTarget = searchSources.firstOrNull ?? ""; + } + } + + void updateSearchSourcesIfNeeded() { + var old = searchSources; + findSearchSources(); + if (old.isEqualsTo(searchSources)) { + return; + } + setState(() {}); + } + + void manageSearchSources() { + showPopUpWidget(App.rootContext, setSearchSourcesWidget()); + } + + Widget buildEmpty() { + var msg = "No Search Sources".tl; + msg += '\n'; + VoidCallback onTap; + if (ComicSource.isEmpty) { + msg += "Please add some sources".tl; + onTap = () { + context.to(() => ComicSourcePage()); + }; + } else { + msg += "Please check your settings".tl; + onTap = manageSearchSources; + } + return NetworkError( + message: msg, + retry: onTap, + withAppbar: true, + buttonText: "Manage".tl, + ); + } + @override Widget build(BuildContext context) { + if (searchSources.isEmpty) { + return buildEmpty(); + } return Scaffold( body: SmoothCustomScrollView( slivers: buildSlivers().toList(), @@ -190,8 +253,7 @@ class _SearchPageState extends State { } Widget buildSearchTarget() { - var sources = - ComicSource.all().where((e) => e.searchPageData != null).toList(); + var sources = searchSources.map((e) => ComicSource.find(e)!).toList(); return SliverToBoxAdapter( child: Container( width: double.infinity, @@ -203,6 +265,10 @@ class _SearchPageState extends State { contentPadding: EdgeInsets.zero, leading: const Icon(Icons.search), title: Text("Search in".tl), + trailing: IconButton( + icon: const Icon(Icons.settings), + onPressed: manageSearchSources, + ), ), Wrap( spacing: 8, @@ -229,11 +295,6 @@ class _SearchPageState extends State { onChanged: (value) { setState(() { aggregatedSearch = value ?? false; - if (!aggregatedSearch && - appdata.settings['defaultSearchTarget'] == - "_aggregated_") { - searchTarget = sources.first.key; - } }); }, ), @@ -245,9 +306,7 @@ class _SearchPageState extends State { } void useDefaultOptions() { - final searchOptions = - ComicSource.find(searchTarget)!.searchPageData!.searchOptions ?? - []; + final searchOptions = currentSearchPageData.searchOptions ?? []; options = searchOptions.map((e) => e.defaultValue).toList(); } @@ -258,9 +317,7 @@ class _SearchPageState extends State { var children = []; - final searchOptions = - ComicSource.find(searchTarget)!.searchPageData!.searchOptions ?? - []; + final searchOptions = currentSearchPageData.searchOptions ?? []; if (searchOptions.length != options.length) { useDefaultOptions(); } @@ -394,7 +451,9 @@ class _SearchPageState extends State { Text( subTitle, style: TextStyle( - fontSize: 14, color: Theme.of(context).colorScheme.outline), + fontSize: 14, + color: Theme.of(context).colorScheme.outline, + ), ) ], ), diff --git a/lib/pages/search_result_page.dart b/lib/pages/search_result_page.dart index b5beac7..f174c14 100644 --- a/lib/pages/search_result_page.dart +++ b/lib/pages/search_result_page.dart @@ -116,13 +116,13 @@ class _SearchResultPageState extends State { @override void initState() { sourceKey = widget.sourceKey; + text = checkAutoLanguage(widget.text); controller = SearchBarController( - currentText: checkAutoLanguage(widget.text), + currentText: text, onSearch: search, ); options = widget.options ?? const []; validateOptions(); - text = widget.text; appdata.addSearchHistory(text); suggestionsController = _SuggestionsController(controller); super.initState(); diff --git a/lib/pages/settings/explore_settings.dart b/lib/pages/settings/explore_settings.dart index dc40018..957182e 100644 --- a/lib/pages/settings/explore_settings.dart +++ b/lib/pages/settings/explore_settings.dart @@ -38,19 +38,11 @@ class _ExploreSettingsState extends State { ).toSliver(), _PopupWindowSetting( title: "Network Favorite Pages".tl, - builder: () { - var pages = {}; - for (var c in ComicSource.all()) { - if (c.favoriteData != null) { - pages[c.favoriteData!.key] = c.favoriteData!.title; - } - } - return _MultiPagesFilter( - title: "Network Favorite Pages".tl, - settingsIndex: "favorites", - pages: pages, - ); - }, + builder: setFavoritesPagesWidget, + ).toSliver(), + _PopupWindowSetting( + title: "Search Sources".tl, + builder: setSearchSourcesWidget, ).toSliver(), _SwitchSetting( title: "Show favorite status on comic tile".tl, @@ -208,4 +200,32 @@ Widget setCategoryPagesWidget() { settingsIndex: "categories", pages: pages, ); +} + +Widget setFavoritesPagesWidget() { + var pages = {}; + for (var c in ComicSource.all()) { + if (c.favoriteData != null) { + pages[c.favoriteData!.key] = c.favoriteData!.title; + } + } + return _MultiPagesFilter( + title: "Network Favorite Pages".tl, + settingsIndex: "favorites", + pages: pages, + ); +} + +Widget setSearchSourcesWidget() { + var pages = {}; + for (var c in ComicSource.all()) { + if (c.searchPageData != null) { + pages[c.key] = c.name; + } + } + return _MultiPagesFilter( + title: "Search Sources".tl, + settingsIndex: "searchSources", + pages: pages, + ); } \ No newline at end of file diff --git a/lib/utils/cbz.dart b/lib/utils/cbz.dart index 16eee6a..d36d54a 100644 --- a/lib/utils/cbz.dart +++ b/lib/utils/cbz.dart @@ -85,6 +85,10 @@ abstract class CBZ { if (cache.existsSync()) cache.deleteSync(recursive: true); cache.createSync(); await extractArchive(file, cache); + var f = cache.listSync(); + if (f.length == 1 && f.first is Directory) { + cache = f.first as Directory; + } var metaDataFile = File(FilePath.join(cache.path, 'metadata.json')); ComicMetaData? metaData; if (metaDataFile.existsSync()) { @@ -171,7 +175,7 @@ abstract class CBZ { return comic; } - static Future export(LocalComic comic) async { + static Future export(LocalComic comic, String outFilePath) async { var cache = Directory(FilePath.join(App.cachePath, 'cbz_export')); if (cache.existsSync()) cache.deleteSync(recursive: true); cache.createSync(); @@ -230,7 +234,7 @@ abstract class CBZ { ).toJson(), ), ); - var cbz = File(FilePath.join(App.cachePath, sanitizeFileName('${comic.title}.cbz'))); + var cbz = File(outFilePath); if (cbz.existsSync()) cbz.deleteSync(); await _compress(cache.path, cbz.path); cache.deleteSync(recursive: true); diff --git a/lib/utils/data_sync.dart b/lib/utils/data_sync.dart index 7f6aebf..4ee6435 100644 --- a/lib/utils/data_sync.dart +++ b/lib/utils/data_sync.dart @@ -118,6 +118,7 @@ class DataSync with ChangeNotifier { await client.remove(files.first.name!); } await client.write(filename, await data.readAsBytes()); + data.deleteIgnoreError(); Log.info("Upload Data", "Data uploaded successfully"); return const Res(true); } catch (e, s) { diff --git a/lib/utils/epub.dart b/lib/utils/epub.dart index 7505f00..42764a1 100644 --- a/lib/utils/epub.dart +++ b/lib/utils/epub.dart @@ -24,7 +24,8 @@ class EpubData { }); } -Future createEpubComic(EpubData data, String cacheDir) async { +Future createEpubComic( + EpubData data, String cacheDir, String outFilePath) async { final workingDir = Directory(FilePath.join(cacheDir, 'epub')); if (workingDir.existsSync()) { workingDir.deleteSync(recursive: true); @@ -109,8 +110,7 @@ ${images.map((e) => ' $e').join('\n')} } // content.opf - final contentOpf = - File(FilePath.join(workingDir.path, 'content.opf')); + final contentOpf = File(FilePath.join(workingDir.path, 'content.opf')); final uuid = const Uuid().v4(); var spineStrBuilder = StringBuffer(); for (var i = 0; i < chapterIndex; i++) { @@ -171,16 +171,15 @@ ${navMapStrBuilder.toString()} '''); - // zip - final zipPath = FilePath.join(cacheDir, '${data.title}.epub'); - ZipFile.compressFolder(workingDir.path, zipPath); + ZipFile.compressFolder(workingDir.path, outFilePath); workingDir.deleteSync(recursive: true); - return File(zipPath); + return File(outFilePath); } -Future createEpubWithLocalComic(LocalComic comic) async { +Future createEpubWithLocalComic( + LocalComic comic, String outFilePath) async { var chapters = >{}; if (comic.chapters == null) { chapters[comic.title] = @@ -188,11 +187,11 @@ Future createEpubWithLocalComic(LocalComic comic) async { .map((e) => File(e)) .toList(); } else { - for (var chapter in comic.chapters!.keys) { - chapters[comic.chapters![chapter]!] = (await LocalManager() - .getImages(comic.id, comic.comicType, chapter)) - .map((e) => File(e)) - .toList(); + for (var chapter in comic.downloadedChapters) { + chapters[comic.chapters![chapter]!] = + (await LocalManager().getImages(comic.id, comic.comicType, chapter)) + .map((e) => File(e)) + .toList(); } } var data = EpubData( @@ -205,6 +204,6 @@ Future createEpubWithLocalComic(LocalComic comic) async { final cacheDir = App.cachePath; return Isolate.run(() => overrideIO(() async { - return createEpubComic(data, cacheDir); + return createEpubComic(data, cacheDir, outFilePath); })); } diff --git a/lib/utils/io.dart b/lib/utils/io.dart index dc6df55..d5ec62b 100644 --- a/lib/utils/io.dart +++ b/lib/utils/io.dart @@ -35,19 +35,9 @@ class FilePath { } extension FileSystemEntityExt on FileSystemEntity { + /// Get the base name of the file or directory. String get name { - var path = this.path; - if (path.endsWith('/') || path.endsWith('\\')) { - path = path.substring(0, path.length - 1); - } - - int i = path.length - 1; - - while (i >= 0 && path[i] != '\\' && path[i] != '/') { - i--; - } - - return path.substring(i + 1); + return p.basename(path); } Future deleteIgnoreError({bool recursive = false}) async { @@ -83,6 +73,10 @@ extension FileExtension on File { // Stream is not usable since [AndroidFile] does not support [openRead]. await newFile.writeAsBytes(await readAsBytes()); } + + String get basenameWithoutExt { + return p.basenameWithoutExtension(path); + } } extension DirectoryExtension on Directory { diff --git a/lib/utils/pdf.dart b/lib/utils/pdf.dart index 085ea1f..48da584 100644 --- a/lib/utils/pdf.dart +++ b/lib/utils/pdf.dart @@ -30,14 +30,14 @@ Future _createPdfFromComic({ files.removeWhere( (element) => element is! File || element.path.startsWith('cover')); files.sort((a, b) { - var aName = (a as File).name; - var bName = (b as File).name; + var aName = (a as File).basenameWithoutExt; + var bName = (b as File).basenameWithoutExt; var aNumber = int.tryParse(aName); var bNumber = int.tryParse(bName); if (aNumber != null && bNumber != null) { return aNumber.compareTo(bNumber); } - return aName.compareTo(bName); + return a.name.compareTo(b.name); }); } @@ -49,7 +49,7 @@ Future _createPdfFromComic({ images.add(file.path); } } else { - for (var chapter in comic.chapters!.keys) { + for (var chapter in comic.downloadedChapters) { var files = Directory(FilePath.join(baseDir, chapter)).listSync(); reorderFiles(files); for (var file in files) { @@ -112,10 +112,7 @@ Future _runIsolate( ); } -Future createPdfFromComicIsolate({ - required LocalComic comic, - required String savePath, -}) async { +Future createPdfFromComicIsolate(LocalComic comic, String savePath) async { var receivePort = ReceivePort(); SendPort? sendPort; Isolate? isolate; @@ -134,7 +131,8 @@ Future createPdfFromComicIsolate({ } }); isolate = await _runIsolate(comic, savePath, receivePort.sendPort); - return completer.future; + await completer.future; + return File(savePath); } class PdfGenerator { diff --git a/pubspec.lock b/pubspec.lock index 25c9852..6f5ba9f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1100,4 +1100,4 @@ packages: version: "0.0.10" sdks: dart: ">=3.6.0 <4.0.0" - flutter: ">=3.27.3" + flutter: ">=3.27.4" diff --git a/pubspec.yaml b/pubspec.yaml index 0624920..5f4e03e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,11 +2,11 @@ name: venera description: "A comic app." publish_to: 'none' -version: 1.2.3+123 +version: 1.2.4+124 environment: sdk: '>=3.6.0 <4.0.0' - flutter: 3.27.3 + flutter: 3.27.4 dependencies: flutter: @@ -82,7 +82,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^5.0.0 flutter_to_arch: ^1.0.1 - flutter_to_debian: + flutter_to_debian: ^2.0.2 flutter: uses-material-design: true diff --git a/windows/build.iss b/windows/build.iss index 15cc4d6..5faf1a6 100644 --- a/windows/build.iss +++ b/windows/build.iss @@ -3,11 +3,36 @@ #define MyAppName "Venera" #define MyAppVersion "{{version}}" -#define MyAppPublisher "wgh136" +#define MyAppPublisher "nyne" #define MyAppURL "https://github.com/venera-app/venera" #define MyAppExeName "venera.exe" #define RootPath "{{root_path}}" +[Code] +procedure CurStepChanged(CurStep: TSetupStep); +var + OldVersionPath, ShortcutPath: string; +begin + if CurStep = ssInstall then + begin + OldVersionPath := 'C:\Program Files (x86)\Venera'; + if DirExists(OldVersionPath) then + begin + DelTree(OldVersionPath, True, True, True); + ShortcutPath := GetEnv('USERPROFILE') + '\Desktop\Venera.lnk'; + if FileExists(ShortcutPath) then + begin + DeleteFile(ShortcutPath); + end; + ShortcutPath := 'C:\Users\Public\Desktop\Venera.lnk'; + if FileExists(ShortcutPath) then + begin + DeleteFile(ShortcutPath); + end; + end; + end; +end; + [Setup] ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) @@ -30,6 +55,8 @@ SetupIconFile={#RootPath}\windows\runner\resources\app_icon.ico Compression=lzma SolidCompression=yes WizardStyle=modern +ArchitecturesInstallIn64BitMode=x64compatible +ArchitecturesAllowed=x64compatible [Languages] Name: "english"; MessagesFile: "compiler:Default.isl"