feat: better language picker, adaptive select tile and settings section contrast

This commit is contained in:
Kingkor Roy Tirtho 2023-05-01 00:44:35 +06:00
parent 12915f3e5a
commit 6430a25870
6 changed files with 753 additions and 362 deletions

View File

@ -0,0 +1,262 @@
class ISOLanguageName {
final String name;
final String nativeName;
const ISOLanguageName({
required this.name,
required this.nativeName,
});
}
abstract class LanguageLocals {
static final Map isoLangs = {
"ab": const ISOLanguageName(name: "Abkhaz", nativeName: "аҧсуа"),
"aa": const ISOLanguageName(name: "Afar", nativeName: "Afaraf"),
"af": const ISOLanguageName(name: "Afrikaans", nativeName: "Afrikaans"),
"ak": const ISOLanguageName(name: "Akan", nativeName: "Akan"),
"sq": const ISOLanguageName(name: "Albanian", nativeName: "Shqip"),
"am": const ISOLanguageName(name: "Amharic", nativeName: "አማርኛ"),
"ar": const ISOLanguageName(name: "Arabic", nativeName: "العربية"),
"an": const ISOLanguageName(name: "Aragonese", nativeName: "Aragonés"),
"hy": const ISOLanguageName(name: "Armenian", nativeName: "Հայերեն"),
"as": const ISOLanguageName(name: "Assamese", nativeName: "অসমীয়া"),
"av": const ISOLanguageName(
name: "Avaric",
nativeName: "авар мацӀ, магӀарул мацӀ",
),
"ae": const ISOLanguageName(name: "Avestan", nativeName: "avesta"),
"ay": const ISOLanguageName(name: "Aymara", nativeName: "aymar aru"),
"az": const ISOLanguageName(
name: "Azerbaijani",
nativeName: "azərbaycan dili",
),
"bm": const ISOLanguageName(name: "Bambara", nativeName: "bamanankan"),
"ba": const ISOLanguageName(name: "Bashkir", nativeName: "башҡорт теле"),
"eu": const ISOLanguageName(name: "Basque", nativeName: "euskara,"),
"be": const ISOLanguageName(name: "Belarusian", nativeName: "Беларуская"),
"bn": const ISOLanguageName(name: "Bengali", nativeName: "বাংলা"),
"bh": const ISOLanguageName(name: "Bihari", nativeName: "भोजपुरी"),
"bi": const ISOLanguageName(name: "Bislama", nativeName: "Bislama"),
"bs": const ISOLanguageName(name: "Bosnian", nativeName: "bosanski jezik"),
"br": const ISOLanguageName(name: "Breton", nativeName: "brezhoneg"),
"bg":
const ISOLanguageName(name: "Bulgarian", nativeName: "български език"),
"my": const ISOLanguageName(name: "Burmese", nativeName: "ဗမာစာ"),
"ca":
const ISOLanguageName(name: "Catalan; Valencian", nativeName: "Català"),
"ch": const ISOLanguageName(name: "Chamorro", nativeName: "Chamoru"),
"ce": const ISOLanguageName(name: "Chechen", nativeName: "нохчийн мотт"),
"ny": const ISOLanguageName(name: "Chichewa", nativeName: "chiCheŵa"),
"zh": const ISOLanguageName(name: "Chinese", nativeName: "汉语"),
"cv": const ISOLanguageName(name: "Chuvash", nativeName: "чӑваш чӗлхи"),
"kw": const ISOLanguageName(name: "Cornish", nativeName: "Kernewek"),
"co": const ISOLanguageName(name: "Corsican", nativeName: "lingua corsa"),
"cr": const ISOLanguageName(name: "Cree", nativeName: "ᓀᐦᐃᔭᐍᐏᐣ"),
"hr": const ISOLanguageName(name: "Croatian", nativeName: "hrvatski"),
"cs": const ISOLanguageName(name: "Czech", nativeName: "česky, čeština"),
"da": const ISOLanguageName(name: "Danish", nativeName: "dansk"),
"dv": const ISOLanguageName(name: "Maldivian;", nativeName: "ދިވެހި"),
"nl": const ISOLanguageName(name: "Dutch", nativeName: "Vlaams"),
"en": const ISOLanguageName(name: "English", nativeName: "English"),
"eo": const ISOLanguageName(name: "Esperanto", nativeName: "Esperanto"),
"et": const ISOLanguageName(name: "Estonian", nativeName: "eesti"),
"ee": const ISOLanguageName(name: "Ewe", nativeName: "Eʋegbe"),
"fo": const ISOLanguageName(name: "Faroese", nativeName: "føroyskt"),
"fj": const ISOLanguageName(name: "Fijian", nativeName: "vosa Vakaviti"),
"fi": const ISOLanguageName(name: "Finnish", nativeName: "suomi"),
"fr": const ISOLanguageName(name: "French", nativeName: "français"),
"ff": const ISOLanguageName(
name: "Fula; Fulah; Pulaar; Pular",
nativeName: "Fulfulde, Pulaar, Pular"),
"gl": const ISOLanguageName(name: "Galician", nativeName: "Galego"),
"ka": const ISOLanguageName(name: "Georgian", nativeName: "ქართული"),
"de": const ISOLanguageName(name: "German", nativeName: "Deutsch"),
"el": const ISOLanguageName(name: "Greek, Modern", nativeName: "Ελληνικά"),
"gn": const ISOLanguageName(name: "Guaraní", nativeName: "Avañeẽ"),
"gu": const ISOLanguageName(name: "Gujarati", nativeName: "ગુજરાતી"),
"ht": const ISOLanguageName(
name: "Haitian; Haitian Creole", nativeName: "Kreyòl ayisyen"),
"ha": const ISOLanguageName(name: "Hausa", nativeName: "Hausa, هَوُسَ"),
"he": const ISOLanguageName(name: "Hebrew (modern)", nativeName: "עברית"),
"hz": const ISOLanguageName(name: "Herero", nativeName: "Otjiherero"),
"hi": const ISOLanguageName(name: "Hindi", nativeName: "हिन्दी, हिंदी"),
"ho": const ISOLanguageName(name: "Hiri Motu", nativeName: "Hiri Motu"),
"hu": const ISOLanguageName(name: "Hungarian", nativeName: "Magyar"),
"ia": const ISOLanguageName(name: "Interlingua", nativeName: "Interlingua"),
"id": const ISOLanguageName(
name: "Indonesian", nativeName: "Bahasa Indonesia"),
"ie": const ISOLanguageName(name: "Interlingue", nativeName: "Occidental"),
"ga": const ISOLanguageName(name: "Irish", nativeName: "Gaeilge"),
"ig": const ISOLanguageName(name: "Igbo", nativeName: "Asụsụ Igbo"),
"ik": const ISOLanguageName(
name: "Inupiaq", nativeName: "Iñupiaq, Iñupiatun"),
"io": const ISOLanguageName(name: "Ido", nativeName: "Ido"),
"is": const ISOLanguageName(name: "Icelandic", nativeName: "Íslenska"),
"it": const ISOLanguageName(name: "Italian", nativeName: "Italiano"),
"iu": const ISOLanguageName(name: "Inuktitut", nativeName: "ᐃᓄᒃᑎᑐᑦ"),
"ja":
const ISOLanguageName(name: "Japanese", nativeName: "日本語 (にほんご/にっぽんご)"),
"jv": const ISOLanguageName(name: "Javanese", nativeName: "basa Jawa"),
"kl": const ISOLanguageName(
name: "Kalaallisut, Greenlandic",
nativeName: "kalaallisut, kalaallit oqaasii"),
"kn": const ISOLanguageName(name: "Kannada", nativeName: "ಕನ್ನಡ"),
"kr": const ISOLanguageName(name: "Kanuri", nativeName: "Kanuri"),
"ks":
const ISOLanguageName(name: "Kashmiri", nativeName: "कश्मीरी, كشميري‎"),
"kk": const ISOLanguageName(name: "Kazakh", nativeName: "Қазақ тілі"),
"km": const ISOLanguageName(name: "Khmer", nativeName: "ភាសាខ្មែរ"),
"ki": const ISOLanguageName(name: "Kikuyu, Gikuyu", nativeName: "Gĩkũyũ"),
"rw":
const ISOLanguageName(name: "Kinyarwanda", nativeName: "Ikinyarwanda"),
"ky": const ISOLanguageName(
name: "Kirghiz, Kyrgyz", nativeName: "кыргыз тили"),
"kv": const ISOLanguageName(name: "Komi", nativeName: "коми кыв"),
"kg": const ISOLanguageName(name: "Kongo", nativeName: "KiKongo"),
"ko": const ISOLanguageName(
name: "Korean", nativeName: "한국어 (韓國語), 조선말 (朝鮮語)"),
"ku": const ISOLanguageName(name: "Kurdish", nativeName: "Kurdî, كوردی‎"),
"kj": const ISOLanguageName(
name: "Kwanyama, Kuanyama", nativeName: "Kuanyama"),
"la": const ISOLanguageName(
name: "Latin", nativeName: "latine, lingua latina"),
"lb": const ISOLanguageName(
name: "Luxembourgish, Letzeburgesch", nativeName: "Lëtzebuergesch"),
"lg": const ISOLanguageName(name: "Luganda", nativeName: "Luganda"),
"li": const ISOLanguageName(
name: "Limburgish, Limburgan, Limburger", nativeName: "Limburgs"),
"ln": const ISOLanguageName(name: "Lingala", nativeName: "Lingála"),
"lo": const ISOLanguageName(name: "Lao", nativeName: "ພາສາລາວ"),
"lt":
const ISOLanguageName(name: "Lithuanian", nativeName: "lietuvių kalba"),
"lu": const ISOLanguageName(name: "Luba-Katanga", nativeName: ""),
"lv": const ISOLanguageName(name: "Latvian", nativeName: "latviešu valoda"),
"gv": const ISOLanguageName(name: "Manx", nativeName: "Gaelg, Gailck"),
"mk": const ISOLanguageName(
name: "Macedonian", nativeName: "македонски јазик"),
"mg":
const ISOLanguageName(name: "Malagasy", nativeName: "Malagasy fiteny"),
"ms": const ISOLanguageName(
name: "Malay", nativeName: "bahasa Melayu, بهاس ملايو‎"),
"ml": const ISOLanguageName(name: "Malayalam", nativeName: "മലയാളം"),
"mt": const ISOLanguageName(name: "Maltese", nativeName: "Malti"),
"mi": const ISOLanguageName(name: "Māori", nativeName: "te reo Māori"),
"mr": const ISOLanguageName(name: "Marathi (Marāṭhī)", nativeName: "मराठी"),
"mh":
const ISOLanguageName(name: "Marshallese", nativeName: "Kajin M̧ajeļ"),
"mn": const ISOLanguageName(name: "Mongolian", nativeName: "монгол"),
"na": const ISOLanguageName(name: "Nauru", nativeName: "Ekakairũ Naoero"),
"nv": const ISOLanguageName(
name: "Navajo, Navaho", nativeName: "Diné bizaad, Dinékʼehǰí"),
"nb": const ISOLanguageName(
name: "Norwegian Bokmål", nativeName: "Norsk bokmål"),
"nd":
const ISOLanguageName(name: "North Ndebele", nativeName: "isiNdebele"),
"ne": const ISOLanguageName(name: "Nepali", nativeName: "नेपाली"),
"ng": const ISOLanguageName(name: "Ndonga", nativeName: "Owambo"),
"nn": const ISOLanguageName(
name: "Norwegian Nynorsk", nativeName: "Norsk nynorsk"),
"no": const ISOLanguageName(name: "Norwegian", nativeName: "Norsk"),
"ii": const ISOLanguageName(name: "Nuosu", nativeName: "ꆈꌠ꒿ Nuosuhxop"),
"nr":
const ISOLanguageName(name: "South Ndebele", nativeName: "isiNdebele"),
"oc": const ISOLanguageName(name: "Occitan", nativeName: "Occitan"),
"oj": const ISOLanguageName(name: "Ojibwe, Ojibwa", nativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ"),
"cu": const ISOLanguageName(
name: "Old Church Slavonic", nativeName: "ѩзыкъ словѣньскъ"),
"om": const ISOLanguageName(name: "Oromo", nativeName: "Afaan Oromoo"),
"or": const ISOLanguageName(name: "Oriya", nativeName: "ଓଡ଼ିଆ"),
"os": const ISOLanguageName(
name: "Ossetian, Ossetic", nativeName: "ирон æвзаг"),
"pa": const ISOLanguageName(
name: "Panjabi, Punjabi", nativeName: "ਪੰਜਾਬੀ, پنجابی‎"),
"pi": const ISOLanguageName(name: "Pāli", nativeName: "पाऴि"),
"fa": const ISOLanguageName(name: "Persian", nativeName: "فارسی"),
"pl": const ISOLanguageName(name: "Polish", nativeName: "polski"),
"ps": const ISOLanguageName(name: "Pashto, Pushto", nativeName: "پښتو"),
"pt": const ISOLanguageName(name: "Portuguese", nativeName: "Português"),
"qu":
const ISOLanguageName(name: "Quechua", nativeName: "Runa Simi, Kichwa"),
"rm": const ISOLanguageName(
name: "Romansh", nativeName: "rumantsch grischun"),
"rn": const ISOLanguageName(name: "Kirundi", nativeName: "kiRundi"),
"ro": const ISOLanguageName(
name: "Romanian, Moldavian, Moldovan", nativeName: "română"),
"ru": const ISOLanguageName(name: "Russian", nativeName: "русский язык"),
"sa": const ISOLanguageName(
name: "Sanskrit (Saṁskṛta)", nativeName: "संस्कृतम्"),
"sc": const ISOLanguageName(name: "Sardinian", nativeName: "sardu"),
"sd": const ISOLanguageName(
name: "Sindhi", nativeName: "सिन्धी, سنڌي، سندھی‎"),
"se": const ISOLanguageName(
name: "Northern Sami", nativeName: "Davvisámegiella"),
"sm": const ISOLanguageName(name: "Samoan", nativeName: "gagana faa Samoa"),
"sg": const ISOLanguageName(name: "Sango", nativeName: "yângâ tî sängö"),
"sr": const ISOLanguageName(name: "Serbian", nativeName: "српски језик"),
"gd": const ISOLanguageName(
name: "Scottish Gaelic; Gaelic", nativeName: "Gàidhlig"),
"sn": const ISOLanguageName(name: "Shona", nativeName: "chiShona"),
"si":
const ISOLanguageName(name: "Sinhala, Sinhalese", nativeName: "සිංහල"),
"sk": const ISOLanguageName(name: "Slovak", nativeName: "slovenčina"),
"sl": const ISOLanguageName(name: "Slovene", nativeName: "slovenščina"),
"so": const ISOLanguageName(
name: "Somali", nativeName: "Soomaaliga, af Soomaali"),
"st": const ISOLanguageName(name: "Southern Sotho", nativeName: "Sesotho"),
"es": const ISOLanguageName(
name: "Spanish; Castilian", nativeName: "español, castellano"),
"su": const ISOLanguageName(name: "Sundanese", nativeName: "Basa Sunda"),
"sw": const ISOLanguageName(name: "Swahili", nativeName: "Kiswahili"),
"ss": const ISOLanguageName(name: "Swati", nativeName: "SiSwati"),
"sv": const ISOLanguageName(name: "Swedish", nativeName: "svenska"),
"ta": const ISOLanguageName(name: "Tamil", nativeName: "தமிழ்"),
"te": const ISOLanguageName(name: "Telugu", nativeName: "తెలుగు"),
"tg": const ISOLanguageName(
name: "Tajik", nativeName: "тоҷикӣ, toğikī, تاجیکی‎"),
"th": const ISOLanguageName(name: "Thai", nativeName: "ไทย"),
"ti": const ISOLanguageName(name: "Tigrinya", nativeName: "ትግርኛ"),
"bo": const ISOLanguageName(
name: "Tibetan Standard, Tibetan, Central", nativeName: "བོད་ཡིག"),
"tk":
const ISOLanguageName(name: "Turkmen", nativeName: "Türkmen, Түркмен"),
"tl": const ISOLanguageName(
name: "Tagalog", nativeName: "Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔"),
"tn": const ISOLanguageName(name: "Tswana", nativeName: "Setswana"),
"to": const ISOLanguageName(
name: "Tonga (Tonga Islands)", nativeName: "faka Tonga"),
"tr": const ISOLanguageName(name: "Turkish", nativeName: "Türkçe"),
"ts": const ISOLanguageName(name: "Tsonga", nativeName: "Xitsonga"),
"tt": const ISOLanguageName(
name: "Tatar", nativeName: "татарча, tatarça, تاتارچا‎"),
"tw": const ISOLanguageName(name: "Twi", nativeName: "Twi"),
"ty": const ISOLanguageName(name: "Tahitian", nativeName: "Reo Tahiti"),
"ug": const ISOLanguageName(
name: "Uighur, Uyghur", nativeName: "Uyƣurqə, ئۇيغۇرچە‎"),
"uk": const ISOLanguageName(name: "Ukrainian", nativeName: "українська"),
"ur": const ISOLanguageName(name: "Urdu", nativeName: "اردو"),
"uz": const ISOLanguageName(
name: "Uzbek", nativeName: "zbek, Ўзбек, أۇزبېك‎"),
"ve": const ISOLanguageName(name: "Venda", nativeName: "Tshivenḓa"),
"vi": const ISOLanguageName(name: "Vietnamese", nativeName: "Tiếng Việt"),
"vo": const ISOLanguageName(name: "Volapük", nativeName: "Volapük"),
"wa": const ISOLanguageName(name: "Walloon", nativeName: "Walon"),
"cy": const ISOLanguageName(name: "Welsh", nativeName: "Cymraeg"),
"wo": const ISOLanguageName(name: "Wolof", nativeName: "Wollof"),
"fy": const ISOLanguageName(name: "Western Frisian", nativeName: "Frysk"),
"xh": const ISOLanguageName(name: "Xhosa", nativeName: "isiXhosa"),
"yi": const ISOLanguageName(name: "Yiddish", nativeName: "ייִדיש"),
"yo": const ISOLanguageName(name: "Yoruba", nativeName: "Yorùbá"),
"za": const ISOLanguageName(
name: "Zhuang, Chuang",
nativeName: "Saɯ cueŋƅ, Saw cuengh",
)
};
static ISOLanguageName getDisplayLanguage(key) {
if (isoLangs.containsKey(key)) {
return isoLangs[key]!;
} else {
throw Exception("Language key incorrect");
}
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
class SectionCardWithHeading extends StatelessWidget {
final String heading;
final List<Widget> children;
const SectionCardWithHeading({
super.key,
required this.heading,
required this.children,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
heading,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Column(mainAxisSize: MainAxisSize.min, children: children),
),
),
],
);
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/hooks/use_breakpoints.dart';
class AdaptiveSelectTile<T> extends HookWidget {
final Widget title;
final Widget? subtitle;
final Widget? secondary;
final ListTileControlAffinity? controlAffinity;
final T value;
final ValueChanged<T?>? onChanged;
final List<DropdownMenuItem<T>> options;
final Breakpoints breakAfterOr;
const AdaptiveSelectTile({
required this.title,
required this.value,
required this.onChanged,
required this.options,
this.controlAffinity = ListTileControlAffinity.trailing,
this.subtitle,
this.secondary,
this.breakAfterOr = Breakpoints.md,
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final breakpoint = useBreakpoints();
final rawControl = DropdownButton<T>(
items: options,
value: value,
onChanged: onChanged,
);
final controlPlaceholder = useMemoized(
() => options
.firstWhere(
(element) => element.value == value,
orElse: () => DropdownMenuItem<T>(
value: null,
child: Container(),
),
)
.child,
[value, options]);
final control = breakpoint >= breakAfterOr
? rawControl
: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
border: Border.all(
color: theme.colorScheme.primary,
width: 2,
),
borderRadius: BorderRadius.circular(10),
),
child: DefaultTextStyle(
style: TextStyle(
color: theme.colorScheme.primary,
),
child: controlPlaceholder,
),
);
return ListTile(
title: title,
subtitle: subtitle,
leading: controlAffinity != ListTileControlAffinity.leading
? secondary
: control,
trailing: controlAffinity == ListTileControlAffinity.leading
? secondary
: control,
onTap: breakpoint >= breakAfterOr
? null
: () {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: title,
children: [
for (final option in options)
RadioListTile<T>(
title: option.child,
value: option.value as T,
groupValue: value,
onChanged: (v) {
Navigator.pop(context);
onChanged?.call(v);
},
),
],
);
},
);
},
);
}
}

View File

@ -6,13 +6,17 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/collections/language_codes.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
import 'package:spotube/components/settings/section_card_with_heading.dart';
import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
import 'package:spotube/components/shared/page_window_title_bar.dart';
import 'package:spotube/collections/spotify_markets.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/l10n/l10n.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/downloader_provider.dart';
@ -61,398 +65,382 @@ class SettingsPage extends HookConsumerWidget {
constraints: const BoxConstraints(maxWidth: 1366),
child: ListView(
children: [
Text(
" ${context.l10n.account}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
if (auth == null)
AdaptiveListTile(
leading: Icon(
SpotubeIcons.login,
color: theme.colorScheme.primary,
),
title: Align(
alignment: Alignment.centerLeft,
child: AutoSizeText(
context.l10n.login_with_spotify,
maxLines: 1,
style: TextStyle(
SectionCardWithHeading(
heading: context.l10n.account,
children: [
if (auth == null)
AdaptiveListTile(
leading: Icon(
SpotubeIcons.login,
color: theme.colorScheme.primary,
),
),
),
trailing: (context, update) => FilledButton(
onPressed: () {
GoRouter.of(context).push("/login");
},
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0),
),
),
),
child: Text(
context.l10n.connect_with_spotify.toUpperCase(),
),
),
)
else
Builder(builder: (context) {
return ListTile(
leading: const Icon(SpotubeIcons.logout),
title: SizedBox(
height: 50,
width: 180,
child: Align(
title: Align(
alignment: Alignment.centerLeft,
child: AutoSizeText(
context.l10n.logout_of_this_account,
context.l10n.login_with_spotify,
maxLines: 1,
style: TextStyle(
color: theme.colorScheme.primary,
),
),
),
),
trailing: FilledButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.red),
foregroundColor:
MaterialStateProperty.all(Colors.white),
trailing: (context, update) => FilledButton(
onPressed: () {
GoRouter.of(context).push("/login");
},
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0),
),
),
),
child: Text(
context.l10n.connect_with_spotify.toUpperCase(),
),
),
onPressed: () async {
ref
.read(
AuthenticationNotifier.provider.notifier)
.logout();
GoRouter.of(context).pop();
},
child: Text(context.l10n.logout),
),
);
}),
Text(
" ${context.l10n.language_region}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
)
else
Builder(builder: (context) {
return ListTile(
leading: const Icon(SpotubeIcons.logout),
title: SizedBox(
height: 50,
width: 180,
child: Align(
alignment: Alignment.centerLeft,
child: AutoSizeText(
context.l10n.logout_of_this_account,
maxLines: 1,
),
),
),
trailing: FilledButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.red),
foregroundColor:
MaterialStateProperty.all(Colors.white),
),
onPressed: () async {
ref
.read(AuthenticationNotifier
.provider.notifier)
.logout();
GoRouter.of(context).pop();
},
child: Text(context.l10n.logout),
),
);
}),
],
),
ListTile(
leading: const Icon(SpotubeIcons.language),
title: Text(context.l10n.language),
trailing: DropdownButton<Locale>(
value: preferences.locale,
items: [
DropdownMenuItem(
value: const Locale("system", "system"),
child: Text(context.l10n.system_default),
),
for (final locale in L10n.all)
SectionCardWithHeading(
heading: context.l10n.language_region,
children: [
AdaptiveSelectTile<Locale>(
value: preferences.locale,
onChanged: (locale) {
if (locale == null) return;
preferences.setLocale(locale);
},
title: Text(context.l10n.language),
secondary: const Icon(SpotubeIcons.language),
options: [
DropdownMenuItem(
value: locale,
child: Text(locale.languageCode),
value: const Locale("system", "system"),
child: Text(context.l10n.system_default),
),
],
onChanged: (value) {
if (value != null) {
preferences.setLocale(value);
}
},
),
),
AdaptiveListTile(
leading: const Icon(SpotubeIcons.shoppingBag),
title: Text(context.l10n.market_place_region),
subtitle: Text(context.l10n.recommendation_country),
trailing: (context, update) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 350),
child: DropdownMenu(
initialSelection: preferences.recommendationMarket,
dropdownMenuEntries: spotifyMarkets
for (final locale in L10n.all)
DropdownMenuItem(
value: locale,
child: Builder(builder: (context) {
final isoCodeName =
LanguageLocals.getDisplayLanguage(
locale.languageCode,
);
return Text(
"${isoCodeName.name} (${isoCodeName.nativeName})",
);
}),
),
],
),
AdaptiveSelectTile<String>(
breakAfterOr: Breakpoints.lg,
secondary: const Icon(SpotubeIcons.shoppingBag),
title: Text(context.l10n.market_place_region),
subtitle: Text(context.l10n.recommendation_country),
value: preferences.recommendationMarket,
onChanged: (value) {
if (value == null) return;
preferences.setRecommendationMarket(value);
},
options: spotifyMarkets
.map(
(country) => DropdownMenuEntry(
(country) => DropdownMenuItem(
value: country.first,
label: country.last,
child: Text(country.last),
),
)
.toList(),
onSelected: (value) {
if (value == null) return;
preferences.setRecommendationMarket(
value as String,
);
update?.call(() {});
),
],
),
SectionCardWithHeading(
heading: context.l10n.appearance,
children: [
AdaptiveSelectTile<LayoutMode>(
secondary: const Icon(SpotubeIcons.dashboard),
title: Text(context.l10n.layout_mode),
subtitle: Text(context.l10n.override_layout_settings),
value: preferences.layoutMode,
onChanged: (value) {
if (value != null) {
preferences.setLayoutMode(value);
}
},
),
),
),
Text(
" ${context.l10n.appearance}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
AdaptiveListTile(
leading: const Icon(SpotubeIcons.dashboard),
title: Text(context.l10n.layout_mode),
subtitle: Text(context.l10n.override_layout_settings),
trailing: (context, update) => DropdownButton<LayoutMode>(
items: [
DropdownMenuItem(
value: LayoutMode.adaptive,
child: Text(context.l10n.adaptive),
),
DropdownMenuItem(
value: LayoutMode.compact,
child: Text(context.l10n.compact),
),
DropdownMenuItem(
value: LayoutMode.extended,
child: Text(context.l10n.extended),
),
],
value: preferences.layoutMode,
onChanged: (value) {
if (value != null) {
preferences.setLayoutMode(value);
update?.call(() {});
}
},
),
),
AdaptiveListTile(
leading: const Icon(SpotubeIcons.darkMode),
title: Text(context.l10n.theme),
trailing: (context, update) => DropdownButton<ThemeMode>(
value: preferences.themeMode,
items: [
DropdownMenuItem(
value: ThemeMode.dark,
child: Text(context.l10n.dark),
),
DropdownMenuItem(
value: ThemeMode.light,
child: Text(context.l10n.light),
),
DropdownMenuItem(
value: ThemeMode.system,
child: Text(context.l10n.system),
),
],
onChanged: (value) {
if (value != null) {
preferences.setThemeMode(value);
update?.call(() {});
}
},
),
),
ListTile(
leading: const Icon(SpotubeIcons.palette),
title: Text(context.l10n.accent_color),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 5,
),
trailing: ColorTile.compact(
color: preferences.accentColorScheme,
onPressed: pickColorScheme(),
isActive: true,
),
onTap: pickColorScheme(),
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.colorSync),
title: Text(context.l10n.sync_album_color),
subtitle: Text(context.l10n.sync_album_color_description),
value: preferences.albumColorSync,
onChanged: preferences.setAlbumColorSync,
),
Text(
" ${context.l10n.playback}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
AdaptiveListTile(
leading: const Icon(SpotubeIcons.audioQuality),
title: Text(context.l10n.audio_quality),
trailing: (context, update) =>
DropdownButton<AudioQuality>(
value: preferences.audioQuality,
items: [
DropdownMenuItem(
value: AudioQuality.high,
child: Text(context.l10n.high),
),
DropdownMenuItem(
value: AudioQuality.low,
child: Text(context.l10n.low),
),
],
onChanged: (value) {
if (value != null) {
preferences.setAudioQuality(value);
update?.call(() {});
}
},
),
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.download),
title: Text(context.l10n.pre_download_play),
subtitle:
Text(context.l10n.pre_download_play_description),
value: preferences.predownload,
onChanged: (state) {
preferences.setPredownload(state);
},
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.fastForward),
title: Text(context.l10n.skip_non_music),
value: preferences.skipSponsorSegments,
onChanged: (state) {
preferences.setSkipSponsorSegments(state);
},
),
ListTile(
leading: const Icon(SpotubeIcons.playlistRemove),
title: Text(context.l10n.blacklist),
subtitle: Text(context.l10n.blacklist_description),
onTap: () {
GoRouter.of(context).push("/settings/blacklist");
},
trailing: const Icon(SpotubeIcons.angleRight),
),
Text(
" ${context.l10n.downloads}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
Tooltip(
message: isDownloading
? context.l10n.wait_for_download_to_finish
: "",
child: ListTile(
leading: const Icon(SpotubeIcons.download),
title: Text(context.l10n.download_location),
subtitle: Text(preferences.downloadLocation),
trailing: FilledButton(
onPressed:
isDownloading ? null : pickDownloadLocation,
child: const Icon(SpotubeIcons.folder),
),
onTap: isDownloading ? null : pickDownloadLocation,
),
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.lyrics),
title: Text(context.l10n.download_lyrics),
value: preferences.saveTrackLyrics,
onChanged: (state) {
preferences.setSaveTrackLyrics(state);
},
),
if (DesktopTools.platform.isDesktop) ...[
Text(
" ${context.l10n.desktop}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
AdaptiveListTile(
leading: const Icon(SpotubeIcons.close),
title: Text(context.l10n.close_behavior),
trailing: (context, update) =>
DropdownButton<CloseBehavior>(
value: preferences.closeBehavior,
items: [
options: [
DropdownMenuItem(
value: CloseBehavior.close,
child: Text(context.l10n.close),
value: LayoutMode.adaptive,
child: Text(context.l10n.adaptive),
),
DropdownMenuItem(
value: CloseBehavior.minimizeToTray,
child: Text(context.l10n.minimize_to_tray),
value: LayoutMode.compact,
child: Text(context.l10n.compact),
),
DropdownMenuItem(
value: LayoutMode.extended,
child: Text(context.l10n.extended),
),
],
),
AdaptiveSelectTile<ThemeMode>(
secondary: const Icon(SpotubeIcons.darkMode),
title: Text(context.l10n.theme),
value: preferences.themeMode,
options: [
DropdownMenuItem(
value: ThemeMode.dark,
child: Text(context.l10n.dark),
),
DropdownMenuItem(
value: ThemeMode.light,
child: Text(context.l10n.light),
),
DropdownMenuItem(
value: ThemeMode.system,
child: Text(context.l10n.system),
),
],
onChanged: (value) {
if (value != null) {
preferences.setCloseBehavior(value);
update?.call(() {});
preferences.setThemeMode(value);
}
},
),
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.tray),
title: Text(context.l10n.show_tray_icon),
value: preferences.showSystemTrayIcon,
onChanged: preferences.setShowSystemTrayIcon,
),
],
Text(
" ${context.l10n.about}",
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
ListTile(
leading: const Icon(SpotubeIcons.palette),
title: Text(context.l10n.accent_color),
contentPadding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 5,
),
trailing: ColorTile.compact(
color: preferences.accentColorScheme,
onPressed: pickColorScheme(),
isActive: true,
),
onTap: pickColorScheme(),
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.colorSync),
title: Text(context.l10n.sync_album_color),
subtitle:
Text(context.l10n.sync_album_color_description),
value: preferences.albumColorSync,
onChanged: preferences.setAlbumColorSync,
),
],
),
AdaptiveListTile(
leading: const Icon(
SpotubeIcons.heart,
color: Colors.pink,
SectionCardWithHeading(
heading: context.l10n.playback,
children: [
AdaptiveSelectTile<AudioQuality>(
secondary: const Icon(SpotubeIcons.audioQuality),
title: Text(context.l10n.audio_quality),
value: preferences.audioQuality,
options: [
DropdownMenuItem(
value: AudioQuality.high,
child: Text(context.l10n.high),
),
DropdownMenuItem(
value: AudioQuality.low,
child: Text(context.l10n.low),
),
],
onChanged: (value) {
if (value != null) {
preferences.setAudioQuality(value);
}
},
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.download),
title: Text(context.l10n.pre_download_play),
subtitle:
Text(context.l10n.pre_download_play_description),
value: preferences.predownload,
onChanged: (state) {
preferences.setPredownload(state);
},
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.fastForward),
title: Text(context.l10n.skip_non_music),
value: preferences.skipSponsorSegments,
onChanged: (state) {
preferences.setSkipSponsorSegments(state);
},
),
ListTile(
leading: const Icon(SpotubeIcons.playlistRemove),
title: Text(context.l10n.blacklist),
subtitle: Text(context.l10n.blacklist_description),
onTap: () {
GoRouter.of(context).push("/settings/blacklist");
},
trailing: const Icon(SpotubeIcons.angleRight),
),
],
),
SectionCardWithHeading(
heading: context.l10n.downloads,
children: [
Tooltip(
message: isDownloading
? context.l10n.wait_for_download_to_finish
: "",
child: ListTile(
leading: const Icon(SpotubeIcons.download),
title: Text(context.l10n.download_location),
subtitle: Text(preferences.downloadLocation),
trailing: FilledButton(
onPressed:
isDownloading ? null : pickDownloadLocation,
child: const Icon(SpotubeIcons.folder),
),
onTap: isDownloading ? null : pickDownloadLocation,
),
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.lyrics),
title: Text(context.l10n.download_lyrics),
value: preferences.saveTrackLyrics,
onChanged: (state) {
preferences.setSaveTrackLyrics(state);
},
),
],
),
if (DesktopTools.platform.isDesktop)
SectionCardWithHeading(
heading: context.l10n.desktop,
children: [
AdaptiveSelectTile<CloseBehavior>(
secondary: const Icon(SpotubeIcons.close),
title: Text(context.l10n.close_behavior),
value: preferences.closeBehavior,
options: [
DropdownMenuItem(
value: CloseBehavior.close,
child: Text(context.l10n.close),
),
DropdownMenuItem(
value: CloseBehavior.minimizeToTray,
child: Text(context.l10n.minimize_to_tray),
),
],
onChanged: (value) {
if (value != null) {
preferences.setCloseBehavior(value);
}
},
),
SwitchListTile(
secondary: const Icon(SpotubeIcons.tray),
title: Text(context.l10n.show_tray_icon),
value: preferences.showSystemTrayIcon,
onChanged: preferences.setShowSystemTrayIcon,
),
],
),
title: SizedBox(
height: 50,
width: 200,
child: Align(
alignment: Alignment.centerLeft,
child: AutoSizeText(
context.l10n.u_love_spotube,
maxLines: 1,
style: const TextStyle(
color: Colors.pink,
fontWeight: FontWeight.bold,
SectionCardWithHeading(
heading: context.l10n.about,
children: [
AdaptiveListTile(
leading: const Icon(
SpotubeIcons.heart,
color: Colors.pink,
),
title: SizedBox(
height: 50,
width: 200,
child: Align(
alignment: Alignment.centerLeft,
child: AutoSizeText(
context.l10n.u_love_spotube,
maxLines: 1,
style: const TextStyle(
color: Colors.pink,
fontWeight: FontWeight.bold,
),
),
),
),
trailing: (context, update) => FilledButton(
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.red[100]),
foregroundColor: const MaterialStatePropertyAll(
Colors.pinkAccent),
padding: const MaterialStatePropertyAll(
EdgeInsets.all(15)),
),
onPressed: () {
launchUrlString(
"https://opencollective.com/spotube",
mode: LaunchMode.externalApplication,
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(SpotubeIcons.heart),
const SizedBox(width: 5),
Text(context.l10n.please_sponsor),
],
),
),
),
),
trailing: (context, update) => FilledButton(
style: ButtonStyle(
backgroundColor:
MaterialStatePropertyAll(Colors.red[100]),
foregroundColor:
const MaterialStatePropertyAll(Colors.pinkAccent),
padding: const MaterialStatePropertyAll(
EdgeInsets.all(15)),
),
onPressed: () {
launchUrlString(
"https://opencollective.com/spotube",
mode: LaunchMode.externalApplication,
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(SpotubeIcons.heart),
const SizedBox(width: 5),
Text(context.l10n.please_sponsor),
],
),
),
),
if (Env.enableUpdateChecker)
SwitchListTile(
secondary: const Icon(SpotubeIcons.update),
title: Text(context.l10n.check_for_updates),
value: preferences.checkUpdate,
onChanged: (checked) =>
preferences.setCheckUpdate(checked),
),
ListTile(
leading: const Icon(SpotubeIcons.info),
title: Text(context.l10n.about_spotube),
trailing: const Icon(SpotubeIcons.angleRight),
onTap: () {
GoRouter.of(context).push("/settings/about");
},
if (Env.enableUpdateChecker)
SwitchListTile(
secondary: const Icon(SpotubeIcons.update),
title: Text(context.l10n.check_for_updates),
value: preferences.checkUpdate,
onChanged: (checked) =>
preferences.setCheckUpdate(checked),
),
ListTile(
leading: const Icon(SpotubeIcons.info),
title: Text(context.l10n.about_spotube),
trailing: const Icon(SpotubeIcons.angleRight),
onTap: () {
GoRouter.of(context).push("/settings/about");
},
)
],
),
],
),

View File

@ -95,7 +95,7 @@ class SpotubeAudioPlayer {
Stream<bool> get bufferingStream {
if (apSupportedPlatform) {
return const Stream.empty();
return Stream.value(false);
} else {
throw UnimplementedError();
}
@ -215,7 +215,6 @@ class SpotubeAudioPlayer {
Future<void> pause() async {
await _audioPlayer?.pause();
throw UnimplementedError();
}
Future<void> resume() async {

View File

@ -13,6 +13,7 @@ ThemeData theme(Color seed, Brightness brightness) {
horizontalTitleGap: 0,
iconColor: scheme.onSurface,
),
appBarTheme: const AppBarTheme(surfaceTintColor: Colors.transparent),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),