mirror of
https://github.com/IllusionMan1212/lyrics-grabbr.git
synced 2026-10-24 02:12:27 +00:00
feat: implement lyrics fetching and finalize first app version
This commit is contained in:
+141
-61
@@ -2,6 +2,9 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'notification_listener.dart';
|
||||
import 'search_result.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert' as convert;
|
||||
|
||||
Future<void> main() async {
|
||||
runApp(const LyricsGrabbr());
|
||||
@@ -33,10 +36,11 @@ class HomePage extends StatefulWidget {
|
||||
|
||||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
late AndroidNotificationListener _notifications;
|
||||
late StreamSubscription<NotificationEvent> _subscription;
|
||||
BuildContext? permissionDialogCtx;
|
||||
NotificationEvent? currentlyPlaying;
|
||||
NotificationEvent? lastNotification;
|
||||
bool running = false;
|
||||
bool searching = false;
|
||||
final List<SearchResult> searchResults = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -79,24 +83,24 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
SystemNavigator.pop();
|
||||
return Future.value(false);
|
||||
},
|
||||
child: AlertDialog(
|
||||
title: const Text(
|
||||
'Notifications Access Permission',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
content: const Text(
|
||||
'This app requires permission to listen to notifications to function properly. Please grant permission in the next screen.',
|
||||
style: TextStyle(color: Colors.blueGrey),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await AndroidNotificationListener
|
||||
.openPermissionSettings();
|
||||
},
|
||||
child: const Text("OK")),
|
||||
],
|
||||
child: AlertDialog(
|
||||
title: const Text(
|
||||
'Notifications Access Permission',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
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.',
|
||||
style: TextStyle(color: Colors.blueGrey),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await AndroidNotificationListener
|
||||
.openPermissionSettings();
|
||||
},
|
||||
child: const Text('OK')),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -112,21 +116,74 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
}
|
||||
|
||||
void onData(NotificationEvent event) {
|
||||
setState(() {
|
||||
currentlyPlaying = event;
|
||||
});
|
||||
if (event.playbackState == PlaybackState.STATE_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastNotification?.songTitle != event.songTitle &&
|
||||
lastNotification?.artist != event.artist) {
|
||||
searchLyrics(event.songTitle, event.artist);
|
||||
|
||||
setState(() {
|
||||
searchResults.clear();
|
||||
lastNotification = event;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void startListening() {
|
||||
_notifications = AndroidNotificationListener();
|
||||
|
||||
_subscription = _notifications.notificationStream.listen(onData);
|
||||
_notifications.notificationStream.listen(onData);
|
||||
setState(() => running = true);
|
||||
}
|
||||
|
||||
void stopListening() {
|
||||
setState(() => running = false);
|
||||
_subscription.cancel();
|
||||
void searchLyrics(
|
||||
String song,
|
||||
String artist,
|
||||
) async {
|
||||
setState(() {
|
||||
searching = true;
|
||||
});
|
||||
|
||||
// TODO: build an httpclient to reuse between requests.
|
||||
final uri = Uri.https(
|
||||
'api.illusionman1212.tech',
|
||||
'/lyrics/search',
|
||||
{'q': '$song $artist'},
|
||||
);
|
||||
|
||||
var response = await http.get(uri);
|
||||
if (response.statusCode == 200) {
|
||||
final jsonRes = convert.jsonDecode(response.body) as Map<String, dynamic>;
|
||||
List<dynamic> results = jsonRes['all'];
|
||||
|
||||
for (var result in results) {
|
||||
var myResult = SearchResult.fromJson(result);
|
||||
setState(() {
|
||||
searchResults.add(myResult);
|
||||
});
|
||||
}
|
||||
|
||||
setState(() {
|
||||
searching = false;
|
||||
});
|
||||
} else {
|
||||
// TODO: display error
|
||||
print('error in http get');
|
||||
setState(() {
|
||||
searching = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildResults() {
|
||||
return ListView.builder(
|
||||
itemBuilder: (_, i) {
|
||||
return searchResults[i];
|
||||
},
|
||||
itemCount: searchResults.length,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -134,52 +191,75 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
running ? RichText(
|
||||
text: TextSpan(
|
||||
text: currentlyPlaying?.songTitle,
|
||||
style: const TextStyle(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (running)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: lastNotification?.songTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 24.0,
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: currentlyPlaying?.artist,
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: '\n',
|
||||
),
|
||||
TextSpan(
|
||||
text: lastNotification?.artist,
|
||||
style: const TextStyle(
|
||||
fontSize: 20.0,
|
||||
color: Color(0xFF303030),
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
]),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
) :
|
||||
const Text("Listener stopped"),
|
||||
running ? Text(
|
||||
currentlyPlaying?.playbackState == PlaybackState.STATE_PLAYING ? "Spotify Playing" : "Spotify Paused",
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
) :
|
||||
const Text("")
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: searching
|
||||
? Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: const [
|
||||
CircularProgressIndicator(value: null)
|
||||
]))
|
||||
: Flexible(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
margin: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Text(
|
||||
running
|
||||
? 'Found ${searchResults.length} result(s)'
|
||||
: "",
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(child: _buildResults()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: running ? stopListening : startListening,
|
||||
tooltip: running ? 'Stop listener' : "Start listener",
|
||||
child: running ? const Icon(Icons.stop) : const Icon(Icons.play_arrow),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user