mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-06 07:29:42 +00:00
Compare commits
6 Commits
6f3f571929
...
af8067bd92
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af8067bd92 | ||
|
|
ca6924f5a9 | ||
|
|
42e954428b | ||
|
|
8c1337d1fc | ||
|
|
94e704087f | ||
|
|
8e287ab1e5 |
69
lib/components/dialogs/link_open_permission_dialog.dart
Normal file
69
lib/components/dialogs/link_open_permission_dialog.dart
Normal file
@ -0,0 +1,69 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class LinkOpenPermissionDialog extends StatelessWidget {
|
||||
final String? href;
|
||||
const LinkOpenPermissionDialog({super.key, this.href});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 450),
|
||||
child: AlertDialog(
|
||||
title: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(SpotubeIcons.warning),
|
||||
Text(context.l10n.open_link_in_browser),
|
||||
],
|
||||
),
|
||||
content: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
"${context.l10n.do_you_want_to_open_the_following_link}:\n",
|
||||
),
|
||||
if (href != null)
|
||||
TextSpan(
|
||||
text: "$href\n\n",
|
||||
style: const TextStyle(color: Colors.blue),
|
||||
),
|
||||
TextSpan(text: context.l10n.unsafe_url_warning),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Button.ghost(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
Button.ghost(
|
||||
onPressed: () {
|
||||
if (href != null) {
|
||||
Clipboard.setData(ClipboardData(text: href!));
|
||||
}
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(context.l10n.copy_link),
|
||||
),
|
||||
Button.destructive(
|
||||
onPressed: () {
|
||||
if (href != null) {
|
||||
launchUrlString(
|
||||
href!,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(context.l10n.open),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/components/dialogs/link_open_permission_dialog.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class AppMarkdown extends StatelessWidget {
|
||||
@ -28,61 +26,7 @@ class AppMarkdown extends StatelessWidget {
|
||||
final allowOpeningLink = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 450),
|
||||
child: AlertDialog(
|
||||
title: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
const Icon(SpotubeIcons.warning),
|
||||
Text(context.l10n.open_link_in_browser),
|
||||
],
|
||||
),
|
||||
content: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
"${context.l10n.do_you_want_to_open_the_following_link}:\n",
|
||||
),
|
||||
if (href != null)
|
||||
TextSpan(
|
||||
text: "$href\n\n",
|
||||
style: const TextStyle(color: Colors.blue),
|
||||
),
|
||||
TextSpan(text: context.l10n.unsafe_url_warning),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Button.ghost(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(context.l10n.cancel),
|
||||
),
|
||||
Button.ghost(
|
||||
onPressed: () {
|
||||
if (href != null) {
|
||||
Clipboard.setData(ClipboardData(text: href));
|
||||
}
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(context.l10n.copy_link),
|
||||
),
|
||||
Button.destructive(
|
||||
onPressed: () {
|
||||
if (href != null) {
|
||||
launchUrlString(
|
||||
href,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(context.l10n.open),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return LinkOpenPermissionDialog(href: href);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -461,5 +461,6 @@
|
||||
"available_plugins": "Available plugins",
|
||||
"configure_your_own_metadata_plugin": "Configure your own playlist/album/artist/feed metadata provider",
|
||||
"audio_scrobblers": "Audio Scrobblers",
|
||||
"scrobbling": "Scrobbling"
|
||||
"scrobbling": "Scrobbling",
|
||||
"source": "Source: "
|
||||
}
|
||||
|
||||
@ -2930,6 +2930,12 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Scrobbling'**
|
||||
String get scrobbling;
|
||||
|
||||
/// No description provided for @source.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Source: '**
|
||||
String get source;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@ -1537,4 +1537,7 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'التتبع';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1538,4 +1538,7 @@ class AppLocalizationsBn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'স্ক্রোব্বলিং';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1548,4 +1548,7 @@ class AppLocalizationsCa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1538,4 +1538,7 @@ class AppLocalizationsCs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1550,4 +1550,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1536,4 +1536,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1551,4 +1551,7 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1548,4 +1548,7 @@ class AppLocalizationsEu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1536,4 +1536,7 @@ class AppLocalizationsFa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'اسکراببلینگ';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1536,4 +1536,7 @@ class AppLocalizationsFi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1556,4 +1556,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1542,4 +1542,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'स्क्रॉबलिंग';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1544,4 +1544,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1543,4 +1543,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1507,4 +1507,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1545,4 +1545,7 @@ class AppLocalizationsKa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'სქრობლინგი';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1511,4 +1511,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => '스크로블링';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1548,4 +1548,7 @@ class AppLocalizationsNe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'स्क्रब्बलिंग';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1542,4 +1542,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1544,4 +1544,7 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1541,4 +1541,7 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1544,4 +1544,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Скробблинг';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1550,4 +1550,7 @@ class AppLocalizationsTa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'ஸ்க்ரோப்ளிங்';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1533,4 +1533,7 @@ class AppLocalizationsTh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1551,4 +1551,7 @@ class AppLocalizationsTl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1544,4 +1544,7 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1540,4 +1540,7 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Скроблінг';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1546,4 +1546,7 @@ class AppLocalizationsVi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
@ -1500,6 +1500,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scrobbling => 'Scrobbling';
|
||||
|
||||
@override
|
||||
String get source => 'Source: ';
|
||||
}
|
||||
|
||||
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
||||
|
||||
@ -9,7 +9,7 @@ import 'package:spotube/provider/history/recent.dart';
|
||||
|
||||
class HomeRecentlyPlayedSection extends HookConsumerWidget {
|
||||
const HomeRecentlyPlayedSection({super.key});
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final history = ref.watch(recentlyPlayedItems);
|
||||
@ -20,17 +20,20 @@ class HomeRecentlyPlayedSection extends HookConsumerWidget {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final uniqueItems = <dynamic>{};
|
||||
final filteredItems = [
|
||||
for (final item in historyData)
|
||||
if (item.playlist != null && item.playlist?.id != null && uniqueItems.add(item.playlist!.id!))
|
||||
item.playlist
|
||||
else if (item.album != null && item.album?.id != null && uniqueItems.add(item.album?.id))
|
||||
item.album
|
||||
];
|
||||
|
||||
return Skeletonizer(
|
||||
enabled: history.isLoading,
|
||||
child: HorizontalPlaybuttonCardView(
|
||||
title: Text(context.l10n.recently_played),
|
||||
items: [
|
||||
for (final item in historyData)
|
||||
if (item.playlist != null)
|
||||
item.playlist
|
||||
else if (item.album != null)
|
||||
item.album
|
||||
],
|
||||
items: filteredItems,
|
||||
hasNextPage: false,
|
||||
isLoadingNextPage: false,
|
||||
onFetchMore: () {},
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
@ -8,6 +9,7 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:change_case/change_case.dart';
|
||||
|
||||
class MetadataPluginRepositoryItem extends HookConsumerWidget {
|
||||
final MetadataPluginRepository pluginRepo;
|
||||
@ -26,144 +28,180 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget {
|
||||
final isInstalling = useState(false);
|
||||
|
||||
return Card(
|
||||
child: Basic(
|
||||
title: Text(
|
||||
"${pluginRepo.owner == "KRTirtho" ? "" : "${pluginRepo.owner}/"}${pluginRepo.name}"),
|
||||
subtitle: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(pluginRepo.description),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (pluginRepo.owner == "KRTirtho") ...[
|
||||
PrimaryBadge(
|
||||
leading: Icon(SpotubeIcons.done),
|
||||
child: Text(context.l10n.official),
|
||||
),
|
||||
SecondaryBadge(
|
||||
leading: host == "github.com"
|
||||
? const Icon(SpotubeIcons.github)
|
||||
: null,
|
||||
child: Text(host),
|
||||
onPressed: () {
|
||||
launchUrlString(pluginRepo.repoUrl);
|
||||
},
|
||||
),
|
||||
] else ...[
|
||||
Text(context.l10n.author_name(pluginRepo.owner)),
|
||||
DestructiveBadge(
|
||||
leading: const Icon(SpotubeIcons.warning),
|
||||
child: Text(context.l10n.third_party),
|
||||
)
|
||||
]
|
||||
],
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Basic(
|
||||
title: Text(
|
||||
pluginRepo.name.startsWith("spotube-plugin")
|
||||
? pluginRepo.name
|
||||
.replaceFirst("spotube-plugin-", "")
|
||||
.trim()
|
||||
.toCapitalCase()
|
||||
: pluginRepo.name.toCapitalCase(),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Button.primary(
|
||||
enabled: !isInstalling.value,
|
||||
onPressed: () async {
|
||||
try {
|
||||
isInstalling.value = true;
|
||||
final pluginConfig = await pluginsNotifier
|
||||
.downloadAndCachePlugin(pluginRepo.repoUrl);
|
||||
subtitle: Text(pluginRepo.description),
|
||||
trailing: Button.primary(
|
||||
enabled: !isInstalling.value,
|
||||
onPressed: () async {
|
||||
try {
|
||||
isInstalling.value = true;
|
||||
final pluginConfig = await pluginsNotifier
|
||||
.downloadAndCachePlugin(pluginRepo.repoUrl);
|
||||
|
||||
if (!context.mounted) return;
|
||||
final isOfficialPlugin = pluginRepo.owner == "KRTirtho";
|
||||
if (!context.mounted) return;
|
||||
final isOfficialPlugin = pluginRepo.owner == "KRTirtho";
|
||||
|
||||
final isAllowed = isOfficialPlugin
|
||||
? true
|
||||
: await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final pluginAbilities = pluginConfig.apis
|
||||
.map(
|
||||
(e) => context.l10n.can_access_name_api(e.name))
|
||||
.join("\n\n");
|
||||
final isAllowed = isOfficialPlugin
|
||||
? true
|
||||
: await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final pluginAbilities = pluginConfig.apis
|
||||
.map((e) =>
|
||||
context.l10n.can_access_name_api(e.name))
|
||||
.join("\n\n");
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
context.l10n.do_you_want_to_install_this_plugin),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(context.l10n.third_party_plugin_warning),
|
||||
const Gap(8),
|
||||
FutureBuilder(
|
||||
future:
|
||||
pluginsNotifier.getLogoPath(pluginConfig),
|
||||
builder: (context, snapshot) {
|
||||
return Basic(
|
||||
leading: snapshot.hasData
|
||||
? Image.file(
|
||||
snapshot.data!,
|
||||
width: 36,
|
||||
height: 36,
|
||||
)
|
||||
: Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: context
|
||||
.theme.colorScheme.secondary,
|
||||
borderRadius:
|
||||
BorderRadius.circular(8),
|
||||
),
|
||||
child:
|
||||
const Icon(SpotubeIcons.plugin),
|
||||
),
|
||||
title: Text(pluginConfig.name),
|
||||
subtitle: Text(pluginConfig.description),
|
||||
);
|
||||
},
|
||||
return AlertDialog(
|
||||
title: Text(context
|
||||
.l10n.do_you_want_to_install_this_plugin),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(context.l10n.third_party_plugin_warning),
|
||||
const Gap(8),
|
||||
FutureBuilder(
|
||||
future: pluginsNotifier
|
||||
.getLogoPath(pluginConfig),
|
||||
builder: (context, snapshot) {
|
||||
return Basic(
|
||||
leading: snapshot.hasData
|
||||
? Image.file(
|
||||
snapshot.data!,
|
||||
width: 36,
|
||||
height: 36,
|
||||
)
|
||||
: Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: context.theme
|
||||
.colorScheme.secondary,
|
||||
borderRadius:
|
||||
BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
SpotubeIcons.plugin),
|
||||
),
|
||||
title: Text(pluginConfig.name),
|
||||
subtitle:
|
||||
Text(pluginConfig.description),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Gap(8),
|
||||
AppMarkdown(
|
||||
data:
|
||||
"**${context.l10n.author}**: ${pluginConfig.author}\n\n"
|
||||
"**${context.l10n.repository}**: [${pluginConfig.repository ?? 'N/A'}](${pluginConfig.repository})\n\n\n\n"
|
||||
"${context.l10n.this_plugin_can_do_following}:\n\n"
|
||||
"$pluginAbilities",
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
AppMarkdown(
|
||||
data:
|
||||
"**${context.l10n.author}**: ${pluginConfig.author}\n\n"
|
||||
"**${context.l10n.repository}**: [${pluginConfig.repository ?? 'N/A'}](${pluginConfig.repository})\n\n\n\n"
|
||||
"${context.l10n.this_plugin_can_do_following}:\n\n"
|
||||
"$pluginAbilities",
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Button.secondary(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(context.l10n.decline),
|
||||
),
|
||||
Button.primary(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(context.l10n.accept),
|
||||
),
|
||||
],
|
||||
actions: [
|
||||
Button.secondary(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(context.l10n.decline),
|
||||
),
|
||||
Button.primary(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(context.l10n.accept),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (isAllowed != true) return;
|
||||
await pluginsNotifier.addPlugin(pluginConfig);
|
||||
} finally {
|
||||
if (context.mounted) {
|
||||
isInstalling.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
leading: isInstalling.value
|
||||
? const CircularProgressIndicator()
|
||||
: const Icon(SpotubeIcons.add),
|
||||
child: Text(context.l10n.install),
|
||||
),
|
||||
if (isAllowed != true) return;
|
||||
await pluginsNotifier.addPlugin(pluginConfig);
|
||||
} finally {
|
||||
if (context.mounted) {
|
||||
isInstalling.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
leading: isInstalling.value
|
||||
? SizedBox.square(
|
||||
dimension: 20,
|
||||
child: CircularProgressIndicator(
|
||||
color: context.theme.colorScheme.primaryForeground,
|
||||
),
|
||||
)
|
||||
: const Icon(SpotubeIcons.add),
|
||||
child: Text(context.l10n.install),
|
||||
),
|
||||
),
|
||||
if (pluginRepo.owner != "KRTirtho")
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: context.l10n.source),
|
||||
TextSpan(
|
||||
text: pluginRepo.repoUrl.replaceAll("https://", ""),
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
launchUrlString(pluginRepo.repoUrl);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
style: context.theme.typography.xSmall,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
if (pluginRepo.owner == "KRTirtho")
|
||||
PrimaryBadge(
|
||||
leading: const Icon(SpotubeIcons.done),
|
||||
child: Text(context.l10n.official),
|
||||
)
|
||||
else ...[
|
||||
Text(
|
||||
context.l10n.author_name(pluginRepo.owner),
|
||||
style: context.theme.typography.xSmall,
|
||||
),
|
||||
DestructiveBadge(
|
||||
leading: const Icon(SpotubeIcons.warning),
|
||||
child: Text(context.l10n.third_party),
|
||||
),
|
||||
],
|
||||
SecondaryBadge(
|
||||
leading: host == "github.com"
|
||||
? const Icon(SpotubeIcons.github)
|
||||
: null,
|
||||
child: Text(host),
|
||||
onPressed: () {
|
||||
launchUrlString(pluginRepo.repoUrl);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -350,6 +350,8 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
|
||||
abilities: plugin.abilities.map((e) => e.name).toList(),
|
||||
pluginApiVersion: Value(plugin.pluginApiVersion),
|
||||
repository: Value(plugin.repository),
|
||||
// Setting the very first plugin as the default plugin
|
||||
selected: Value(state.valueOrNull?.plugins.isEmpty ?? true),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -362,6 +364,17 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
|
||||
}
|
||||
await database.metadataPluginsTable.deleteWhere((tbl) =>
|
||||
tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author));
|
||||
|
||||
// Same here, if the removed plugin is the default plugin
|
||||
// set the first available plugin as the default plugin
|
||||
// only when there is 1 remaining plugin
|
||||
if (state.valueOrNull?.defaultPluginConfig == plugin) {
|
||||
final remainingPlugins =
|
||||
state.valueOrNull?.plugins.where((p) => p != plugin) ?? [];
|
||||
if (remainingPlugins.length == 1) {
|
||||
await setDefaultPlugin(remainingPlugins.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updatePlugin(
|
||||
|
||||
@ -315,7 +315,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
change_case:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: change_case
|
||||
sha256: f4e08feaa845e75e4f5ad2b0e15f24813d7ea6c27e7b78252f0c17f752cf1157
|
||||
|
||||
@ -163,6 +163,7 @@ dependencies:
|
||||
get_it: ^8.0.3
|
||||
flutter_markdown_plus: ^1.0.3
|
||||
pub_semver: ^2.2.0
|
||||
change_case: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.13
|
||||
|
||||
@ -1,5 +1,118 @@
|
||||
{
|
||||
"ar": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"bn": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ca": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"cs": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"de": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"hi": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ka": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ne": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
"audio_source"
|
||||
"audio_source",
|
||||
"source"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"ta": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"th": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"tl": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"vi": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"source"
|
||||
],
|
||||
|
||||
"zh_TW": [
|
||||
"source"
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user