Skip to content

Commit

Permalink
feat: playlist generation all parameters support
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Jun 8, 2023
1 parent d57aad5 commit 9877d5f
Show file tree
Hide file tree
Showing 8 changed files with 666 additions and 60 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"cmake.configureOnOpen": false,
"cSpell.words": [
"acousticness",
"danceability",
"instrumentalness",
"Mpris",
"speechiness",
"Spotube",
"winget"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class _MultiSelectDialog<T> extends HookWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
autofocus: true,
controller: searchController,
decoration: InputDecoration(
hintText: context.l10n.search,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';

typedef RecommendationAttribute = ({double min, double target, double max});

RecommendationAttribute lowValues(double base) =>
(min: 1 * base, target: 0.3 * base, max: 0.3 * base);
RecommendationAttribute moderateValues(double base) =>
(min: 0.5 * base, target: 1 * base, max: 0.5 * base);
RecommendationAttribute highValues(double base) =>
(min: 0.3 * base, target: 0.3 * base, max: 1 * base);

class RecommendationAttributeDials extends HookWidget {
final Widget title;
final RecommendationAttribute values;
final ValueChanged<RecommendationAttribute> onChanged;
final double base;

const RecommendationAttributeDials({
Key? key,
required this.values,
required this.onChanged,
required this.title,
this.base = 1,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final animation = useAnimationController(
duration: const Duration(milliseconds: 300),
);
final labelStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w500,
);

final minSlider = Row(
children: [
Text(context.l10n.min, style: labelStyle),
Expanded(
child: Slider(
value: values.min / base,
min: 0,
max: 1,
onChanged: (value) => onChanged((
min: value * base,
target: values.target,
max: values.max,
)),
),
),
],
);

final targetSlider = Row(
children: [
Text(context.l10n.target, style: labelStyle),
Expanded(
child: Slider(
value: values.target / base,
min: 0,
max: 1,
onChanged: (value) => onChanged((
min: values.min,
target: value * base,
max: values.max,
)),
),
),
],
);

final maxSlider = Row(
children: [
Text(context.l10n.max, style: labelStyle),
Expanded(
child: Slider(
value: values.max / base,
min: 0,
max: 1,
onChanged: (value) => onChanged((
min: values.min,
target: values.target,
max: value * base,
)),
),
),
],
);

return LayoutBuilder(builder: (context, constrain) {
return Card(
child: ExpansionTile(
title: DefaultTextStyle(
style: Theme.of(context).textTheme.titleMedium!,
child: title,
),
shape: const Border(),
leading: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.rotate(
angle: (animation.value * 3.14) / 2,
child: child,
);
},
child: const Icon(Icons.chevron_right),
),
trailing: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ToggleButtons(
borderRadius: BorderRadius.circular(8),
textStyle: labelStyle,
isSelected: [
values == lowValues(base),
values == moderateValues(base),
values == highValues(base),
],
onPressed: (index) {
RecommendationAttribute newValues = zeroValues;
switch (index) {
case 0:
newValues = lowValues(base);
break;
case 1:
newValues = moderateValues(base);
break;
case 2:
newValues = highValues(base);
break;
}

if (newValues == values) {
onChanged(zeroValues);
} else {
onChanged(newValues);
}
},
children: [
Text(context.l10n.low),
Text(" ${context.l10n.moderate} "),
Text(context.l10n.high),
],
),
),
onExpansionChanged: (value) {
if (value) {
animation.forward();
} else {
animation.reverse();
}
},
children: [
if (constrain.mdAndUp)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const SizedBox(width: 16),
Expanded(child: minSlider),
Expanded(child: targetSlider),
Expanded(child: maxSlider),
],
)
else
Padding(
padding: const EdgeInsets.only(left: 16),
child: Column(
children: [
minSlider,
targetSlider,
maxSlider,
],
),
),
],
),
);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/components/library/playlist_generate/recommendation_attribute_dials.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';

class RecommendationAttributeFields extends HookWidget {
final Widget title;
final RecommendationAttribute values;
final ValueChanged<RecommendationAttribute> onChanged;
final Map<String, RecommendationAttribute>? presets;

const RecommendationAttributeFields({
Key? key,
required this.values,
required this.onChanged,
required this.title,
this.presets,
}) : super(key: key);

@override
Widget build(BuildContext context) {
final animation = useAnimationController(
duration: const Duration(milliseconds: 300),
);
final labelStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w500,
);

final minController = useTextEditingController(text: values.min.toString());
final targetController =
useTextEditingController(text: values.target.toString());
final maxController = useTextEditingController(text: values.max.toString());

useEffect(() {
listener() {
onChanged((
min: double.tryParse(minController.text) ?? 0,
target: double.tryParse(targetController.text) ?? 0,
max: double.tryParse(maxController.text) ?? 0,
));
}

minController.addListener(listener);
targetController.addListener(listener);
maxController.addListener(listener);

return () {
minController.removeListener(listener);
targetController.removeListener(listener);
maxController.removeListener(listener);
};
}, [values]);

final minField = TextField(
controller: minController,
decoration: InputDecoration(
labelText: context.l10n.min,
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
);

final targetField = TextField(
controller: targetController,
decoration: InputDecoration(
labelText: context.l10n.target,
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
);

final maxField = TextField(
controller: maxController,
decoration: InputDecoration(
labelText: context.l10n.max,
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
);

return LayoutBuilder(builder: (context, constrain) {
return Card(
child: ExpansionTile(
title: DefaultTextStyle(
style: Theme.of(context).textTheme.titleMedium!,
child: title,
),
shape: const Border(),
leading: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.rotate(
angle: (animation.value * 3.14) / 2,
child: child,
);
},
child: const Icon(Icons.chevron_right),
),
trailing: presets == null
? const SizedBox.shrink()
: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ToggleButtons(
borderRadius: BorderRadius.circular(8),
textStyle: labelStyle,
isSelected: presets!.values
.map((value) => value == values)
.toList(),
onPressed: (index) {
RecommendationAttribute newValues =
presets!.values.elementAt(index);
if (newValues == values) {
onChanged(zeroValues);
minController.text = zeroValues.min.toString();
targetController.text = zeroValues.target.toString();
maxController.text = zeroValues.max.toString();
} else {
onChanged(newValues);
minController.text = newValues.min.toString();
targetController.text = newValues.target.toString();
maxController.text = newValues.max.toString();
}
},
children: presets!.keys.map((key) => Text(key)).toList(),
),
),
onExpansionChanged: (value) {
if (value) {
animation.forward();
} else {
animation.reverse();
}
},
children: [
const SizedBox(height: 8),
if (constrain.mdAndUp)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const SizedBox(width: 16),
Expanded(child: minField),
const SizedBox(width: 16),
Expanded(child: targetField),
const SizedBox(width: 16),
Expanded(child: maxField),
const SizedBox(width: 16),
],
)
else
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
minField,
const SizedBox(height: 16),
targetField,
const SizedBox(height: 16),
maxField,
],
),
),
const SizedBox(height: 8),
],
),
);
});
}
}
Loading

0 comments on commit 9877d5f

Please sign in to comment.