-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored quick actions and notifications to use a unified ExternalA…
…ction type
- Loading branch information
Showing
8 changed files
with
165 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import 'package:finale/services/generic.dart'; | ||
import 'package:flutter/foundation.dart'; | ||
import 'package:rxdart/rxdart.dart'; | ||
|
||
import 'profile_tab.dart'; | ||
import 'time_safe_stream.dart'; | ||
|
||
/// An action taken outside of the app that causes the app to open. | ||
/// | ||
/// Sources are quick actions, iOS widgets, and notifications. | ||
class ExternalAction { | ||
final ExternalActionType type; | ||
final dynamic value; | ||
|
||
ExternalAction.scrobbleOnce() | ||
: type = ExternalActionType.scrobbleOnce, | ||
value = null; | ||
|
||
ExternalAction.scrobbleContinuously() | ||
: type = ExternalActionType.scrobbleContinuously, | ||
value = null; | ||
|
||
ExternalAction.viewAlbum(BasicAlbum album) | ||
: type = ExternalActionType.viewAlbum, | ||
value = album; | ||
|
||
ExternalAction.viewArtist(BasicArtist artist) | ||
: type = ExternalActionType.viewArtist, | ||
value = artist; | ||
|
||
ExternalAction.viewTrack(Track track) | ||
: type = ExternalActionType.viewTrack, | ||
value = track; | ||
|
||
ExternalAction.viewTab(ProfileTab tab) | ||
: type = ExternalActionType.viewTab, | ||
value = tab; | ||
|
||
ExternalAction.openSpotifyChecker() | ||
: type = ExternalActionType.openSpotifyChecker, | ||
value = null; | ||
} | ||
|
||
enum ExternalActionType { | ||
scrobbleOnce, | ||
scrobbleContinuously, | ||
viewAlbum, | ||
viewArtist, | ||
viewTrack, | ||
viewTab, | ||
openSpotifyChecker, | ||
} | ||
|
||
// This stream needs to be open for the entire lifetime of the app. | ||
// ignore: close_sinks | ||
@protected | ||
final externalActions = ReplaySubject<Timestamped<ExternalAction>>(); | ||
|
||
/// A stream of [ExternalAction]s that should be handled. | ||
Stream<ExternalAction> get externalActionsStream => | ||
externalActions.timeSafeStream(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,136 +1,86 @@ | ||
/// Handles [QuickActions] and initial links by firing the corresponding | ||
/// [ExternalAction]s. | ||
import 'package:finale/services/generic.dart'; | ||
import 'package:finale/util/profile_tab.dart'; | ||
import 'package:finale/util/external_actions.dart'; | ||
import 'package:quick_actions/quick_actions.dart'; | ||
import 'package:rxdart/rxdart.dart'; | ||
import 'package:uni_links/uni_links.dart'; | ||
|
||
import 'profile_tab.dart'; | ||
import 'time_safe_stream.dart'; | ||
|
||
class QuickAction { | ||
final QuickActionType type; | ||
final dynamic value; | ||
|
||
QuickAction.scrobbleOnce() | ||
: type = QuickActionType.scrobbleOnce, | ||
value = null; | ||
|
||
QuickAction.scrobbleContinuously() | ||
: type = QuickActionType.scrobbleContinuously, | ||
value = null; | ||
|
||
QuickAction.viewAlbum(BasicAlbum album) | ||
: type = QuickActionType.viewAlbum, | ||
value = album; | ||
|
||
QuickAction.viewArtist(BasicArtist artist) | ||
: type = QuickActionType.viewArtist, | ||
value = artist; | ||
|
||
QuickAction.viewTrack(Track track) | ||
: type = QuickActionType.viewTrack, | ||
value = track; | ||
|
||
QuickAction.viewTab(ProfileTab tab) | ||
: type = QuickActionType.viewTab, | ||
value = tab; | ||
} | ||
Future<void> setup() async { | ||
const quickActions = QuickActions(); | ||
await quickActions.initialize((type) { | ||
_handleLink(Uri(host: type)); | ||
}); | ||
await quickActions.setShortcutItems(const [ | ||
ShortcutItem( | ||
type: 'scrobbleonce', | ||
localizedTitle: 'Recognize song', | ||
icon: 'add', | ||
), | ||
ShortcutItem( | ||
type: 'scrobblecontinuously', | ||
localizedTitle: 'Recognize continuously', | ||
icon: 'all_inclusive', | ||
), | ||
]); | ||
|
||
try { | ||
final initialUri = await getInitialUri(); | ||
_handleLink(initialUri); | ||
} on FormatException { | ||
// Do nothing. | ||
} | ||
|
||
enum QuickActionType { | ||
scrobbleOnce, | ||
scrobbleContinuously, | ||
viewAlbum, | ||
viewArtist, | ||
viewTrack, | ||
viewTab, | ||
uriLinkStream.listen((uri) { | ||
_handleLink(uri); | ||
}); | ||
} | ||
|
||
class QuickActionsManager { | ||
static QuickActionsManager? _instance; | ||
|
||
factory QuickActionsManager() => _instance ??= QuickActionsManager._(); | ||
|
||
QuickActionsManager._(); | ||
|
||
// This stream needs to be open for the entire lifetime of the app. | ||
// ignore: close_sinks | ||
final _quickActions = ReplaySubject<Timestamped<QuickAction>>(); | ||
|
||
/// A stream of [QuickAction]s that should be handled. | ||
Stream<QuickAction> get quickActionStream => _quickActions.timeSafeStream(); | ||
|
||
Future<void> setup() async { | ||
const quickActions = QuickActions(); | ||
await quickActions.initialize((type) { | ||
_handleLink(Uri(host: type)); | ||
}); | ||
await quickActions.setShortcutItems(const [ | ||
ShortcutItem( | ||
type: 'scrobbleonce', | ||
localizedTitle: 'Recognize song', | ||
icon: 'add', | ||
), | ||
ShortcutItem( | ||
type: 'scrobblecontinuously', | ||
localizedTitle: 'Recognize continuously', | ||
icon: 'all_inclusive', | ||
), | ||
]); | ||
|
||
try { | ||
final initialUri = await getInitialUri(); | ||
_handleLink(initialUri); | ||
} on FormatException { | ||
// Do nothing. | ||
void _handleLink(Uri? uri) { | ||
if (uri == null) { | ||
return; | ||
} else if (uri.host == 'scrobbleonce') { | ||
externalActions.addTimestamped(ExternalAction.scrobbleOnce()); | ||
} else if (uri.host == 'scrobblecontinuously') { | ||
externalActions.addTimestamped(ExternalAction.scrobbleContinuously()); | ||
} else if (uri.host == 'album') { | ||
final name = uri.queryParameters['name']!; | ||
final artist = uri.queryParameters['artist']!; | ||
externalActions.addTimestamped(ExternalAction.viewAlbum( | ||
ConcreteBasicAlbum(name, ConcreteBasicArtist(artist)))); | ||
} else if (uri.host == 'artist') { | ||
final name = uri.queryParameters['name']!; | ||
externalActions | ||
.addTimestamped(ExternalAction.viewArtist(ConcreteBasicArtist(name))); | ||
} else if (uri.host == 'track') { | ||
final name = uri.queryParameters['name']!; | ||
final artist = uri.queryParameters['artist']!; | ||
externalActions.addTimestamped( | ||
ExternalAction.viewTrack(BasicConcreteTrack(name, artist, null))); | ||
} else if (uri.host == 'profiletab') { | ||
final tabString = uri.queryParameters['tab']; | ||
ProfileTab tab; | ||
|
||
switch (tabString) { | ||
case 'scrobble': | ||
tab = ProfileTab.recentScrobbles; | ||
break; | ||
case 'artist': | ||
tab = ProfileTab.topArtists; | ||
break; | ||
case 'album': | ||
tab = ProfileTab.topAlbums; | ||
break; | ||
case 'track': | ||
tab = ProfileTab.topTracks; | ||
break; | ||
default: | ||
throw ArgumentError.value(tabString, 'tab', 'Unknown tab'); | ||
} | ||
|
||
uriLinkStream.listen((uri) { | ||
_handleLink(uri); | ||
}); | ||
} | ||
|
||
void _handleLink(Uri? uri) { | ||
if (uri == null) { | ||
return; | ||
} else if (uri.host == 'scrobbleonce') { | ||
_quickActions.addTimestamped(QuickAction.scrobbleOnce()); | ||
} else if (uri.host == 'scrobblecontinuously') { | ||
_quickActions.addTimestamped(QuickAction.scrobbleContinuously()); | ||
} else if (uri.host == 'album') { | ||
final name = uri.queryParameters['name']!; | ||
final artist = uri.queryParameters['artist']!; | ||
_quickActions.addTimestamped(QuickAction.viewAlbum( | ||
ConcreteBasicAlbum(name, ConcreteBasicArtist(artist)))); | ||
} else if (uri.host == 'artist') { | ||
final name = uri.queryParameters['name']!; | ||
_quickActions | ||
.addTimestamped(QuickAction.viewArtist(ConcreteBasicArtist(name))); | ||
} else if (uri.host == 'track') { | ||
final name = uri.queryParameters['name']!; | ||
final artist = uri.queryParameters['artist']!; | ||
_quickActions.addTimestamped( | ||
QuickAction.viewTrack(BasicConcreteTrack(name, artist, null))); | ||
} else if (uri.host == 'profiletab') { | ||
final tabString = uri.queryParameters['tab']; | ||
ProfileTab tab; | ||
|
||
switch (tabString) { | ||
case 'scrobble': | ||
tab = ProfileTab.recentScrobbles; | ||
break; | ||
case 'artist': | ||
tab = ProfileTab.topArtists; | ||
break; | ||
case 'album': | ||
tab = ProfileTab.topAlbums; | ||
break; | ||
case 'track': | ||
tab = ProfileTab.topTracks; | ||
break; | ||
default: | ||
throw ArgumentError.value(tabString, 'tab', 'Unknown tab'); | ||
} | ||
|
||
_quickActions.addTimestamped(QuickAction.viewTab(tab)); | ||
} | ||
externalActions.addTimestamped(ExternalAction.viewTab(tab)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.