mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
feat: custom piped & invidious instance support
This commit is contained in:
parent
c3bbc129ad
commit
91871d0d26
@ -8,6 +8,7 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
final Widget title;
|
||||
final Widget? subtitle;
|
||||
final Widget? secondary;
|
||||
final List<Widget>? trailing;
|
||||
final ListTileControlAffinity? controlAffinity;
|
||||
final T value;
|
||||
final ValueChanged<T?>? onChanged;
|
||||
@ -34,6 +35,7 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
this.controlAffinity = ListTileControlAffinity.trailing,
|
||||
this.subtitle,
|
||||
this.secondary,
|
||||
this.trailing,
|
||||
this.breakLayout,
|
||||
this.showValueWhenUnfolded = true,
|
||||
super.key,
|
||||
@ -54,8 +56,10 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
onChanged: onChanged,
|
||||
popupConstraints: popupConstraints ?? const BoxConstraints(maxWidth: 200),
|
||||
popupWidthConstraint: popupWidthConstraint ?? PopoverConstraint.flexible,
|
||||
autoClosePopover: true,
|
||||
popup: (context) {
|
||||
return SelectPopup(
|
||||
autoClose: true,
|
||||
items: SelectItemBuilder(
|
||||
childCount: options.length,
|
||||
builder: (context, index) {
|
||||
@ -82,9 +86,20 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
leading: controlAffinity != ListTileControlAffinity.leading
|
||||
? secondary
|
||||
: control,
|
||||
trailing: controlAffinity == ListTileControlAffinity.leading
|
||||
? secondary
|
||||
: control,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
spacing: 5,
|
||||
children: [
|
||||
...?trailing,
|
||||
if (controlAffinity == ListTileControlAffinity.leading &&
|
||||
secondary != null)
|
||||
secondary!
|
||||
else if (controlAffinity == ListTileControlAffinity.trailing &&
|
||||
control != null)
|
||||
control,
|
||||
],
|
||||
),
|
||||
onTap: breakLayout ?? mediaQuery.mdAndUp
|
||||
? null
|
||||
: () {
|
||||
|
@ -422,5 +422,7 @@
|
||||
"youtube_engine_set_path": "Make sure it's available in the PATH variable or\nset the absolute path to the {engine} executable below",
|
||||
"youtube_engine_unix_issue_message": "In macOS/Linux/unix like OS's, setting path on .zshrc/.bashrc/.bash_profile etc. won't work.\nYou need to set the path in the shell configuration file",
|
||||
"download": "Download",
|
||||
"file_not_found": "File not found"
|
||||
"file_not_found": "File not found",
|
||||
"custom": "Custom",
|
||||
"add_custom_url": "Add custom URL"
|
||||
}
|
@ -4,6 +4,9 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' show ListTile;
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -11,6 +14,8 @@ import 'package:piped_client/piped_client.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/routes.gr.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/form/text_form_field.dart';
|
||||
import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/components/adaptive/adaptive_select_tile.dart';
|
||||
@ -97,10 +102,106 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
value: preferences.pipedInstance,
|
||||
showValueWhenUnfolded: false,
|
||||
options: data
|
||||
.sortedBy((e) => e.name)
|
||||
.map(
|
||||
(e) => SelectItemButton(
|
||||
trailing: [
|
||||
Tooltip(
|
||||
tooltip: TooltipContainer(
|
||||
child: Text(context.l10n.add_custom_url),
|
||||
),
|
||||
child: IconButton.outline(
|
||||
icon: const Icon(SpotubeIcons.edit),
|
||||
size: ButtonSize.small,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withValues(alpha: 0.5),
|
||||
builder: (context) => HookBuilder(
|
||||
builder: (context) {
|
||||
final controller =
|
||||
useShadcnTextEditingController(
|
||||
text: preferences.pipedInstance,
|
||||
);
|
||||
final formKey = useMemoized(
|
||||
() => GlobalKey<FormBuilderState>(), []);
|
||||
|
||||
return Alert(
|
||||
title:
|
||||
Text(context.l10n.piped_instance).h4(),
|
||||
content: FormBuilder(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
const Gap(10),
|
||||
TextFormBuilderField(
|
||||
name: "url",
|
||||
controller: controller,
|
||||
placeholder: Text(
|
||||
context.l10n.piped_instance),
|
||||
validator:
|
||||
FormBuilderValidators.url(),
|
||||
),
|
||||
const Gap(10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Button.secondary(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(context.l10n.cancel),
|
||||
),
|
||||
),
|
||||
const Gap(10),
|
||||
Expanded(
|
||||
child: Button.primary(
|
||||
onPressed: () {
|
||||
if (!formKey.currentState!
|
||||
.saveAndValidate()) {
|
||||
return;
|
||||
}
|
||||
preferencesNotifier
|
||||
.setPipedInstance(
|
||||
controller.text,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(context.l10n.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
options: [
|
||||
if (data
|
||||
.none((e) => e.apiUrl == preferences.pipedInstance))
|
||||
SelectItemButton(
|
||||
value: preferences.pipedInstance,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
style: theme.typography.xSmall.copyWith(
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: context.l10n.custom),
|
||||
const TextSpan(text: "\n"),
|
||||
TextSpan(text: preferences.pipedInstance),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final e in data.sortedBy((e) => e.name))
|
||||
SelectItemButton(
|
||||
value: e.apiUrl,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
@ -121,8 +222,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferencesNotifier.setPipedInstance(value);
|
||||
@ -157,12 +257,108 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
"${context.l10n.invidious_description}\n"
|
||||
"${context.l10n.invidious_warning}",
|
||||
),
|
||||
trailing: [
|
||||
Tooltip(
|
||||
tooltip: TooltipContainer(
|
||||
child: Text(context.l10n.add_custom_url),
|
||||
),
|
||||
child: IconButton.outline(
|
||||
icon: const Icon(SpotubeIcons.edit),
|
||||
size: ButtonSize.small,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withValues(alpha: 0.5),
|
||||
builder: (context) => HookBuilder(
|
||||
builder: (context) {
|
||||
final controller =
|
||||
useShadcnTextEditingController(
|
||||
text: preferences.invidiousInstance,
|
||||
);
|
||||
final formKey = useMemoized(
|
||||
() => GlobalKey<FormBuilderState>(), []);
|
||||
|
||||
return Alert(
|
||||
title: Text(context.l10n.invidious_instance)
|
||||
.h4(),
|
||||
content: FormBuilder(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
const Gap(10),
|
||||
TextFormBuilderField(
|
||||
name: "url",
|
||||
controller: controller,
|
||||
placeholder: Text(context
|
||||
.l10n.invidious_instance),
|
||||
validator:
|
||||
FormBuilderValidators.url(),
|
||||
),
|
||||
const Gap(10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Button.secondary(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(context.l10n.cancel),
|
||||
),
|
||||
),
|
||||
const Gap(10),
|
||||
Expanded(
|
||||
child: Button.primary(
|
||||
onPressed: () {
|
||||
if (!formKey.currentState!
|
||||
.saveAndValidate()) {
|
||||
return;
|
||||
}
|
||||
preferencesNotifier
|
||||
.setInvidiousInstance(
|
||||
controller.text,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(context.l10n.save),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
value: preferences.invidiousInstance,
|
||||
showValueWhenUnfolded: false,
|
||||
options: data
|
||||
.sortedBy((e) => e.name)
|
||||
.map(
|
||||
(e) => SelectItemButton(
|
||||
options: [
|
||||
if (data.none((e) =>
|
||||
e.details.uri == preferences.invidiousInstance))
|
||||
SelectItemButton(
|
||||
value: preferences.invidiousInstance,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
style: theme.typography.xSmall.copyWith(
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: context.l10n.custom),
|
||||
const TextSpan(text: "\n"),
|
||||
TextSpan(text: preferences.invidiousInstance),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
for (final e in data.sortedBy((e) => e.name))
|
||||
SelectItemButton(
|
||||
value: e.details.uri,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
@ -183,8 +379,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferencesNotifier.setInvidiousInstance(value);
|
||||
|
@ -22,7 +22,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"bn": [
|
||||
@ -48,7 +50,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"ca": [
|
||||
@ -74,7 +78,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"cs": [
|
||||
@ -100,7 +106,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"de": [
|
||||
@ -126,7 +134,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"es": [
|
||||
@ -152,7 +162,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
@ -178,7 +190,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
@ -204,7 +218,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
@ -230,7 +246,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
@ -256,7 +274,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"hi": [
|
||||
@ -282,7 +302,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"id": [
|
||||
@ -308,7 +330,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"it": [
|
||||
@ -334,7 +358,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
@ -360,7 +386,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"ka": [
|
||||
@ -386,7 +414,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
@ -412,7 +442,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"ne": [
|
||||
@ -438,7 +470,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
@ -464,7 +498,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
@ -490,7 +526,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
@ -516,7 +554,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
@ -542,7 +582,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"th": [
|
||||
@ -568,7 +610,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
@ -594,7 +638,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
@ -620,7 +666,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"vi": [
|
||||
@ -646,7 +694,9 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
@ -672,6 +722,8 @@
|
||||
"youtube_engine_set_path",
|
||||
"youtube_engine_unix_issue_message",
|
||||
"download",
|
||||
"file_not_found"
|
||||
"file_not_found",
|
||||
"custom",
|
||||
"add_custom_url"
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user