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