Compare commits

..

No commits in common. "fda2257119a57116315ab2a84b23e81a559b13fb" and "e1fa9efa14ef6a95d55a57089e9fccad1bb90d61" have entirely different histories.

18 changed files with 111 additions and 139 deletions

View File

@ -12,10 +12,10 @@ on:
type: boolean type: boolean
default: true default: true
jobs: jobs:
description: Jobs to run (flathub,aur,winget,chocolatey) description: Jobs to run (flathub,aur,winget,chocolatey,playstore)
required: true required: true
type: string type: string
default: "flathub,aur,winget,chocolatey" default: "flathub,aur,winget,chocolatey,playstore"
jobs: jobs:
flathub: flathub:
@ -112,26 +112,26 @@ jobs:
- name: Tagname (workflow dispatch) - name: Tagname (workflow dispatch)
run: echo 'TAG_NAME=${{inputs.version}}' >> $GITHUB_ENV run: echo 'TAG_NAME=${{inputs.version}}' >> $GITHUB_ENV
# - uses: robinraju/release-downloader@main - uses: robinraju/release-downloader@main
# with: with:
# repository: KRTirtho/spotube repository: KRTirtho/spotube
# tag: v${{ env.TAG_NAME }} tag: v${{ env.TAG_NAME }}
# tarBall: false tarBall: false
# zipBall: false zipBall: false
# out-file-path: dist out-file-path: dist
# fileName: "Spotube-playstore-all-arch.aab" fileName: "Spotube-playstore-all-arch.aab"
# - name: Create service-account.json - name: Create service-account.json
# run: | run: |
# echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_BASE64 }}" | base64 -d > service-account.json echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_BASE64 }}" | base64 -d > service-account.json
# - name: Upload Android Release to Play Store - name: Upload Android Release to Play Store
# if: ${{!inputs.dry_run}} if: ${{!inputs.dry_run}}
# uses: r0adkll/upload-google-play@v1 uses: r0adkll/upload-google-play@v1
# with: with:
# serviceAccountJson: ./service-account.json serviceAccountJson: ./service-account.json
# releaseFiles: ./dist/Spotube-playstore-all-arch.aab releaseFiles: ./dist/Spotube-playstore-all-arch.aab
# packageName: oss.krtirtho.spotube packageName: oss.krtirtho.spotube
# track: production track: production
# status: draft status: draft
# releaseName: ${{ env.TAG_NAME }} releaseName: ${{ env.TAG_NAME }}

View File

@ -49,6 +49,7 @@ jobs:
arch: all arch: all
files: | files: |
build/Spotube-android-all-arch.apk build/Spotube-android-all-arch.apk
build/Spotube-playstore-all-arch.aab
- os: windows-latest - os: windows-latest
platform: windows platform: windows
arch: x86 arch: x86
@ -76,14 +77,6 @@ jobs:
cache: true cache: true
git-source: https://github.com/flutter/flutter.git git-source: https://github.com/flutter/flutter.git
- name: free disk space
if: ${{ matrix.platform == 'android' }}
run: |
sudo swapoff -a
sudo rm -f /swapfile
sudo apt clean
docker rmi $(docker image ls -aq)
df -h
- name: Setup Java - name: Setup Java
if: ${{matrix.platform == 'android'}} if: ${{matrix.platform == 'android'}}
uses: actions/setup-java@v4 uses: actions/setup-java@v4

View File

@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:collection/collection.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:xml/xml.dart';
import '../../core/env.dart'; import '../../core/env.dart';
import 'common.dart'; import 'common.dart';
@ -22,6 +24,39 @@ class AndroidBuildCommand extends Command with BuildCommandCommonSteps {
"flutter build apk --flavor ${CliEnv.channel.name}", "flutter build apk --flavor ${CliEnv.channel.name}",
); );
await dotEnvFile.writeAsString(
"\nENABLE_UPDATE_CHECK=0"
"\nHIDE_DONATIONS=1",
mode: FileMode.append,
);
final androidManifestFile = File(
join(cwd.path, "android", "app", "src", "main", "AndroidManifest.xml"));
final androidManifestXml =
XmlDocument.parse(await androidManifestFile.readAsString());
final deletingElement =
androidManifestXml.findAllElements("meta-data").firstWhereOrNull(
(el) =>
el.getAttribute("android:name") ==
"com.google.android.gms.car.application",
);
deletingElement?.parent?.children.remove(deletingElement);
await androidManifestFile.writeAsString(
androidManifestXml.toXmlString(pretty: true),
);
await shell.run(
"""
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs
flutter build appbundle --flavor ${CliEnv.channel.name}
""",
);
final ogApkFile = File( final ogApkFile = File(
join( join(
"build", "build",
@ -36,6 +71,22 @@ class AndroidBuildCommand extends Command with BuildCommandCommonSteps {
join(cwd.path, "build", "Spotube-android-all-arch.apk"), join(cwd.path, "build", "Spotube-android-all-arch.apk"),
); );
final ogAppbundleFile = File(
join(
cwd.path,
"build",
"app",
"outputs",
"bundle",
"${CliEnv.channel.name}Release",
"app-${CliEnv.channel.name}-release.aab",
),
);
await ogAppbundleFile.copy(
join(cwd.path, "build", "Spotube-playstore-all-arch.aab"),
);
stdout.writeln("✅ Built Android Apk and Appbundle"); stdout.writeln("✅ Built Android Apk and Appbundle");
} }
} }

View File

@ -83,8 +83,6 @@ Future<void> main(List<String> rawArgs) async {
// force High Refresh Rate on some Android devices (like One Plus) // force High Refresh Rate on some Android devices (like One Plus)
if (kIsAndroid) { if (kIsAndroid) {
await FlutterDisplayMode.setHighRefreshRate(); await FlutterDisplayMode.setHighRefreshRate();
}
if (kIsAndroid || kIsDesktop) {
await NewPipeExtractor.init(); await NewPipeExtractor.init();
} }

View File

@ -19,10 +19,10 @@ import 'package:spotube/services/kv_store/kv_store.dart';
import 'package:flutter/widgets.dart' hide Table, Key, View; import 'package:flutter/widgets.dart' hide Table, Key, View;
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/youtube_engine/newpipe_engine.dart'; import 'package:spotube/services/youtube_engine/newpipe_engine.dart';
import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart'; import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart';
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart'; import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
import 'package:spotube/utils/platform.dart';
import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
@ -212,14 +212,26 @@ class AppDatabase extends _$AppDatabase {
); );
}, },
from9To10: (m, schema) async { from9To10: (m, schema) async {
await m.dropColumn(schema.preferencesTable, "piped_instance"); try {
await m.dropColumn(schema.preferencesTable, "invidious_instance"); await m
await m.addColumn( .dropColumn(schema.preferencesTable, "piped_instance")
schema.sourceMatchTable, .catchError((e) {});
sourceMatchTable.sourceInfo, await m
); .dropColumn(schema.preferencesTable, "invidious_instance")
await customStatement("DROP INDEX IF EXISTS uniq_track_match;"); .catchError((e) {});
await m.dropColumn(schema.sourceMatchTable, "source_id"); await m
.addColumn(
schema.sourceMatchTable,
sourceMatchTable.sourceInfo,
)
.catchError((e) {});
await m
.dropColumn(schema.sourceMatchTable, "source_id")
.catchError((e) {});
} catch (e) {
AppLogger.log.e(e);
return;
}
}, },
), ),
); );

View File

@ -111,9 +111,7 @@ class PreferencesTable extends Table {
localLibraryLocation: [], localLibraryLocation: [],
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
audioSourceId: null, audioSourceId: null,
youtubeClientEngine: kIsIOS youtubeClientEngine: YoutubeClientEngine.youtubeExplode,
? YoutubeClientEngine.youtubeExplode
: YoutubeClientEngine.newPipe,
discordPresence: true, discordPresence: true,
endlessPlayback: true, endlessPlayback: true,
enableConnect: false, enableConnect: false,

View File

@ -1,5 +1,10 @@
part of '../database.dart'; part of '../database.dart';
@TableIndex(
name: "uniq_track_match",
columns: {#trackId, #sourceInfo, #sourceType},
unique: true,
)
class SourceMatchTable extends Table { class SourceMatchTable extends Table {
IntColumn get id => integer().autoIncrement()(); IntColumn get id => integer().autoIncrement()();
TextColumn get trackId => text()(); TextColumn get trackId => text()();

View File

@ -4,7 +4,6 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -102,11 +101,7 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
final plugins = await database.pluginsTable.select().get(); final plugins = await database.pluginsTable.select().get();
final pluginState = await toStatePlugins(plugins); return await toStatePlugins(plugins);
await _loadDefaultPlugins(pluginState);
return pluginState;
} }
Future<MetadataPluginState> toStatePlugins( Future<MetadataPluginState> toStatePlugins(
@ -176,45 +171,6 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
); );
} }
Future<void> _loadDefaultPlugins(MetadataPluginState pluginState) async {
const plugins = [
"spotube-plugin-musicbrainz-listenbrainz",
"spotube-plugin-youtube-audio",
];
for (final plugin in plugins) {
final byteData = await rootBundle.load(
"assets/plugins/$plugin/plugin.smplug",
);
final pluginConfig =
await extractPluginArchive(byteData.buffer.asUint8List());
try {
await addPlugin(pluginConfig);
} on MetadataPluginException catch (e) {
if (e.errorCode == MetadataPluginErrorCode.duplicatePlugin &&
await isPluginUpdate(pluginConfig)) {
final oldConfig = pluginState.plugins
.firstWhereOrNull((p) => p.slug == pluginConfig.slug);
if (oldConfig == null) continue;
final isDefaultMetadata =
oldConfig == pluginState.defaultMetadataPluginConfig;
final isDefaultAudioSource =
oldConfig == pluginState.defaultAudioSourcePluginConfig;
await removePlugin(pluginConfig);
await addPlugin(pluginConfig);
if (isDefaultMetadata) {
await setDefaultMetadataPlugin(pluginConfig);
}
if (isDefaultAudioSource) {
await setDefaultAudioSourcePlugin(pluginConfig);
}
}
}
}
}
Uri _getGithubReleasesUrl(String repoUrl) { Uri _getGithubReleasesUrl(String repoUrl) {
final parsedUri = Uri.parse(repoUrl); final parsedUri = Uri.parse(repoUrl);
final uri = parsedUri.replace( final uri = parsedUri.replace(
@ -417,19 +373,11 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
repository: Value(plugin.repository), repository: Value(plugin.repository),
// Setting the very first plugin as the default plugin // Setting the very first plugin as the default plugin
selectedForMetadata: Value( selectedForMetadata: Value(
(state.valueOrNull?.plugins (state.valueOrNull?.plugins.isEmpty ?? true) &&
.where(
(d) => d.abilities.contains(PluginAbilities.metadata))
.isEmpty ??
true) &&
plugin.abilities.contains(PluginAbilities.metadata), plugin.abilities.contains(PluginAbilities.metadata),
), ),
selectedForAudioSource: Value( selectedForAudioSource: Value(
(state.valueOrNull?.plugins (state.valueOrNull?.plugins.isEmpty ?? true) &&
.where((d) =>
d.abilities.contains(PluginAbilities.audioSource))
.isEmpty ??
true) &&
plugin.abilities.contains(PluginAbilities.audioSource), plugin.abilities.contains(PluginAbilities.audioSource),
), ),
), ),
@ -472,27 +420,6 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
} }
} }
Future<bool> isPluginUpdate(PluginConfiguration newPlugin) async {
final pluginRes = await (database.pluginsTable.select()
..where(
(tbl) =>
tbl.name.equals(newPlugin.name) &
tbl.author.equals(newPlugin.author),
)
..limit(1))
.get();
if (pluginRes.isEmpty) {
return false;
}
final oldPlugin = pluginRes.first;
final oldPluginApiVersion = Version.parse(oldPlugin.pluginApiVersion);
final newPluginApiVersion = Version.parse(newPlugin.pluginApiVersion);
return newPluginApiVersion > oldPluginApiVersion;
}
Future<void> updatePlugin( Future<void> updatePlugin(
PluginConfiguration plugin, PluginConfiguration plugin,
PluginUpdateAvailable update, PluginUpdateAvailable update,

View File

@ -6,7 +6,7 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
class NewPipeEngine implements YouTubeEngine { class NewPipeEngine implements YouTubeEngine {
static bool get isAvailableForPlatform => kIsAndroid || kIsDesktop; static bool get isAvailableForPlatform => kIsAndroid;
AudioOnlyStreamInfo _parseAudioStream(AudioStream stream, String videoId) { AudioOnlyStreamInfo _parseAudioStream(AudioStream stream, String videoId) {
return AudioOnlyStreamInfo( return AudioOnlyStreamInfo(

View File

@ -8,7 +8,6 @@
#include <desktop_webview_window/desktop_webview_window_plugin.h> #include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_new_pipe_extractor/flutter_new_pipe_extractor_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <flutter_timezone/flutter_timezone_plugin.h> #include <flutter_timezone/flutter_timezone_plugin.h>
#include <gtk/gtk_plugin.h> #include <gtk/gtk_plugin.h>
@ -31,9 +30,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_new_pipe_extractor_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterNewPipeExtractorPlugin");
flutter_new_pipe_extractor_plugin_register_with_registrar(flutter_new_pipe_extractor_registrar);
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);

View File

@ -5,7 +5,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_window desktop_webview_window
file_selector_linux file_selector_linux
flutter_new_pipe_extractor
flutter_secure_storage_linux flutter_secure_storage_linux
flutter_timezone flutter_timezone
gtk gtk

View File

@ -15,7 +15,6 @@ import device_info_plus
import file_picker import file_picker
import file_selector_macos import file_selector_macos
import flutter_inappwebview_macos import flutter_inappwebview_macos
import flutter_new_pipe_extractor
import flutter_secure_storage_macos import flutter_secure_storage_macos
import flutter_timezone import flutter_timezone
import irondash_engine_context import irondash_engine_context
@ -45,7 +44,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
FlutterNewPipeExtractorPlugin.register(with: registry.registrar(forPlugin: "FlutterNewPipeExtractorPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))

View File

@ -955,7 +955,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: "898fd4ebcef77f5177b08aa6f9b9047bd02c6b9b" resolved-ref: d4d71545111c8ca6c91f0040091c42d74cce1762
url: "https://github.com/KRTirtho/flutter_new_pipe_extractor.git" url: "https://github.com/KRTirtho/flutter_new_pipe_extractor.git"
source: git source: git
version: "0.1.0" version: "0.1.0"
@ -2002,10 +2002,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: random_user_agents name: random_user_agents
sha256: "95647149687167e82a7b39e1b4616fdebb574981b71b6f0cfca21b69f36293a8" sha256: "19facde509a2482dababb454faf2aceff797a6ae08e80f91268c0c8a7420f03b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.17" version: "1.0.15"
recase: recase:
dependency: transitive dependency: transitive
description: description:
@ -2821,7 +2821,7 @@ packages:
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml: xml:
dependency: transitive dependency: "direct dev"
description: description:
name: xml name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226

View File

@ -177,6 +177,7 @@ dev_dependencies:
process_run: ^0.14.2 process_run: ^0.14.2
pubspec_parse: ^1.3.0 pubspec_parse: ^1.3.0
pub_api_client: ^3.0.0 pub_api_client: ^3.0.0
xml: ^6.5.0
io: ^1.0.4 io: ^1.0.4
drift_dev: ^2.21.0 drift_dev: ^2.21.0
auto_route_generator: ^9.0.0 auto_route_generator: ^9.0.0
@ -227,8 +228,6 @@ flutter:
- assets/branding/spotube-logo.png - assets/branding/spotube-logo.png
- assets/branding/spotube-logo-light.png - assets/branding/spotube-logo-light.png
- assets/branding/spotube-logo.ico - assets/branding/spotube-logo.ico
- assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug
- assets/plugins/spotube-plugin-youtube-audio/plugin.smplug
- LICENSE - LICENSE
- packages/flutter_undraw/assets/undraw/access_denied.svg - packages/flutter_undraw/assets/undraw/access_denied.svg
- packages/flutter_undraw/assets/undraw/fixing_bugs.svg - packages/flutter_undraw/assets/undraw/fixing_bugs.svg

View File

@ -12,7 +12,6 @@
#include <desktop_webview_window/desktop_webview_window_plugin.h> #include <desktop_webview_window/desktop_webview_window_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h> #include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
#include <flutter_new_pipe_extractor/flutter_new_pipe_extractor_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_timezone/flutter_timezone_plugin_c_api.h> #include <flutter_timezone/flutter_timezone_plugin_c_api.h>
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h> #include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
@ -40,8 +39,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
FlutterNewPipeExtractorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterNewPipeExtractorPluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
FlutterTimezonePluginCApiRegisterWithRegistrar( FlutterTimezonePluginCApiRegisterWithRegistrar(

View File

@ -9,7 +9,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_window desktop_webview_window
file_selector_windows file_selector_windows
flutter_inappwebview_windows flutter_inappwebview_windows
flutter_new_pipe_extractor
flutter_secure_storage_windows flutter_secure_storage_windows
flutter_timezone flutter_timezone
irondash_engine_context irondash_engine_context