mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
High Constrast color scheme support added
Download button shows done if already downloaded Search TextField design improved
This commit is contained in:
parent
3702e29135
commit
ff4e5df44c
@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:spotube/components/Shared/NotFound.dart';
|
import 'package:spotube/components/Shared/NotFound.dart';
|
||||||
|
import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
||||||
import 'package:spotube/components/Shared/TrackTile.dart';
|
import 'package:spotube/components/Shared/TrackTile.dart';
|
||||||
import 'package:spotube/helpers/image-to-url-string.dart';
|
import 'package:spotube/helpers/image-to-url-string.dart';
|
||||||
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
||||||
@ -48,6 +49,9 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
var titleStyle = Theme.of(context).textTheme.headline4?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
);
|
||||||
return BackdropFilter(
|
return BackdropFilter(
|
||||||
filter: ImageFilter.blur(
|
filter: ImageFilter.blur(
|
||||||
sigmaX: 12.0,
|
sigmaX: 12.0,
|
||||||
@ -76,12 +80,17 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text("Queue", style: titleStyle),
|
||||||
"Queue (${playback.playlist?.name})",
|
Padding(
|
||||||
style: Theme.of(context).textTheme.headline4?.copyWith(
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
fontWeight: FontWeight.bold,
|
child: Text(
|
||||||
),
|
playback.playlist?.name ?? "",
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText1
|
||||||
|
?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Flexible(
|
Flexible(
|
||||||
|
@ -43,36 +43,30 @@ class Search extends HookConsumerWidget {
|
|||||||
color: Theme.of(context).backgroundColor,
|
color: Theme.of(context).backgroundColor,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Container(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
child: Row(
|
color: Theme.of(context).backgroundColor,
|
||||||
children: [
|
child: Expanded(
|
||||||
Expanded(
|
child: TextField(
|
||||||
child: TextField(
|
controller: controller,
|
||||||
controller: controller,
|
decoration: InputDecoration(
|
||||||
decoration:
|
isDense: true,
|
||||||
const InputDecoration(hintText: "Search..."),
|
suffix: ElevatedButton(
|
||||||
onSubmitted: (value) {
|
child: const Icon(Icons.search_rounded),
|
||||||
|
onPressed: () {
|
||||||
ref.read(searchTermStateProvider.notifier).state =
|
ref.read(searchTermStateProvider.notifier).state =
|
||||||
controller.value.text;
|
controller.value.text;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
hintStyle: const TextStyle(height: 2),
|
||||||
|
hintText: "Search...",
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
onSubmitted: (value) {
|
||||||
MaterialButton(
|
ref.read(searchTermStateProvider.notifier).state =
|
||||||
elevation: 3,
|
controller.value.text;
|
||||||
splashColor: Theme.of(context).primaryColor,
|
},
|
||||||
padding: const EdgeInsets.symmetric(vertical: 21),
|
),
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
textColor: Colors.white,
|
|
||||||
child: const Icon(Icons.search_rounded),
|
|
||||||
onPressed: () {
|
|
||||||
ref.read(searchTermStateProvider.notifier).state =
|
|
||||||
controller.value.text;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
searchSnapshot.when(
|
searchSnapshot.when(
|
||||||
|
@ -3,6 +3,23 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/provider/UserPreferences.dart';
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
|
|
||||||
|
final highContrast = MaterialColor(
|
||||||
|
const Color.fromARGB(255, 104, 104, 104).value,
|
||||||
|
const {
|
||||||
|
50: Colors.white,
|
||||||
|
100: Color.fromARGB(255, 233, 233, 233),
|
||||||
|
200: Color.fromARGB(255, 224, 219, 219),
|
||||||
|
300: Color.fromARGB(255, 207, 207, 207),
|
||||||
|
400: Color.fromARGB(255, 146, 146, 146),
|
||||||
|
500: Color.fromARGB(255, 104, 104, 104),
|
||||||
|
600: Color.fromARGB(255, 78, 78, 78),
|
||||||
|
700: Color.fromARGB(255, 61, 61, 61),
|
||||||
|
800: Color.fromARGB(255, 27, 27, 27),
|
||||||
|
850: Color.fromARGB(255, 20, 20, 20),
|
||||||
|
900: Colors.black,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
final colorsMap = {
|
final colorsMap = {
|
||||||
"Red": Colors.red,
|
"Red": Colors.red,
|
||||||
"Pink": Colors.pink,
|
"Pink": Colors.pink,
|
||||||
@ -23,6 +40,7 @@ final colorsMap = {
|
|||||||
"Brown": Colors.brown,
|
"Brown": Colors.brown,
|
||||||
"BlueGrey": Colors.blueGrey,
|
"BlueGrey": Colors.blueGrey,
|
||||||
"Grey": Colors.grey,
|
"Grey": Colors.grey,
|
||||||
|
"HighContrast": highContrast,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ColorSchemeType {
|
enum ColorSchemeType {
|
||||||
|
@ -29,8 +29,29 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
final status = useState<TrackStatus>(TrackStatus.idle);
|
final status = useState<TrackStatus>(TrackStatus.idle);
|
||||||
YoutubeExplode yt = useMemoized(() => YoutubeExplode());
|
YoutubeExplode yt = useMemoized(() => YoutubeExplode());
|
||||||
|
|
||||||
|
final outputFile = useState<File?>(null);
|
||||||
|
final downloadFolder = useState<String?>(null);
|
||||||
|
String fileName =
|
||||||
|
"${track?.name} - ${artistsToString<Artist>(track?.artists ?? [])}";
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
(() async {
|
||||||
|
downloadFolder.value = path.join(
|
||||||
|
Platform.isAndroid
|
||||||
|
? "/storage/emulated/0/Download"
|
||||||
|
: (await path_provider.getDownloadsDirectory())!.path,
|
||||||
|
"Spotube");
|
||||||
|
|
||||||
|
outputFile.value =
|
||||||
|
File(path.join(downloadFolder.value!, "$fileName.mp3"));
|
||||||
|
}());
|
||||||
|
return null;
|
||||||
|
}, [fileName, track]);
|
||||||
|
|
||||||
final _downloadTrack = useCallback(() async {
|
final _downloadTrack = useCallback(() async {
|
||||||
if (track == null) return;
|
if (track == null ||
|
||||||
|
outputFile.value == null ||
|
||||||
|
downloadFolder.value == null) return;
|
||||||
if ((kIsMobile) &&
|
if ((kIsMobile) &&
|
||||||
!await Permission.storage.isGranted &&
|
!await Permission.storage.isGranted &&
|
||||||
!await Permission.storage.isPermanentlyDenied) {
|
!await Permission.storage.isPermanentlyDenied) {
|
||||||
@ -47,18 +68,10 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
StreamManifest manifest = await yt.videos.streamsClient
|
StreamManifest manifest = await yt.videos.streamsClient
|
||||||
.getManifest((track as SpotubeTrack).ytTrack.url);
|
.getManifest((track as SpotubeTrack).ytTrack.url);
|
||||||
|
|
||||||
String downloadFolder = path.join(
|
|
||||||
Platform.isAndroid
|
|
||||||
? "/storage/emulated/0/Download"
|
|
||||||
: (await path_provider.getDownloadsDirectory())!.path,
|
|
||||||
"Spotube");
|
|
||||||
String fileName =
|
|
||||||
"${track?.name} - ${artistsToString<Artist>(track?.artists ?? [])}";
|
|
||||||
File outputFile = File(path.join(downloadFolder, "$fileName.mp3"));
|
|
||||||
File outputLyricsFile =
|
File outputLyricsFile =
|
||||||
File(path.join(downloadFolder, "$fileName-lyrics.txt"));
|
File(path.join(downloadFolder.value!, "$fileName-lyrics.txt"));
|
||||||
|
|
||||||
if (await outputFile.exists()) {
|
if (await outputFile.value!.exists()) {
|
||||||
final shouldReplace = await showDialog<bool>(
|
final shouldReplace = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
@ -113,9 +126,11 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!await outputFile.exists()) await outputFile.create(recursive: true);
|
if (!await outputFile.value!.exists()) {
|
||||||
|
await outputFile.value!.create(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
IOSink outputFileStream = outputFile.openWrite();
|
IOSink outputFileStream = outputFile.value!.openWrite();
|
||||||
await audioStream.pipe(outputFileStream);
|
await audioStream.pipe(outputFileStream);
|
||||||
await outputFileStream.flush();
|
await outputFileStream.flush();
|
||||||
await outputFileStream.close().then((value) async {
|
await outputFileStream.close().then((value) async {
|
||||||
@ -157,12 +172,20 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
yt,
|
yt,
|
||||||
preferences.saveTrackLyrics,
|
preferences.saveTrackLyrics,
|
||||||
playback.track,
|
playback.track,
|
||||||
|
outputFile.value,
|
||||||
|
downloadFolder.value,
|
||||||
|
fileName
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
return () => yt.close();
|
return () => yt.close();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
final outputFileExists = useMemoized(
|
||||||
|
() => outputFile.value?.existsSync() == true,
|
||||||
|
[outputFile.value, status.value, track],
|
||||||
|
);
|
||||||
|
|
||||||
if (status.value == TrackStatus.downloading) {
|
if (status.value == TrackStatus.downloading) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
child: CircularProgressIndicator.adaptive(
|
child: CircularProgressIndicator.adaptive(
|
||||||
@ -175,7 +198,9 @@ class DownloadTrackButton extends HookConsumerWidget {
|
|||||||
return const Icon(Icons.download_done_rounded);
|
return const Icon(Icons.download_done_rounded);
|
||||||
}
|
}
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: const Icon(Icons.download_rounded),
|
icon: Icon(
|
||||||
|
outputFileExists ? Icons.download_done_rounded : Icons.download_rounded,
|
||||||
|
),
|
||||||
onPressed: track != null && track is SpotubeTrack ? _downloadTrack : null,
|
onPressed: track != null && track is SpotubeTrack ? _downloadTrack : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
margin: margin,
|
margin: margin,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 200),
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
child: Ink(
|
child: Ink(
|
||||||
@ -37,10 +38,11 @@ class PlaybuttonCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 3),
|
offset: const Offset(0, 3),
|
||||||
spreadRadius: 5,
|
spreadRadius: 5,
|
||||||
color: Theme.of(context).shadowColor)
|
color: Theme.of(context).shadowColor,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
Loading…
Reference in New Issue
Block a user