15 Commits
Author SHA1 Message Date
Illusion 3c834c7eed chore: update flutter submodule and bump version to 0.3.5 2023-07-24 21:37:39 +03:00
Illusion a770033563 fix: show indicator when a song is an instrumental 2023-07-24 20:57:01 +03:00
Illusion 1417eef23c make lyrics selectable and upgrade sdk version and deps 2023-07-17 01:41:00 +03:00
Illusion cd39204f6a bump version to 0.3.4 2023-02-06 22:33:22 +02:00
Illusion edc1f3a88b chore: change abi versioning 2023-02-06 22:30:46 +02:00
Illusion 0d96d906a4 chore: v0.3.3
* updated dependencies and removed unused ones
* added flutter as a submodule for easier f-droid updates
2023-02-05 18:51:22 +02:00
Illusion 55025d6d11 chore: short description under 80 chars 2023-02-05 07:10:28 +02:00
Illusion 3935faffc7 chore: correct license in README 2023-02-05 05:51:43 +02:00
Illusion 20c2862ea5 chore: fastlane for f-droid 2023-02-05 05:22:04 +02:00
Illusion 99b1035638 chore: sign app with release keys and change appId 2023-02-05 00:52:02 +02:00
Illusion e69b5e6848 fix: move to new api domain 2022-10-02 00:32:36 +03:00
Illusion 42077e17ee feat/fix: themes with some fixes 2022-08-07 06:26:54 +03:00
Illusion 55d26abaae chore: update readme 2022-08-06 02:42:48 +03:00
Illusion 197be6330b fix: show toast error and fallback icon when album art fails to load 2022-08-06 02:25:55 +03:00
Illusion 100157a8d4 feat/fix: bump to 0.2.0
* remove spotify-only restriction.
* only support players that use mediasession and media notifications.
* don't capture non-mediasession notifications.
2022-08-06 00:55:18 +03:00
26 changed files with 779 additions and 361 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "3rdparty/flutter"]
path = 3rdparty/flutter
url = https://github.com/flutter/flutter
Vendored Submodule
+1
Submodule 3rdparty/flutter added at f468f3366c
+6 -1
View File
@@ -1,3 +1,8 @@
# lyricsgrabbr # lyricsgrabbr
App that listens to your notifications and provides lyrics for the currently playing song.
App to listen to Spotify notifications and provide lyrics for currently playing song. ## Supported players
Any player that uses the MediaSession API _should_ be supported
## License
The project is licensed under the GPL3 license while the assets (icon) are licensed under the creative commons license.
+30 -3
View File
@@ -21,6 +21,12 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
@@ -42,17 +48,27 @@ android {
} }
defaultConfig { defaultConfig {
applicationId "com.illusionware.lyricsgrabbr" applicationId "com.illusionman1212.lyricsgrabbr"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 31 targetSdkVersion 31
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. signingConfig signingConfigs.release
// Signing with the debug keys for now, so `flutter run --release` works. }
debug {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
} }
@@ -66,3 +82,14 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
} }
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.illusionware.lyricsgrabbr"> package="com.illusionman1212.lyricsgrabbr">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
+1 -1
View File
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.illusionware.lyricsgrabbr"> package="com.illusionman1212.lyricsgrabbr">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@@ -1,4 +1,4 @@
package com.illusionware.lyricsgrabbr package com.illusionman1212.lyricsgrabbr
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@@ -1,4 +1,4 @@
package com.illusionware.lyricsgrabbr package com.illusionman1212.lyricsgrabbr
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
@@ -56,21 +56,19 @@ class NotificationListener : NotificationListenerService() {
} }
override fun onNotificationPosted(sbn: StatusBarNotification) { override fun onNotificationPosted(sbn: StatusBarNotification) {
if (sbn.packageName == SPOTIFY_PACKAGE) {
sendNotificationBroadcast(sbn, applicationContext) sendNotificationBroadcast(sbn, applicationContext)
} }
}
private fun getStopServiceNotificationAction(): PendingIntent { private fun getStopServiceNotificationAction(): PendingIntent {
val intent = Intent(MainActivity.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val intent = Intent(MainActivity.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
return pendingIntent return pendingIntent
} }
private fun startForegroundService() { private fun startForegroundService() {
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle("Service Running") .setContentTitle("Service Running")
.setContentText("Play some songs on spotify to get lyrics") .setContentText("Play some songs on a supported player to get lyrics")
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setShowWhen(false) .setShowWhen(false)
.addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction()) .addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction())
@@ -103,19 +101,21 @@ class NotificationListener : NotificationListenerService() {
private fun getCurrentNotifications(context: Context) { private fun getCurrentNotifications(context: Context) {
val sbns = activeNotifications val sbns = activeNotifications
for (sbn in sbns) { for (sbn in sbns) {
if (sbn.packageName == SPOTIFY_PACKAGE) {
sendNotificationBroadcast(sbn, context) sendNotificationBroadcast(sbn, context)
} }
} }
}
private fun sendNotificationBroadcast(sbn: StatusBarNotification, context: Context) { private fun sendNotificationBroadcast(sbn: StatusBarNotification, context: Context) {
// get the media session stuff // get the media session stuff
val mediaSessionToken = sbn.notification.extras.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION) val mediaSessionToken = sbn.notification.extras.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)
val mediaCtrl = mediaSessionToken?.let { MediaController(context, it) } if (mediaSessionToken == null) {
val duration = mediaCtrl?.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000) return
val playbackState = mediaCtrl?.playbackState?.state }
val albumArt = mediaCtrl?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
val mediaCtrl = mediaSessionToken.let { MediaController(context, it) }
val duration = mediaCtrl.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000)
val playbackState = mediaCtrl.playbackState?.state
val albumArt = mediaCtrl.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
// Retrieve package name to set as title. // Retrieve package name to set as title.
val packageName = sbn.packageName val packageName = sbn.packageName
@@ -134,7 +134,7 @@ class NotificationListener : NotificationListenerService() {
if (playbackState == PlaybackState.STATE_PAUSED) { if (playbackState == PlaybackState.STATE_PAUSED) {
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle("Service Running") .setContentTitle("Service Running")
.setContentText("Play some songs on spotify to get lyrics") .setContentText("Play some songs on a supported player to get lyrics")
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setShowWhen(false) .setShowWhen(false)
.addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction()) .addAction(R.drawable.ic_notification, "Stop Service", getStopServiceNotificationAction())
@@ -173,7 +173,6 @@ class NotificationListener : NotificationListenerService() {
const val NOTIFICATION_PACKAGE_DURATION = "package_duration" const val NOTIFICATION_PACKAGE_DURATION = "package_duration"
const val NOTIFICATION_ID = 12251999 const val NOTIFICATION_ID = 12251999
const val NOTIFICATION_CHANNEL_ID = "Lyrics" const val NOTIFICATION_CHANNEL_ID = "Lyrics"
const val SPOTIFY_PACKAGE = "com.spotify.music"
const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE" const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE"
} }
} }
@@ -1,4 +1,4 @@
package com.illusionware.lyricsgrabbr package com.illusionman1212.lyricsgrabbr
import io.flutter.plugin.common.EventChannel.StreamHandler import io.flutter.plugin.common.EventChannel.StreamHandler
import io.flutter.plugin.common.EventChannel.EventSink import io.flutter.plugin.common.EventChannel.EventSink
+1 -1
View File
@@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.illusionware.lyricsgrabbr"> package="com.illusionman1212.lyricsgrabbr">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
+1 -1
View File
@@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.6.10' ext.kotlin_version = '1.8.22'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
@@ -0,0 +1,2 @@
## F-droid release
Initial release on f-droid
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

@@ -0,0 +1 @@
Lyrics grabbr provides lyrics for the currently playing song
@@ -0,0 +1 @@
Lyrics Grabbr
+27 -9
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:convert' as convert;
import 'package:web_scraper/web_scraper.dart'; import 'package:web_scraper/web_scraper.dart';
class LyricsPage extends StatefulWidget { class LyricsPage extends StatefulWidget {
@@ -21,6 +20,7 @@ class LyricsPage extends StatefulWidget {
class _LyricsPageState extends State<LyricsPage> { class _LyricsPageState extends State<LyricsPage> {
bool fetching = false; bool fetching = false;
String? lyrics; String? lyrics;
bool instrumental = false;
@override @override
void initState() { void initState() {
@@ -36,16 +36,18 @@ class _LyricsPageState extends State<LyricsPage> {
String path = widget.url.split('genius.com/')[1]; String path = widget.url.split('genius.com/')[1];
String fullLyrics = ''; String fullLyrics = '';
if (await webScraper.loadWebPage('/$path?react=1')) { if (await webScraper.loadWebPage('/$path?react=1')) {
webScraper.loadFromString(webScraper.getPageContent().replaceAll('<br>', '\n')); webScraper
var element = webScraper.getElement("div[data-lyrics-container]", ['data-lyrics-container']); .loadFromString(webScraper.getPageContent().replaceAll('<br>', '\n'));
var element = webScraper
.getElement("div[data-lyrics-container]", ['data-lyrics-container']);
for (var section in element) { for (var section in element) {
fullLyrics += section['title'] + '\n'; fullLyrics += section['title'] + '\n';
} }
setState(() { setState(() {
lyrics = fullLyrics; lyrics = fullLyrics;
instrumental = fullLyrics.isEmpty;
fetching = false; fetching = false;
}); });
// print(thing);
} }
} }
@@ -73,24 +75,40 @@ class _LyricsPageState extends State<LyricsPage> {
), ),
body: Center( body: Center(
child: fetching child: fetching
? Column( ? const Column(
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: const [CircularProgressIndicator(value: null)], children: [CircularProgressIndicator(value: null)],
), ),
), ),
], ],
) )
: instrumental
? const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.music_note, size: 50),
SizedBox(width: 20, height: 20),
Text(
'This Song is an Instrumental',
style: TextStyle(
fontSize: 20,
fontStyle: FontStyle.italic,
),
),
],
),
)
: SizedBox( : SizedBox(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Container( child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.all(30),
horizontal: 30, vertical: 30), child: SelectableText(
child: Text(
lyrics ?? '', lyrics ?? '',
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
+86 -29
View File
@@ -1,28 +1,61 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'notification_listener.dart'; import 'notification_listener.dart';
import 'search_result.dart'; import 'search_result.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert' as convert; import 'dart:convert' as convert;
import 'package:shared_preferences/shared_preferences.dart';
import 'settings.dart';
import 'theme_provider.dart';
Future<void> main() async { Future<void> main() async {
runApp(const LyricsGrabbr()); runApp(const LyricsGrabbr());
} }
class LyricsGrabbr extends StatelessWidget { class LyricsGrabbr extends StatefulWidget {
const LyricsGrabbr({Key? key}) : super(key: key); const LyricsGrabbr({Key? key}) : super(key: key);
@override
State<LyricsGrabbr> createState() => _LyricsGrabbrState();
}
class _LyricsGrabbrState extends State<LyricsGrabbr> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder<SharedPreferences>(
future: SharedPreferences.getInstance(),
builder: (_, snapshot) {
final prefTheme = snapshot.data?.getString("theme");
var themeMode = ThemeMode.system;
if (prefTheme == AppTheme.Dark.name) {
themeMode = ThemeMode.dark;
} else if (prefTheme == AppTheme.Light.name) {
themeMode = ThemeMode.light;
} else if (prefTheme == AppTheme.System.name) {
themeMode = ThemeMode.system;
}
return snapshot.hasData ? ChangeNotifierProvider(
create: (context) => ThemeProvider(themeMode),
builder: (context, _) {
final themeProvider = Provider.of<ThemeProvider>(context);
return MaterialApp( return MaterialApp(
title: 'Lyrics Grabbr', title: 'Lyrics Grabbr',
theme: ThemeData( theme: MyThemes.lightTheme,
primarySwatch: Colors.purple, darkTheme: MyThemes.darkTheme,
), themeMode: themeProvider.themeMode,
home: const HomePage(title: 'Lyrics Grabbr'), home: const HomePage(title: 'Lyrics Grabbr'),
); );
} }
) : Container();
}
);
}
} }
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@@ -90,7 +123,6 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
), ),
content: const Text( content: const Text(
'This app requires permission to listen to notifications to function properly. Please grant permission in the next screen. BEWARE that the only way to stop the service is to revoke the notification listening permission.', 'This app requires permission to listen to notifications to function properly. Please grant permission in the next screen. BEWARE that the only way to stop the service is to revoke the notification listening permission.',
style: TextStyle(color: Colors.blueGrey),
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -148,7 +180,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
// TODO: build an httpclient to reuse between requests. // TODO: build an httpclient to reuse between requests.
final uri = Uri.https( final uri = Uri.https(
'api.illusionman1212.tech', 'api.illusionman1212.com',
'/lyrics/search', '/lyrics/search',
{'q': '$song $artist'}, {'q': '$song $artist'},
); );
@@ -178,35 +210,28 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
} }
Widget _buildResults() { Widget _buildResults() {
return ListView.builder( return ListView.separated(
itemBuilder: (_, i) { itemBuilder: (_, i) {
return searchResults[i]; return searchResults[i];
}, },
itemCount: searchResults.length, itemCount: searchResults.length,
separatorBuilder: (BuildContext context, int index) {
return const Divider(height: 1);
},
); );
} }
@override List<Widget> _buildPage() {
Widget build(BuildContext context) { if (lastNotification != null) {
return Scaffold( return [
appBar: AppBar(
title: Text(widget.title),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (running)
Container( Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
text: lastNotification?.songTitle, text: lastNotification?.songTitle,
style: const TextStyle( style: TextStyle(
fontSize: 24.0, fontSize: 24.0,
color: Colors.black, color: Theme.of(context).textTheme.bodyLarge?.color,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
children: [ children: [
@@ -215,9 +240,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
), ),
TextSpan( TextSpan(
text: lastNotification?.artist, text: lastNotification?.artist,
style: const TextStyle( style: TextStyle(
fontSize: 20.0, fontSize: 20.0,
color: Color(0xFF303030), color: Theme.of(context).textTheme.bodyMedium?.color,
), ),
) )
]), ]),
@@ -230,9 +255,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: const [ children: const [CircularProgressIndicator(value: null)]))
CircularProgressIndicator(value: null)
]))
: Flexible( : Flexible(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -244,9 +267,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
running running
? 'Found ${searchResults.length} result(s)' ? 'Found ${searchResults.length} result(s)'
: "", : "",
style: const TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
color: Colors.black87, color: Theme.of(context).textTheme.bodyText1?.color,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
@@ -257,8 +280,42 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
), ),
), ),
), ),
];
}
return [
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Text("No supported player is running",
style: TextStyle(fontSize: 24)),
),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.settings),
tooltip: "Settings",
splashRadius: 20,
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => const SettingsPage()));
},
),
], ],
), ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildPage(),
),
), ),
); );
} }
+62 -63
View File
@@ -3,7 +3,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'lyrics.dart'; import 'lyrics.dart';
class SearchResult extends StatefulWidget { class SearchResult extends StatelessWidget {
const SearchResult( const SearchResult(
{Key? key, {Key? key,
required this.title, required this.title,
@@ -16,79 +16,42 @@ class SearchResult extends StatefulWidget {
final String title; final String title;
final String artist; final String artist;
final String url; final Uri url;
final String artistUrl; final Uri artistUrl;
final String thumbnail; final String thumbnail;
final int id; final int id;
SearchResult.fromJson(Map<String, dynamic> json, {Key? key}) SearchResult.fromJson(Map<String, dynamic> json, {Key? key})
: title = json['meta']['title'], : title = json['meta']['title'],
artist = json['meta']['primaryArtist']['name'], artist = json['meta']['primaryArtist']['name'],
url = json['url'], url = Uri.parse(json['url']),
artistUrl = json['meta']['primaryArtist']['url'], artistUrl = Uri.parse(json['meta']['primaryArtist']['url']),
thumbnail = json['resources']['thumbnail'], thumbnail = json['resources']['thumbnail'],
id = json['id'], id = json['id'],
super(key: key); super(key: key);
@override
State<StatefulWidget> createState() => _SearchResultState();
}
class OptionItem extends StatelessWidget {
final String title;
final IconData icon;
final void Function()? tapFunction;
const OptionItem(
{Key? key, required this.title, required this.icon, this.tapFunction})
: super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: tapFunction,
child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Row(
children: [
Container(
margin: const EdgeInsets.only(right: 15),
child: Icon(icon),
),
Text(
title,
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
),
],
),
),
);
}
}
class _SearchResultState extends State<SearchResult> {
List<Widget> _buildOptions() { List<Widget> _buildOptions() {
return <OptionItem>[ return <OptionItem>[
OptionItem( OptionItem(
title: "Open Artist Page in Genius", title: "Open Artist Page in Genius",
icon: Icons.library_music, icon: Icons.library_music,
tapFunction: () async { onTap: () async {
if (!await launch(widget.artistUrl)) { if (!await launchUrl(artistUrl)) {
throw 'could not launch ${widget.artistUrl}'; throw 'could not launch $artistUrl';
} }
}), }),
OptionItem( OptionItem(
title: "Open Song Page in Genius", title: "Open Song Page in Genius",
icon: Icons.music_note, icon: Icons.music_note,
tapFunction: () async { onTap: () async {
if (!await launch(widget.url)) { if (!await launchUrl(url)) {
throw 'could not launch ${widget.url}'; throw 'could not launch $url';
} }
}) })
]; ];
} }
void _showOptionsBottomSheet() { void _showOptionsBottomSheet(BuildContext context) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) { builder: (context) {
@@ -108,33 +71,38 @@ class _SearchResultState extends State<SearchResult> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Material(
child: Container(
margin: const EdgeInsets.only(top: 0.5),
child: InkWell( child: InkWell(
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => LyricsPage( builder: (context) => LyricsPage(
title: widget.title, title: title,
artist: widget.artist, artist: artist,
url: widget.url, url: url.toString(),
), ),
), ),
); );
}, },
onLongPress: () => _showOptionsBottomSheet(), onLongPress: () => _showOptionsBottomSheet(context),
child: Ink( child: Ink(
height: 75, height: 75,
color: Colors.white10,
child: Row( child: Row(
children: [ children: [
Container( Container(
margin: const EdgeInsets.only(right: 20), margin: const EdgeInsets.only(right: 20),
child: Image.network( child: Image.network(
widget.thumbnail, thumbnail,
width: 75, width: 75,
height: 75, height: 75,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Center(
child: SizedBox(
width: 75,
height: 75,
child: Icon(Icons.broken_image, size: 40)),
);
},
), ),
), ),
Flexible( Flexible(
@@ -143,18 +111,18 @@ class _SearchResultState extends State<SearchResult> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
widget.title, title,
style: const TextStyle( style: TextStyle(
color: Colors.black, color: Theme.of(context).textTheme.bodyLarge?.color,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Text( Text(
widget.artist, artist,
style: const TextStyle( style: TextStyle(
color: Colors.black54, color: Theme.of(context).textTheme.titleMedium?.color,
fontSize: 14, fontSize: 14,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -166,6 +134,37 @@ class _SearchResultState extends State<SearchResult> {
), ),
), ),
), ),
);
}
}
class OptionItem extends StatelessWidget {
final String title;
final IconData icon;
final void Function()? onTap;
const OptionItem(
{Key? key, required this.title, required this.icon, this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Ink(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Container(
margin: const EdgeInsets.only(right: 15),
child: Icon(icon),
),
Text(
title,
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600),
),
],
),
), ),
); );
} }
+188
View File
@@ -0,0 +1,188 @@
// ignore_for_file: constant_identifier_names
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'theme_provider.dart';
enum AppTheme {
Dark,
Light,
System
}
class Setting extends StatelessWidget {
final String title;
final String? desc;
final void Function() onTap;
const Setting({
Key? key,
required this.title,
this.desc,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
child: InkWell(
onTap: onTap,
child: Ink(
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 16)),
if (desc != null)
Text(desc!,
style: const TextStyle(color: Colors.grey)),
]),
),
],
),
),
),
),
);
}
}
class SettingsPage extends StatefulWidget {
const SettingsPage({Key? key}) : super(key: key);
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
late SharedPreferences prefs;
String? activeTheme;
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
prefs = await _prefs;
setState(() {
activeTheme = prefs.getString("theme") ?? AppTheme.System.name;
});
}
Widget _buildSettingsGroup(String groupTitle, List<Setting> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(groupTitle,
style: const TextStyle(color: Colors.deepPurple)),
),
...items,
],
);
}
List<Widget> _buildSettingsList(BuildContext context) {
return [
_buildSettingsGroup("Customization", [
Setting(
title: "Theme",
desc: activeTheme,
onTap: () {
final provider = Provider.of<ThemeProvider>(context, listen: false);
showDialog(
context: context,
builder: (BuildContext buildCtx) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2)),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20),
child: Text("Theme",
style: Theme.of(context).textTheme.headline6),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
RadioListTile(
value: AppTheme.System.name,
groupValue: activeTheme,
title: Text("Use System Default", style: Theme.of(context).textTheme.bodyText1),
onChanged: (String? value) => setState(() {
provider.changeTheme(ThemeMode.system);
activeTheme = AppTheme.System.name;
prefs.setString("theme", AppTheme.System.name);
Navigator.pop(context);
})),
RadioListTile(
value: AppTheme.Dark.name,
groupValue: activeTheme,
title: Text("Dark", style: Theme.of(context).textTheme.bodyText1),
onChanged: (String? value) => setState(() {
provider.changeTheme(ThemeMode.dark);
activeTheme = AppTheme.Dark.name;
prefs.setString("theme", AppTheme.Dark.name);
Navigator.pop(context);
})),
RadioListTile(
value: AppTheme.Light.name,
groupValue: activeTheme,
title: Text("Light", style: Theme.of(context).textTheme.bodyText1),
onChanged: (String? value) => setState(() {
provider.changeTheme(ThemeMode.light);
activeTheme = AppTheme.Light.name;
prefs.setString("theme", AppTheme.Light.name);
Navigator.pop(context);
})),
],
),
),
],
),
),
);
});
})
])
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Settings"),
),
body: ListView.separated(
itemBuilder: (_, i) {
return _buildSettingsList(context)[i];
},
separatorBuilder: (_, __) {
return const SizedBox(height: 15);
},
itemCount: 1,
padding: const EdgeInsets.symmetric(vertical: 20),
),
);
}
}
+43
View File
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class ThemeProvider extends ChangeNotifier {
ThemeMode themeMode;
ThemeProvider(this.themeMode);
bool get isDarkMode => themeMode == ThemeMode.dark;
void changeTheme(ThemeMode mode) {
themeMode = mode;
notifyListeners();
}
}
class MyThemes {
static final darkTheme = ThemeData(
primarySwatch: Colors.deepPurple,
dialogTheme: const DialogTheme(backgroundColor: Color(0xFF202020)),
unselectedWidgetColor: Colors.white,
canvasColor: Colors.black,
bottomSheetTheme: const BottomSheetThemeData(backgroundColor: Color(0xFF202020)),
dividerColor: Colors.white,
iconTheme: const IconThemeData(color: Colors.white),
textTheme: const TextTheme(
bodyText1: TextStyle(color: Colors.white, fontSize: 16),
bodyText2: TextStyle(color: Colors.white),
subtitle1: TextStyle(color: Colors.grey),
subtitle2: TextStyle(color: Colors.white),
headline6: TextStyle(color: Colors.white),
),
);
static final lightTheme = ThemeData(
primarySwatch: Colors.purple,
dividerColor: Colors.black,
textTheme: const TextTheme(
bodyText1: TextStyle(fontSize: 16),
subtitle1: TextStyle(color: Colors.black54),
),
);
}
+215 -142
View File
@@ -1,90 +1,78 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.2" version: "2.11.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.17.1"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
name: csslib name: csslib
url: "https://pub.dartlang.org" sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.2" version: "1.0.0"
dbus:
dependency: transitive
description:
name: dbus
url: "https://pub.dartlang.org"
source: hosted
version: "0.7.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
url: "https://pub.dartlang.org" sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "2.0.2"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
url: "https://pub.dartlang.org" sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.4"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -94,30 +82,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "2.0.2"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
url: "https://pub.dartlang.org"
source: hosted
version: "9.3.2"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -132,93 +100,186 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: html name: html
url: "https://pub.dartlang.org" sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.0" version: "0.15.4"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
url: "https://pub.dartlang.org" sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.4" version: "0.13.6"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.2"
js: js:
dependency: transitive dependency: transitive
description: description:
name: js name: js
url: "https://pub.dartlang.org" sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.4" version: "0.6.7"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
url: "https://pub.dartlang.org" sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "2.1.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.11" version: "0.12.15"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4" version: "0.2.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.7.0" version: "1.9.1"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.1" version: "1.8.3"
petitparser: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: path_provider_linux
url: "https://pub.dartlang.org" sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "2.1.11"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted
version: "2.0.6"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
url: "https://pub.dartlang.org" sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
process: provider:
dependency: "direct main"
description:
name: provider
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
url: "https://pub.dev"
source: hosted
version: "6.0.5"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: process name: shared_preferences_android
url: "https://pub.dartlang.org" sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "2.2.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
url: "https://pub.dev"
source: hosted
version: "2.3.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -228,142 +289,154 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
url: "https://pub.dartlang.org" sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.2" version: "1.9.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.0"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.9" version: "0.5.1"
timezone:
dependency: transitive
description:
name: timezone
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.2"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.20" version: "6.1.12"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
url: "https://pub.dartlang.org" sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.15" version: "6.0.36"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
url: "https://pub.dartlang.org" sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.15" version: "6.1.4"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
url: "https://pub.dartlang.org" sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.5"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
url: "https://pub.dartlang.org" sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.6"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
url: "https://pub.dartlang.org" sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.1.3"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
url: "https://pub.dartlang.org" sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.9" version: "2.0.18"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
url: "https://pub.dartlang.org" sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.7"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.4"
web_scraper: web_scraper:
dependency: "direct main" dependency: "direct main"
description: description:
name: web_scraper name: web_scraper
url: "https://pub.dartlang.org" sha256: "34d41de25c1aaeeca2cd91c1d062ee3126f59db6c84ec32da17d44d082ecc177"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4" version: "0.1.4"
win32:
dependency: transitive
description:
name: win32
sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
url: "https://pub.dev"
source: hosted
version: "5.0.5"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0+1" version: "1.0.1"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.3.1"
sdks: sdks:
dart: ">=2.17.0-0 <3.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=2.10.0" flutter: ">=3.10.0"
+6 -5
View File
@@ -1,5 +1,5 @@
name: lyricsgrabbr name: lyricsgrabbr
description: A new Flutter project. description: App that listens to your notifications and provides lyrics for the currently playing song.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
@@ -15,10 +15,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.1+1 version: 0.3.5+4
environment: environment:
sdk: ">=2.16.1 <3.0.0" sdk: ">=3.0.0 <4.0.0"
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@@ -31,9 +31,10 @@ dependencies:
sdk: flutter sdk: flutter
http: ^0.13.4 http: ^0.13.4
flutter_local_notifications: ^9.3.2
url_launcher: ^6.0.20 url_launcher: ^6.0.20
web_scraper: ^0.1.4 web_scraper: ^0.1.4
shared_preferences: ^2.0.15
provider: ^6.0.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -44,7 +45,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^1.0.0 flutter_lints: ^2.0.2
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec