mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: responsive playlist generate page and scrollable multi autocomplete
This commit is contained in:
parent
4a21249ee3
commit
d57aad5612
@ -1,188 +1,187 @@
|
||||
// Country Codes contributed by momobobe <https://github.com/momobobe>
|
||||
|
||||
final spotifyMarkets = [
|
||||
["AL", "Albania (AL)"],
|
||||
["DZ", "Algeria (DZ)"],
|
||||
["AD", "Andorra (AD)"],
|
||||
["AO", "Angola (AO)"],
|
||||
["AG", "Antigua and Barbuda (AG)"],
|
||||
["AR", "Argentina (AR)"],
|
||||
["AM", "Armenia (AM)"],
|
||||
["AU", "Australia (AU)"],
|
||||
["AT", "Austria (AT)"],
|
||||
["AZ", "Azerbaijan (AZ)"],
|
||||
["BH", "Bahrain (BH)"],
|
||||
["BD", "Bangladesh (BD)"],
|
||||
["BB", "Barbados (BB)"],
|
||||
["BY", "Belarus (BY)"],
|
||||
["BE", "Belgium (BE)"],
|
||||
["BZ", "Belize (BZ)"],
|
||||
["BJ", "Benin (BJ)"],
|
||||
["BT", "Bhutan (BT)"],
|
||||
["BO", "Bolivia (BO)"],
|
||||
["BA", "Bosnia and Herzegovina (BA)"],
|
||||
["BW", "Botswana (BW)"],
|
||||
["BR", "Brazil (BR)"],
|
||||
["BN", "Brunei Darussalam (BN)"],
|
||||
["BG", "Bulgaria (BG)"],
|
||||
["BF", "Burkina Faso (BF)"],
|
||||
["BI", "Burundi (BI)"],
|
||||
["CV", "Cabo Verde / Cape Verde (CV)"],
|
||||
["KH", "Cambodia (KH)"],
|
||||
["CM", "Cameroon (CM)"],
|
||||
["CA", "Canada (CA)"],
|
||||
["TD", "Chad (TD)"],
|
||||
["CL", "Chile (CL)"],
|
||||
["CO", "Colombia (CO)"],
|
||||
["KM", "Comoros (KM)"],
|
||||
["CR", "Costa Rica (CR)"],
|
||||
["HR", "Croatia (HR)"],
|
||||
["CW", "Curaçao (CW)"],
|
||||
["CY", "Cyprus (CY)"],
|
||||
["CZ", "Czech Republic (CZ)"],
|
||||
["CI", "Côte d'Ivoire / Ivory Coast (CI)"],
|
||||
["CD", "Democratic Republic of the Congo (CD)"],
|
||||
["DK", "Denmark (DK)"],
|
||||
["DJ", "Djibouti (DJ)"],
|
||||
["DM", "Dominica (DM)"],
|
||||
["DO", "Dominican Republic (DO)"],
|
||||
["EC", "Ecuador (EC)"],
|
||||
["EG", "Egypt (EG)"],
|
||||
["SV", "El Salvador (SV)"],
|
||||
["GQ", "Equatorial Guinea (GQ)"],
|
||||
["EE", "Estonia (EE)"],
|
||||
["SZ", "Eswatini (SZ)"],
|
||||
["FJ", "Fiji (FJ)"],
|
||||
["FI", "Finland (FI)"],
|
||||
["FR", "France (FR)"],
|
||||
["GA", "Gabon (GA)"],
|
||||
["GE", "Georgia (GE)"],
|
||||
["DE", "Germany (DE)"],
|
||||
["GH", "Ghana (GH)"],
|
||||
["GR", "Greece (GR)"],
|
||||
["GD", "Grenada (GD)"],
|
||||
["GT", "Guatemala (GT)"],
|
||||
["GN", "Guinea (GN)"],
|
||||
["GW", "Guinea-Bissau (GW)"],
|
||||
["GY", "Guyana (GY)"],
|
||||
["HT", "Haiti (HT)"],
|
||||
["HN", "Honduras (HN)"],
|
||||
["HK", "Hong Kong (HK)"],
|
||||
["HU", "Hungary (HU)"],
|
||||
["IS", "Iceland (IS)"],
|
||||
["IN", "India (IN)"],
|
||||
["ID", "Indonesia (ID)"],
|
||||
["IQ", "Iraq (IQ)"],
|
||||
["IE", "Ireland (IE)"],
|
||||
["IL", "Israel (IL)"],
|
||||
["IT", "Italy (IT)"],
|
||||
["JM", "Jamaica (JM)"],
|
||||
["JP", "Japan (JP)"],
|
||||
["JO", "Jordan (JO)"],
|
||||
["KZ", "Kazakhstan (KZ)"],
|
||||
["KE", "Kenya (KE)"],
|
||||
["KI", "Kiribati (KI)"],
|
||||
["XK", "Kosovo (XK)"],
|
||||
["KW", "Kuwait (KW)"],
|
||||
["KG", "Kyrgyzstan (KG)"],
|
||||
["LA", "Laos (LA)"],
|
||||
["LV", "Latvia (LV)"],
|
||||
["LB", "Lebanon (LB)"],
|
||||
["LS", "Lesotho (LS)"],
|
||||
["LR", "Liberia (LR)"],
|
||||
["LY", "Libya (LY)"],
|
||||
["LI", "Liechtenstein (LI)"],
|
||||
["LT", "Lithuania (LT)"],
|
||||
["LU", "Luxembourg (LU)"],
|
||||
["MO", "Macao / Macau (MO)"],
|
||||
["MG", "Madagascar (MG)"],
|
||||
["MW", "Malawi (MW)"],
|
||||
["MY", "Malaysia (MY)"],
|
||||
["MV", "Maldives (MV)"],
|
||||
["ML", "Mali (ML)"],
|
||||
["MT", "Malta (MT)"],
|
||||
["MH", "Marshall Islands (MH)"],
|
||||
["MR", "Mauritania (MR)"],
|
||||
["MU", "Mauritius (MU)"],
|
||||
["MX", "Mexico (MX)"],
|
||||
["FM", "Micronesia (FM)"],
|
||||
["MD", "Moldova (MD)"],
|
||||
["MC", "Monaco (MC)"],
|
||||
["MN", "Mongolia (MN)"],
|
||||
["ME", "Montenegro (ME)"],
|
||||
["MA", "Morocco (MA)"],
|
||||
["MZ", "Mozambique (MZ)"],
|
||||
["NA", "Namibia (NA)"],
|
||||
["NR", "Nauru (NR)"],
|
||||
["NP", "Nepal (NP)"],
|
||||
["NL", "Netherlands (NL)"],
|
||||
["NZ", "New Zealand (NZ)"],
|
||||
["NI", "Nicaragua (NI)"],
|
||||
["NE", "Niger (NE)"],
|
||||
["NG", "Nigeria (NG)"],
|
||||
["MK", "North Macedonia (MK)"],
|
||||
["NO", "Norway (NO)"],
|
||||
["OM", "Oman (OM)"],
|
||||
["PK", "Pakistan (PK)"],
|
||||
["PW", "Palau (PW)"],
|
||||
["PS", "Palestine (PS)"],
|
||||
["PA", "Panama (PA)"],
|
||||
["PG", "Papua New Guinea (PG)"],
|
||||
["PY", "Paraguay (PY)"],
|
||||
["PE", "Peru (PE)"],
|
||||
["PH", "Philippines (PH)"],
|
||||
["PL", "Poland (PL)"],
|
||||
["PT", "Portugal (PT)"],
|
||||
["QA", "Qatar (QA)"],
|
||||
["CG", "Republic of the Congo (CG)"],
|
||||
["RO", "Romania (RO)"],
|
||||
["RU", "Russia (RU)"],
|
||||
["RW", "Rwanda (RW)"],
|
||||
["WS", "Samoa (WS)"],
|
||||
["SM", "San Marino (SM)"],
|
||||
["SA", "Saudi Arabia (SA)"],
|
||||
["SN", "Senegal (SN)"],
|
||||
["RS", "Serbia (RS)"],
|
||||
["SC", "Seychelles (SC)"],
|
||||
["SL", "Sierra Leone (SL)"],
|
||||
["SG", "Singapore (SG)"],
|
||||
["SK", "Slovakia (SK)"],
|
||||
["SI", "Slovenia (SI)"],
|
||||
["SB", "Solomon Islands (SB)"],
|
||||
["ZA", "South Africa (ZA)"],
|
||||
["KR", "South Korea (KR)"],
|
||||
["ES", "Spain (ES)"],
|
||||
["LK", "Sri Lanka (LK)"],
|
||||
["VC", "St Vincent and the Grenadines (VC)"],
|
||||
["KN", "St. Kitts and Nevis (KN)"],
|
||||
["LC", "St. Lucia (LC)"],
|
||||
["SR", "Suriname (SR)"],
|
||||
["SE", "Sweden (SE)"],
|
||||
["CH", "Switzerland (CH)"],
|
||||
["ST", "São Tomé and Príncipe (ST)"],
|
||||
["TW", "Taiwan (TW)"],
|
||||
["TJ", "Tajikistan (TJ)"],
|
||||
["TZ", "Tanzania (TZ)"],
|
||||
["TH", "Thailand (TH)"],
|
||||
["BS", "The Bahamas (BS)"],
|
||||
["GM", "The Gambia (GM)"],
|
||||
["TL", "Timor-Leste / East Timor (TL)"],
|
||||
["TG", "Togo (TG)"],
|
||||
["TO", "Tonga (TO)"],
|
||||
["TT", "Trinidad and Tobago (TT)"],
|
||||
["TN", "Tunisia (TN)"],
|
||||
["TR", "Turkey (TR)"],
|
||||
["TV", "Tuvalu (TV)"],
|
||||
["UG", "Uganda (UG)"],
|
||||
["UA", "Ukraine (UA)"],
|
||||
["AE", "United Arab Emirates (AE)"],
|
||||
["GB", "United Kingdom (GB)"],
|
||||
["US", "United States (US)"],
|
||||
["UY", "Uruguay (UY)"],
|
||||
["UZ", "Uzbekistan (UZ)"],
|
||||
["VU", "Vanuatu (VU)"],
|
||||
["VE", "Venezuela (VE)"],
|
||||
["VN", "Vietnam (VN)"],
|
||||
["ZM", "Zambia (ZM)"],
|
||||
["Z", "Zimbabwe (ZW)"],
|
||||
("AL", "Albania (AL)"),
|
||||
("DZ", "Algeria (DZ)"),
|
||||
("AD", "Andorra (AD)"),
|
||||
("AO", "Angola (AO)"),
|
||||
("AG", "Antigua and Barbuda (AG)"),
|
||||
("AR", "Argentina (AR)"),
|
||||
("AM", "Armenia (AM)"),
|
||||
("AU", "Australia (AU)"),
|
||||
("AT", "Austria (AT)"),
|
||||
("AZ", "Azerbaijan (AZ)"),
|
||||
("BH", "Bahrain (BH)"),
|
||||
("BD", "Bangladesh (BD)"),
|
||||
("BB", "Barbados (BB)"),
|
||||
("BY", "Belarus (BY)"),
|
||||
("BE", "Belgium (BE)"),
|
||||
("BZ", "Belize (BZ)"),
|
||||
("BJ", "Benin (BJ)"),
|
||||
("BT", "Bhutan (BT)"),
|
||||
("BO", "Bolivia (BO)"),
|
||||
("BA", "Bosnia and Herzegovina (BA)"),
|
||||
("BW", "Botswana (BW)"),
|
||||
("BR", "Brazil (BR)"),
|
||||
("BN", "Brunei Darussalam (BN)"),
|
||||
("BG", "Bulgaria (BG)"),
|
||||
("BF", "Burkina Faso (BF)"),
|
||||
("BI", "Burundi (BI)"),
|
||||
("CV", "Cabo Verde / Cape Verde (CV)"),
|
||||
("KH", "Cambodia (KH)"),
|
||||
("CM", "Cameroon (CM)"),
|
||||
("CA", "Canada (CA)"),
|
||||
("TD", "Chad (TD)"),
|
||||
("CL", "Chile (CL)"),
|
||||
("CO", "Colombia (CO)"),
|
||||
("KM", "Comoros (KM)"),
|
||||
("CR", "Costa Rica (CR)"),
|
||||
("HR", "Croatia (HR)"),
|
||||
("CW", "Curaçao (CW)"),
|
||||
("CY", "Cyprus (CY)"),
|
||||
("CZ", "Czech Republic (CZ)"),
|
||||
("CI", "Ivory Coast (CI)"),
|
||||
("CD", "Congo (CD)"),
|
||||
("DK", "Denmark (DK)"),
|
||||
("DJ", "Djibouti (DJ)"),
|
||||
("DM", "Dominica (DM)"),
|
||||
("DO", "Dominican Republic (DO)"),
|
||||
("EC", "Ecuador (EC)"),
|
||||
("EG", "Egypt (EG)"),
|
||||
("SV", "El Salvador (SV)"),
|
||||
("GQ", "Equatorial Guinea (GQ)"),
|
||||
("EE", "Estonia (EE)"),
|
||||
("SZ", "Eswatini (SZ)"),
|
||||
("FJ", "Fiji (FJ)"),
|
||||
("FI", "Finland (FI)"),
|
||||
("FR", "France (FR)"),
|
||||
("GA", "Gabon (GA)"),
|
||||
("GE", "Georgia (GE)"),
|
||||
("DE", "Germany (DE)"),
|
||||
("GH", "Ghana (GH)"),
|
||||
("GR", "Greece (GR)"),
|
||||
("GD", "Grenada (GD)"),
|
||||
("GT", "Guatemala (GT)"),
|
||||
("GN", "Guinea (GN)"),
|
||||
("GW", "Guinea-Bissau (GW)"),
|
||||
("GY", "Guyana (GY)"),
|
||||
("HT", "Haiti (HT)"),
|
||||
("HN", "Honduras (HN)"),
|
||||
("HK", "Hong Kong (HK)"),
|
||||
("HU", "Hungary (HU)"),
|
||||
("IS", "Iceland (IS)"),
|
||||
("IN", "India (IN)"),
|
||||
("ID", "Indonesia (ID)"),
|
||||
("IQ", "Iraq (IQ)"),
|
||||
("IE", "Ireland (IE)"),
|
||||
("IL", "Israel (IL)"),
|
||||
("IT", "Italy (IT)"),
|
||||
("JM", "Jamaica (JM)"),
|
||||
("JP", "Japan (JP)"),
|
||||
("JO", "Jordan (JO)"),
|
||||
("KZ", "Kazakhstan (KZ)"),
|
||||
("KE", "Kenya (KE)"),
|
||||
("KI", "Kiribati (KI)"),
|
||||
("XK", "Kosovo (XK)"),
|
||||
("KW", "Kuwait (KW)"),
|
||||
("KG", "Kyrgyzstan (KG)"),
|
||||
("LA", "Laos (LA)"),
|
||||
("LV", "Latvia (LV)"),
|
||||
("LB", "Lebanon (LB)"),
|
||||
("LS", "Lesotho (LS)"),
|
||||
("LR", "Liberia (LR)"),
|
||||
("LY", "Libya (LY)"),
|
||||
("LI", "Liechtenstein (LI)"),
|
||||
("LT", "Lithuania (LT)"),
|
||||
("LU", "Luxembourg (LU)"),
|
||||
("MO", "Macao / Macau (MO)"),
|
||||
("MG", "Madagascar (MG)"),
|
||||
("MW", "Malawi (MW)"),
|
||||
("MY", "Malaysia (MY)"),
|
||||
("MV", "Maldives (MV)"),
|
||||
("ML", "Mali (ML)"),
|
||||
("MT", "Malta (MT)"),
|
||||
("MH", "Marshall Islands (MH)"),
|
||||
("MR", "Mauritania (MR)"),
|
||||
("MU", "Mauritius (MU)"),
|
||||
("MX", "Mexico (MX)"),
|
||||
("FM", "Micronesia (FM)"),
|
||||
("MD", "Moldova (MD)"),
|
||||
("MC", "Monaco (MC)"),
|
||||
("MN", "Mongolia (MN)"),
|
||||
("ME", "Montenegro (ME)"),
|
||||
("MA", "Morocco (MA)"),
|
||||
("MZ", "Mozambique (MZ)"),
|
||||
("NA", "Namibia (NA)"),
|
||||
("NR", "Nauru (NR)"),
|
||||
("NP", "Nepal (NP)"),
|
||||
("NL", "Netherlands (NL)"),
|
||||
("NZ", "New Zealand (NZ)"),
|
||||
("NI", "Nicaragua (NI)"),
|
||||
("NE", "Niger (NE)"),
|
||||
("NG", "Nigeria (NG)"),
|
||||
("MK", "North Macedonia (MK)"),
|
||||
("NO", "Norway (NO)"),
|
||||
("OM", "Oman (OM)"),
|
||||
("PK", "Pakistan (PK)"),
|
||||
("PW", "Palau (PW)"),
|
||||
("PS", "Palestine (PS)"),
|
||||
("PA", "Panama (PA)"),
|
||||
("PG", "Papua New Guinea (PG)"),
|
||||
("PY", "Paraguay (PY)"),
|
||||
("PE", "Peru (PE)"),
|
||||
("PH", "Philippines (PH)"),
|
||||
("PL", "Poland (PL)"),
|
||||
("PT", "Portugal (PT)"),
|
||||
("QA", "Qatar (QA)"),
|
||||
("CG", "Congo (CG)"),
|
||||
("RO", "Romania (RO)"),
|
||||
("RU", "Russia (RU)"),
|
||||
("RW", "Rwanda (RW)"),
|
||||
("WS", "Samoa (WS)"),
|
||||
("SM", "San Marino (SM)"),
|
||||
("SA", "Saudi Arabia (SA)"),
|
||||
("SN", "Senegal (SN)"),
|
||||
("RS", "Serbia (RS)"),
|
||||
("SC", "Seychelles (SC)"),
|
||||
("SL", "Sierra Leone (SL)"),
|
||||
("SG", "Singapore (SG)"),
|
||||
("SK", "Slovakia (SK)"),
|
||||
("SI", "Slovenia (SI)"),
|
||||
("SB", "Solomon Islands (SB)"),
|
||||
("ZA", "South Africa (ZA)"),
|
||||
("KR", "South Korea (KR)"),
|
||||
("ES", "Spain (ES)"),
|
||||
("LK", "Sri Lanka (LK)"),
|
||||
("KN", "St. Kitts and Nevis (KN)"),
|
||||
("LC", "St. Lucia (LC)"),
|
||||
("SR", "Suriname (SR)"),
|
||||
("SE", "Sweden (SE)"),
|
||||
("CH", "Switzerland (CH)"),
|
||||
("ST", "São Tomé and Príncipe (ST)"),
|
||||
("TW", "Taiwan (TW)"),
|
||||
("TJ", "Tajikistan (TJ)"),
|
||||
("TZ", "Tanzania (TZ)"),
|
||||
("TH", "Thailand (TH)"),
|
||||
("BS", "The Bahamas (BS)"),
|
||||
("GM", "The Gambia (GM)"),
|
||||
("TL", "East Timor (TL)"),
|
||||
("TG", "Togo (TG)"),
|
||||
("TO", "Tonga (TO)"),
|
||||
("TT", "Trinidad and Tobago (TT)"),
|
||||
("TN", "Tunisia (TN)"),
|
||||
("TR", "Turkey (TR)"),
|
||||
("TV", "Tuvalu (TV)"),
|
||||
("UG", "Uganda (UG)"),
|
||||
("UA", "Ukraine (UA)"),
|
||||
("AE", "United Arab Emirates (AE)"),
|
||||
("GB", "United Kingdom (GB)"),
|
||||
("US", "United States (US)"),
|
||||
("UY", "Uruguay (UY)"),
|
||||
("UZ", "Uzbekistan (UZ)"),
|
||||
("VU", "Vanuatu (VU)"),
|
||||
("VE", "Venezuela (VE)"),
|
||||
("VN", "Vietnam (VN)"),
|
||||
("ZM", "Zambia (ZM)"),
|
||||
("ZW", "Zimbabwe (ZW)"),
|
||||
];
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
|
||||
enum SelectedItemDisplayType {
|
||||
wrap,
|
||||
@ -39,14 +41,30 @@ class SeedsMultiAutocomplete<T extends Object> extends HookWidget {
|
||||
Widget build(BuildContext context) {
|
||||
useValueListenable(seeds);
|
||||
final theme = Theme.of(context);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final seedController = useTextEditingController();
|
||||
|
||||
final containerKey = useRef(GlobalKey());
|
||||
|
||||
final box =
|
||||
containerKey.value.currentContext?.findRenderObject() as RenderBox?;
|
||||
final position = box?.localToGlobal(Offset.zero); //this is global position
|
||||
final containerYPos = position?.dy ?? 0; //th
|
||||
final containerHeight = box?.size.height ?? 0;
|
||||
|
||||
final listHeight = mediaQuery.size.height -
|
||||
(containerYPos + containerHeight) -
|
||||
// bottom player bar height
|
||||
(mediaQuery.mdAndUp ? 80 : 0);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
LayoutBuilder(builder: (context, constrains) {
|
||||
return Autocomplete<T>(
|
||||
return Container(
|
||||
key: containerKey.value,
|
||||
child: Autocomplete<T>(
|
||||
optionsBuilder: (textEditingValue) async {
|
||||
if (textEditingValue.text.isEmpty) return [];
|
||||
return fetchSeeds(textEditingValue);
|
||||
@ -58,10 +76,11 @@ class SeedsMultiAutocomplete<T extends Object> extends HookWidget {
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
return Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: constrains.maxWidth,
|
||||
),
|
||||
height: max(listHeight, 0),
|
||||
child: Card(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
@ -91,6 +110,7 @@ class SeedsMultiAutocomplete<T extends Object> extends HookWidget {
|
||||
decoration: inputDecoration,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 8),
|
||||
|
@ -11,6 +11,7 @@ import 'package:spotube/components/library/playlist_generate/seeds_multi_autocom
|
||||
import 'package:spotube/components/library/playlist_generate/simple_track_tile.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate_result.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
@ -44,6 +45,172 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
final leftSeedCount =
|
||||
5 - genres.value.length - artists.value.length - tracks.value.length;
|
||||
|
||||
final artistAutoComplete = SeedsMultiAutocomplete<Artist>(
|
||||
seeds: artists,
|
||||
enabled: enabled,
|
||||
inputDecoration: InputDecoration(
|
||||
labelText: "Artists",
|
||||
labelStyle: textTheme.titleMedium,
|
||||
helperText: "Select up to $leftSeedCount artists",
|
||||
),
|
||||
fetchSeeds: (textEditingValue) => spotify.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.artist],
|
||||
)
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Artist>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
)
|
||||
.where(
|
||||
(element) =>
|
||||
artists.value.none((artist) => element.id == artist.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
autocompleteOptionBuilder: (option, onSelected) => ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
option.images,
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
horizontalTitleGap: 20,
|
||||
title: Text(option.name!),
|
||||
subtitle: option.genres?.isNotEmpty != true
|
||||
? null
|
||||
: Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: option.genres!.mapIndexed(
|
||||
(index, genre) {
|
||||
return Chip(
|
||||
label: Text(genre),
|
||||
labelStyle: textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.secondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
side: BorderSide.none,
|
||||
backgroundColor: theme.colorScheme.secondaryContainer,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
onTap: () => onSelected(option),
|
||||
),
|
||||
displayStringForOption: (option) => option.name!,
|
||||
selectedSeedBuilder: (artist) => Chip(
|
||||
avatar: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
artist.images,
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: Text(artist.name!),
|
||||
onDeleted: () {
|
||||
artists.value = [
|
||||
...artists.value..removeWhere((element) => element.id == artist.id)
|
||||
];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final tracksAutocomplete = SeedsMultiAutocomplete<Track>(
|
||||
seeds: tracks,
|
||||
enabled: enabled,
|
||||
selectedItemDisplayType: SelectedItemDisplayType.list,
|
||||
inputDecoration: InputDecoration(
|
||||
labelText: "Tracks",
|
||||
labelStyle: textTheme.titleMedium,
|
||||
helperText: "Select up to $leftSeedCount tracks",
|
||||
),
|
||||
fetchSeeds: (textEditingValue) => spotify.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.track],
|
||||
)
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Track>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
)
|
||||
.where(
|
||||
(element) =>
|
||||
tracks.value.none((track) => element.id == track.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
autocompleteOptionBuilder: (option, onSelected) => ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
option.album?.images,
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
horizontalTitleGap: 20,
|
||||
title: Text(option.name!),
|
||||
subtitle: Text(
|
||||
option.artists?.map((e) => e.name).join(", ") ??
|
||||
option.album?.name ??
|
||||
"",
|
||||
),
|
||||
onTap: () => onSelected(option),
|
||||
),
|
||||
displayStringForOption: (option) => option.name!,
|
||||
selectedSeedBuilder: (option) => SimpleTrackTile(
|
||||
track: option,
|
||||
onDelete: () {
|
||||
tracks.value = [
|
||||
...tracks.value..removeWhere((element) => element.id == option.id)
|
||||
];
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final genreSelector = MultiSelectField<String>(
|
||||
options: genresCollection.data ?? [],
|
||||
selectedOptions: genres.value,
|
||||
getValueForOption: (option) => option,
|
||||
onSelected: (value) {
|
||||
genres.value = value;
|
||||
},
|
||||
dialogTitle: const Text("Select genres"),
|
||||
label: const Text("Add genres"),
|
||||
helperText: "Select up to $leftSeedCount genres",
|
||||
enabled: enabled,
|
||||
);
|
||||
final countrySelector = ValueListenableBuilder(
|
||||
valueListenable: market,
|
||||
builder: (context, value, _) {
|
||||
return DropdownButtonFormField<String>(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Country",
|
||||
labelStyle: textTheme.titleMedium,
|
||||
),
|
||||
isExpanded: true,
|
||||
items: spotifyMarkets
|
||||
.map(
|
||||
(country) => DropdownMenuItem(
|
||||
value: country.$1,
|
||||
child: Text(country.$2),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
value: market.value,
|
||||
onChanged: (value) {
|
||||
market.value = value!;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: PageWindowTitleBar(
|
||||
leading: const BackButton(),
|
||||
@ -51,7 +218,8 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
child: LayoutBuilder(builder: (context, constrains) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
ValueListenableBuilder(
|
||||
@ -100,171 +268,43 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: market,
|
||||
builder: (context, value, _) {
|
||||
return DropdownMenu<String>(
|
||||
hintText: "Select a country",
|
||||
dropdownMenuEntries: spotifyMarkets
|
||||
.map(
|
||||
(country) => DropdownMenuEntry(
|
||||
value: country.first,
|
||||
label: country.last,
|
||||
if (constrains.mdAndUp)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: countrySelector,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: genreSelector,
|
||||
),
|
||||
],
|
||||
)
|
||||
.toList(),
|
||||
initialSelection: market.value,
|
||||
onSelected: (value) {
|
||||
market.value = value!;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
else ...[
|
||||
countrySelector,
|
||||
const SizedBox(height: 16),
|
||||
MultiSelectField<String>(
|
||||
options: genresCollection.data ?? [],
|
||||
selectedOptions: genres.value,
|
||||
getValueForOption: (option) => option,
|
||||
onSelected: (value) {
|
||||
genres.value = value;
|
||||
},
|
||||
dialogTitle: const Text("Select genres"),
|
||||
label: const Text("Add genres"),
|
||||
helperText: "Select up to $leftSeedCount genres",
|
||||
enabled: enabled,
|
||||
),
|
||||
genreSelector,
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
SeedsMultiAutocomplete<Artist>(
|
||||
seeds: artists,
|
||||
enabled: enabled,
|
||||
inputDecoration: InputDecoration(
|
||||
labelText: "Artists",
|
||||
labelStyle: textTheme.titleMedium,
|
||||
helperText: "Select up to $leftSeedCount artists",
|
||||
if (constrains.mdAndUp)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: artistAutoComplete,
|
||||
),
|
||||
fetchSeeds: (textEditingValue) => spotify.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.artist],
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: tracksAutocomplete,
|
||||
),
|
||||
],
|
||||
)
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Artist>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
)
|
||||
.where(
|
||||
(element) => artists.value
|
||||
.none((artist) => element.id == artist.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
autocompleteOptionBuilder: (option, onSelected) => ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
option.images,
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
horizontalTitleGap: 20,
|
||||
title: Text(option.name!),
|
||||
subtitle: option.genres?.isNotEmpty != true
|
||||
? null
|
||||
: Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: option.genres!.mapIndexed(
|
||||
(index, genre) {
|
||||
return Chip(
|
||||
label: Text(genre),
|
||||
labelStyle: textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.secondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
side: BorderSide.none,
|
||||
backgroundColor:
|
||||
theme.colorScheme.secondaryContainer,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
onTap: () => onSelected(option),
|
||||
),
|
||||
displayStringForOption: (option) => option.name!,
|
||||
selectedSeedBuilder: (artist) => Chip(
|
||||
avatar: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
artist.images,
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: Text(artist.name!),
|
||||
onDeleted: () {
|
||||
artists.value = [
|
||||
...artists.value
|
||||
..removeWhere((element) => element.id == artist.id)
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
else ...[
|
||||
artistAutoComplete,
|
||||
const SizedBox(height: 16),
|
||||
SeedsMultiAutocomplete<Track>(
|
||||
seeds: tracks,
|
||||
enabled: enabled,
|
||||
selectedItemDisplayType: SelectedItemDisplayType.list,
|
||||
inputDecoration: InputDecoration(
|
||||
labelText: "Tracks",
|
||||
labelStyle: textTheme.titleMedium,
|
||||
helperText: "Select up to $leftSeedCount tracks",
|
||||
),
|
||||
fetchSeeds: (textEditingValue) => spotify.search
|
||||
.get(
|
||||
textEditingValue.text,
|
||||
types: [SearchType.track],
|
||||
)
|
||||
.first(6)
|
||||
.then(
|
||||
(v) => List.castFrom<dynamic, Track>(
|
||||
v.expand((e) => e.items ?? []).toList(),
|
||||
)
|
||||
.where(
|
||||
(element) => tracks.value
|
||||
.none((track) => element.id == track.id),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
autocompleteOptionBuilder: (option, onSelected) => ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
option.album?.images,
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
),
|
||||
),
|
||||
horizontalTitleGap: 20,
|
||||
title: Text(option.name!),
|
||||
subtitle: Text(
|
||||
option.artists?.map((e) => e.name).join(", ") ??
|
||||
option.album?.name ??
|
||||
"",
|
||||
),
|
||||
onTap: () => onSelected(option),
|
||||
),
|
||||
displayStringForOption: (option) => option.name!,
|
||||
selectedSeedBuilder: (option) => SimpleTrackTile(
|
||||
track: option,
|
||||
onDelete: () {
|
||||
tracks.value = [
|
||||
...tracks.value
|
||||
..removeWhere((element) => element.id == option.id)
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
tracksAutocomplete,
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
FilledButton.icon(
|
||||
icon: const Icon(SpotubeIcons.magic),
|
||||
@ -289,7 +329,8 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -181,8 +181,8 @@ class SettingsPage extends HookConsumerWidget {
|
||||
options: spotifyMarkets
|
||||
.map(
|
||||
(country) => DropdownMenuItem(
|
||||
value: country.first,
|
||||
child: Text(country.last),
|
||||
value: country.$1,
|
||||
child: Text(country.$2),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
Loading…
Reference in New Issue
Block a user