Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images": [
{
"idiom": "universal",
"scale": "1x",
"filename": "icon.png"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "icon@2x.png"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "icon@3x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
},
"properties": {
"template-rendering-intent": "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images": [
{
"idiom": "universal",
"scale": "1x",
"filename": "icon.png"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "icon@2x.png"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "icon@3x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
},
"properties": {
"template-rendering-intent": "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images": [
{
"idiom": "universal",
"scale": "1x",
"filename": "icon.png"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "icon@2x.png"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "icon@3x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
},
"properties": {
"template-rendering-intent": "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images": [
{
"idiom": "universal",
"scale": "1x",
"filename": "icon.png"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "icon@2x.png"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "icon@3x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
},
"properties": {
"template-rendering-intent": "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import 'dart:async';
import 'package:app/audio_handler.dart';
import 'package:app/providers/providers.dart';
import 'package:app/ui/app.dart';
import 'package:app/utils/quick_actions.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';

late KoelAudioHandler audioHandler;
final quickActions = KoelQuickActions();

List<SingleChildWidget> _providers = [
Provider(create: (_) => AuthProvider()),
Expand Down Expand Up @@ -104,6 +106,10 @@ Future<void> main() async {
await GetStorage.init('Preferences');
await GetStorage.init(DownloadProvider.serializedPlayableContainer);

// Register early so a cold launch via a shortcut is captured and buffered
// until the main screen is ready to act on it.
quickActions.initialize();

runApp(
MultiProvider(
providers: _providers,
Expand Down
91 changes: 89 additions & 2 deletions lib/ui/screens/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import 'package:app/constants/constants.dart';
import 'package:app/enums.dart';
import 'package:app/main.dart';
import 'package:app/mixins/stream_subscriber.dart';
import 'package:app/models/models.dart';
import 'package:app/providers/providers.dart';
import 'package:app/ui/screens/screens.dart';
import 'package:app/ui/widgets/widgets.dart';
import 'package:app/utils/quick_actions.dart';
import 'package:app/utils/route_state.dart';
import 'package:audio_service/audio_service.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Expand All @@ -23,8 +26,9 @@ class MainScreen extends StatefulWidget {
_MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
class _MainScreenState extends State<MainScreen> with StreamSubscriber {
static const tabBarHeight = 60.0;
static const _searchTabIndex = 1;
late int _selectedIndex;
var _isOffline = AppState.get('mode', AppMode.online) == AppMode.offline;

Expand Down Expand Up @@ -68,7 +72,90 @@ class _MainScreenState extends State<MainScreen> {

context.read<DownloadSyncProvider>().scheduleSync();

WidgetsBinding.instance.addPostFrameCallback((_) => _restoreRoutes());
_setUpQuickActions();

WidgetsBinding.instance.addPostFrameCallback((_) {
_restoreRoutes();

final pendingAction = quickActions.consumePendingAction();
if (pendingAction != null) _handleQuickAction(pendingAction);
});
}

@override
void dispose() {
unsubscribeAll();
super.dispose();
}

void _setUpQuickActions() {
void refreshShortcuts(MediaItem? item) {
quickActions.setShortcuts(
recentSubtitle: KoelQuickActions.recentSubtitle(
artist: item?.artist,
title: item?.title,
),
);
}

refreshShortcuts(audioHandler.mediaItem.value);
subscribe(audioHandler.mediaItem.listen(refreshShortcuts));
subscribe(quickActions.actions.listen(_handleQuickAction));
}

Future<void> _handleQuickAction(String type) async {
switch (type) {
case KoelQuickActions.search:
_gotoSearchAndFocus();
break;
case KoelQuickActions.playFavorites:
final favoriteProvider = context.read<FavoriteProvider>();
await _shufflePlay(() => favoriteProvider.fetch());
break;
case KoelQuickActions.playDownloaded:
final downloadProvider = context.read<DownloadProvider>();
await _shufflePlay(() async => downloadProvider.playables);
break;
case KoelQuickActions.playRecent:
await _resumeQueue();
break;
}
}

void _gotoSearchAndFocus() {
if (_selectedIndex != _searchTabIndex) {
setState(() => _selectedIndex = _searchTabIndex);
RouteState.setTabIndex(_searchTabIndex);
}
quickActions.requestSearchFocus();
}

Future<void> _shufflePlay(
Future<List<Playable>> Function() loadPlayables) async {
try {
final playables = await loadPlayables();
if (playables.isNotEmpty) {
await audioHandler.replaceQueue(playables, shuffle: true);
}
} catch (_) {
// A quick action fails quietly (e.g. a favourites fetch while offline).
}
}

Future<void> _resumeQueue() async {
try {
// On a cold launch the persisted queue is restored asynchronously, so
// wait briefly for it before resuming.
if (audioHandler.queue.value.isEmpty) {
await audioHandler.queue
.firstWhere((items) => items.isNotEmpty)
.timeout(const Duration(seconds: 5), onTimeout: () => <MediaItem>[]);
}

if (audioHandler.queue.value.isNotEmpty) await audioHandler.play();
} catch (_) {
// A quick action fails quietly.
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void _restoreRoutes() {
Expand Down
25 changes: 24 additions & 1 deletion lib/ui/screens/search.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:app/constants/constants.dart';
import 'package:app/main.dart';
import 'package:app/mixins/stream_subscriber.dart';
import 'package:app/models/models.dart';
import 'package:app/providers/providers.dart';
import 'package:app/ui/widgets/widgets.dart';
Expand All @@ -17,7 +19,7 @@ class SearchScreen extends StatefulWidget {
_SearchScreenState createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {
class _SearchScreenState extends State<SearchScreen> with StreamSubscriber {
var _hasFocus = false;
var _initial = true;
var _playables = <Playable>[];
Expand All @@ -38,6 +40,27 @@ class _SearchScreenState extends State<SearchScreen> {
_focusNode.addListener(() {
setState(() => _hasFocus = _focusNode.hasFocus);
});

// Focus the field when reached via the "Search" quick action, whether this
// screen was already alive or is being built as a result of the action.
if (quickActions.consumePendingSearchFocus()) _focusSearchField();
subscribe(
quickActions.searchFocusRequests.listen((_) => _focusSearchField()),
);
}

void _focusSearchField() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _focusNode.requestFocus();
});
}

@override
void dispose() {
unsubscribeAll();
_focusNode.dispose();
_controller.dispose();
super.dispose();
}

_search(String keywords) =>
Expand Down
Loading
Loading