2022-02-24 02:50:11 +02:00
import 'dart:async' ;
import 'package:flutter/material.dart' ;
import 'package:flutter/services.dart' ;
2022-02-24 21:56:38 +02:00
import 'notification_listener.dart' ;
2022-03-04 23:16:29 +02:00
import 'search_result.dart' ;
import 'package:http/http.dart' as http ;
import 'dart:convert' as convert ;
2022-02-24 02:50:11 +02:00
Future < void > main () async {
runApp ( const LyricsGrabbr ());
}
class LyricsGrabbr extends StatelessWidget {
const LyricsGrabbr ({ Key ? key }) : super ( key: key );
@ override
Widget build ( BuildContext context ) {
return MaterialApp (
title: 'Lyrics Grabbr' ,
theme: ThemeData (
2022-02-27 18:48:49 +02:00
primarySwatch: Colors . purple ,
2022-02-24 02:50:11 +02:00
),
home: const HomePage ( title: 'Lyrics Grabbr' ),
);
}
}
class HomePage extends StatefulWidget {
const HomePage ({ Key ? key , required this . title }) : super ( key: key );
final String title ;
@ override
State < HomePage > createState () => _HomePageState ();
}
class _HomePageState extends State < HomePage > with WidgetsBindingObserver {
late AndroidNotificationListener _notifications ;
BuildContext ? permissionDialogCtx ;
2022-03-04 23:16:29 +02:00
NotificationEvent ? lastNotification ;
2022-02-24 02:50:11 +02:00
bool running = false ;
2022-03-04 23:16:29 +02:00
bool searching = false ;
final List < SearchResult > searchResults = [];
2022-02-24 02:50:11 +02:00
@ override
void initState () {
WidgetsBinding . instance ? . addObserver ( this );
super . initState ();
2022-02-26 19:53:07 +02:00
initPlatformState ();
2022-02-24 02:50:11 +02:00
}
@ override
void dispose () {
WidgetsBinding . instance ? . removeObserver ( this );
super . dispose ();
}
@ override
void didChangeAppLifecycleState ( AppLifecycleState state ) async {
if ( state == AppLifecycleState . resumed ) {
if ( permissionDialogCtx == null ) {
return ;
}
var perm = await AndroidNotificationListener . hasPermission ;
if ( perm != null && perm ) {
Navigator . pop ( permissionDialogCtx ! );
startListening ();
}
}
}
2022-03-01 00:04:30 +02:00
void _showNotificationPermDialog () {
2022-02-24 21:56:38 +02:00
showDialog (
context: context ,
barrierDismissible: false ,
builder: ( BuildContext buildCtx ) {
permissionDialogCtx = buildCtx ;
return WillPopScope (
onWillPop: () {
SystemNavigator . pop ();
return Future . value ( false );
},
2022-03-04 23:16:29 +02:00
child: AlertDialog (
title: const Text (
'Notifications Access Permission' ,
style: TextStyle ( fontWeight: FontWeight . bold ),
2022-02-24 02:50:11 +02:00
),
2022-03-04 23:16:29 +02:00
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' )),
],
),
2022-02-24 21:56:38 +02:00
);
});
2022-02-24 02:50:11 +02:00
}
Future < void > initPlatformState () async {
var perm = await AndroidNotificationListener . hasPermission ;
if ( perm != null && perm ) {
startListening ();
} else {
2022-03-01 00:04:30 +02:00
_showNotificationPermDialog ();
2022-02-24 02:50:11 +02:00
}
}
2022-02-24 21:56:38 +02:00
void onData ( NotificationEvent event ) {
2022-03-04 23:16:29 +02:00
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 ;
});
}
2022-02-24 02:50:11 +02:00
}
void startListening () {
2022-03-01 00:04:30 +02:00
_notifications = AndroidNotificationListener ();
2022-02-24 02:50:11 +02:00
2022-03-04 23:16:29 +02:00
_notifications . notificationStream . listen ( onData );
2022-02-24 02:50:11 +02:00
setState (() => running = true );
}
2022-03-04 23:16:29 +02:00
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 ,
);
2022-02-24 02:50:11 +02:00
}
@ override
Widget build ( BuildContext context ) {
return Scaffold (
appBar: AppBar (
title: Text ( widget . title ),
2022-03-04 23:16:29 +02:00
centerTitle: true ,
2022-02-24 02:50:11 +02:00
),
2022-03-04 23:16:29 +02:00
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 (
2022-03-01 00:04:30 +02:00
fontSize: 24.0 ,
color: Colors . black ,
fontWeight: FontWeight . w700 ,
),
2022-03-04 23:16:29 +02:00
children: [
const TextSpan (
text: ' \n ' ,
),
TextSpan (
text: lastNotification ? . artist ,
2022-03-01 00:04:30 +02:00
style: const TextStyle (
fontSize: 20.0 ,
color: Color ( 0xFF303030 ),
2022-03-04 23:16:29 +02:00
),
)
]),
textAlign: TextAlign . center ,
2022-03-01 00:04:30 +02:00
),
2022-03-04 23:16:29 +02:00
),
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 ()),
],
),
),
),
],
2022-03-01 00:04:30 +02:00
),
),
2022-02-24 02:50:11 +02:00
);
}
}