diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index f4eff8ba..3253c7ab 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -16,6 +16,7 @@ jobs: - run: | sudo apt-get update -y sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make libwebkit2gtk-4.0-dev keybinder-3.0 python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse + - run: dart bin/create-secrets.dart - run: flutter config --enable-linux-desktop - run: flutter pub get - run: flutter clean @@ -26,6 +27,8 @@ jobs: - run: chmod +x /usr/local/bin/appimagetool - run: pip3 install appimage-builder - run: make appimage + # Building Android Application + - run: flutter build apk - uses: actions/upload-artifact@v2 with: name: Spotube-Linux-Bundle @@ -33,6 +36,7 @@ jobs: build/Spotube-linux-x86_64.deb build/Spotube-linux-x86_64.tar.xz build/Spotube-*-x86_64.AppImage + build/app/outputs/apk/release/app-release.apk build_windows: runs-on: windows-latest steps: @@ -40,6 +44,7 @@ jobs: - uses: subosito/flutter-action@v2.2.0 with: cache: true + - run: dart bin/create-secrets.dart - run: flutter config --enable-windows-desktop - run: flutter build windows - run: choco install make -y @@ -59,6 +64,7 @@ jobs: - uses: subosito/flutter-action@v2 with: cache: true + - run: dart bin/create-secrets.dart - run: flutter config --enable-macos-desktop - run: flutter build macos - run: du -sh build/macos/Build/Products/Release/spotube.app diff --git a/README.md b/README.md index 128b1d34..861a40ef 100644 --- a/README.md +++ b/README.md @@ -121,9 +121,15 @@ You need a spotify account & a developer app for - Click on **SHOW CLIENT SECRET** to reveal the **clientSecret**. Then copy the **clientID**, **clientSecret** & paste in the **Spotube's** respective fields ![step-4](https://user-images.githubusercontent.com/61944859/111769501-7fe31e80-88d3-11eb-8fc1-f3655dbd4711.png) -Also, you need a [genius](https://genius.com) account for **lyrics** & a API Client for +**Setup Genius Lyrics (Optional)** -- accessToken +- Signup/Login into [genius](https://genius.com/signup) for **lyrics** +- Go To [Genius Developer Portal](https://genius.com/api-clients/new) for creating an API client + ![Step 2](https://user-images.githubusercontent.com/61944859/158823216-b4942731-c4c5-46c8-8b60-82a372b51cc5.png) +- Generate & copy access token + ![Step 3](https://user-images.githubusercontent.com/61944859/158822817-f04da060-3094-4a3b-8ace-a936d0cda8db.png) +- Paste the copied access token in Spotube's Settings + ![Step 4](https://user-images.githubusercontent.com/61944859/158823984-17f08534-5c92-41bc-918a-23194aad00f5.png) > **Note!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself diff --git a/android/app/build.gradle b/android/app/build.gradle index a26aa426..ceb59302 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -48,6 +48,7 @@ android { targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } buildTypes { @@ -65,4 +66,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:multidex:1.0.3' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 591455ab..5d1d8687 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + @@ -10,8 +12,8 @@ - - + + diff --git a/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java b/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java new file mode 100644 index 00000000..9213f130 --- /dev/null +++ b/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java @@ -0,0 +1,20 @@ +// Generated file. +// If you wish to remove Flutter's multidex support, delete this entire file. + +package io.flutter.app; + +import android.content.Context; +import androidx.annotation.CallSuper; +import androidx.multidex.MultiDex; + +/** + * Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support. + */ +public class FlutterMultiDexApplication extends FlutterApplication { + @Override + @CallSuper + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + } +} diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..91c72c3a Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..4c8c3960 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..808601ea Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..055ab8e8 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..769eb49b Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..5f349f7f --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4b..e39ff9e5 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b79..bbfb35a6 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d43914..07e99ea4 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d3..609ba6c7 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372ee..f599ec5b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..ab983282 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #ffffff + \ No newline at end of file diff --git a/assets/spotube-logo-foreground.png b/assets/spotube-logo-foreground.png new file mode 100644 index 00000000..03883ff5 Binary files /dev/null and b/assets/spotube-logo-foreground.png differ diff --git a/bin/create-secrets.dart b/bin/create-secrets.dart new file mode 100644 index 00000000..a16d89ee --- /dev/null +++ b/bin/create-secrets.dart @@ -0,0 +1,19 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:dotenv/dotenv.dart'; + +void main() async { + load(); + final bool hasKey = env.containsKey("SECRET"); + final val = hasKey ? jsonDecode(env["SECRET"]!) : null; + if (!hasKey || (hasKey && val is! List)) { + throw Exception( + "'SECRET' Environmental Variable isn't configured properly"); + } + + await File(path.join( + Directory.current.path, "lib/models/generated_secrets.dart")) + .writeAsString("final List secrets = ${env["SECRET"]};"); +} diff --git a/lib/components/Lyrics.dart b/lib/components/Lyrics.dart index 16cf56f5..194e6134 100644 --- a/lib/components/Lyrics.dart +++ b/lib/components/Lyrics.dart @@ -3,8 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; -import 'package:spotube/components/Settings.dart'; -import 'package:spotube/components/Shared/SpotubePageRoute.dart'; import 'package:spotube/helpers/artist-to-string.dart'; import 'package:spotube/helpers/getLyrics.dart'; import 'package:spotube/provider/Playback.dart'; @@ -39,7 +37,9 @@ class Lyrics extends HookConsumerWidget { var lyricsSnapshot = useFuture(lyricsFuture); useEffect(() { - if (lyricsSnapshot.hasData && lyricsSnapshot.data != null) { + if (lyricsSnapshot.hasData && + lyricsSnapshot.data != null && + playback.currentTrack != null) { lyrics.value = { "lyrics": lyricsSnapshot.data, "id": playback.currentTrack!.id! diff --git a/lib/helpers/get-random-element.dart b/lib/helpers/get-random-element.dart new file mode 100644 index 00000000..47390678 --- /dev/null +++ b/lib/helpers/get-random-element.dart @@ -0,0 +1,6 @@ +import 'dart:math'; + +final Random _random = Random(); +T getRandomElement(List list) { + return list[_random.nextInt(list.length)]; +} diff --git a/lib/helpers/getLyrics.dart b/lib/helpers/getLyrics.dart index b7034f8d..4ab5491c 100644 --- a/lib/helpers/getLyrics.dart +++ b/lib/helpers/getLyrics.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'package:html/parser.dart' as parser; import 'package:html/dom.dart'; import 'package:http/http.dart' as http; +import 'package:spotube/helpers/get-random-element.dart'; +import 'package:spotube/models/generated_secrets.dart'; String getTitle(String title, String artist) { return "$title $artist" @@ -47,16 +49,17 @@ Future extractLyrics(Uri url) async { Future searchSong( String title, String artist, { - String apiKey = "", + String? apiKey, bool optimizeQuery = false, bool authHeader = false, }) async { try { + if (apiKey == "" || apiKey == null) apiKey = getRandomElement(secrets); const searchUrl = 'https://api.genius.com/search?q='; String song = optimizeQuery ? getTitle(title, artist) : "$title $artist"; String reqUrl = "$searchUrl${Uri.encodeComponent(song)}"; - Map headers = {"Authorization": 'Bearer ' + apiKey}; + Map headers = {"Authorization": 'Bearer $apiKey'}; var response = await http.get( Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"), headers: authHeader ? headers : null, @@ -100,5 +103,6 @@ Future getLyrics( } catch (e, stack) { print("[getLyrics] $e"); print(stack); + return null; } } diff --git a/lib/main.dart b/lib/main.dart index 25918053..e51be40e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,9 +12,16 @@ import 'package:spotube/models/LocalStorageKeys.dart'; import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/ThemeProvider.dart'; import 'package:spotube/provider/YouTube.dart'; +import 'package:just_audio_background/just_audio_background.dart'; void main() async { - if (!Platform.isAndroid && !Platform.isIOS) { + if (Platform.isAndroid || Platform.isIOS) { + await JustAudioBackground.init( + androidNotificationChannelId: 'oss.krtirtho.Spotube', + androidNotificationChannelName: 'Spotube', + androidNotificationOngoing: true, + ); + } else { WidgetsFlutterBinding.ensureInitialized(); await hotKeyManager.unregisterAll(); doWhenWindowReady(() { diff --git a/lib/provider/Playback.dart b/lib/provider/Playback.dart index f87a0046..27f9a468 100644 --- a/lib/provider/Playback.dart +++ b/lib/provider/Playback.dart @@ -4,7 +4,10 @@ import 'package:audio_session/audio_session.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:just_audio_background/just_audio_background.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/helpers/artist-to-string.dart'; +import 'package:spotube/helpers/image-to-url-string.dart'; import 'package:spotube/helpers/search-youtube.dart'; import 'package:spotube/provider/AudioPlayer.dart'; import 'package:spotube/provider/YouTube.dart'; @@ -221,10 +224,17 @@ class Playback extends ChangeNotifier { track ??= _currentTrack; if (track != null && await _audioSession?.setActive(true) == true) { Uri? parsedUri = Uri.tryParse(track.uri ?? ""); + final tag = MediaItem( + id: track.id!, + title: track.name!, + album: track.album?.name, + artist: artistsToString(track.artists ?? []), + artUri: Uri.parse(imageToUrlString(track.album?.images)), + ); if (parsedUri != null && parsedUri.hasAbsolutePath) { await player .setAudioSource( - AudioSource.uri(parsedUri), + AudioSource.uri(parsedUri, tag: tag), preload: true, ) .then((value) async { @@ -238,7 +248,7 @@ class Playback extends ChangeNotifier { if (setTrackUriById(track.id!, ytTrack.uri!)) { await player .setAudioSource( - AudioSource.uri(Uri.parse(ytTrack.uri!)), + AudioSource.uri(Uri.parse(ytTrack.uri!), tag: tag), preload: true, ) .then((value) { diff --git a/pubspec.lock b/pubspec.lock index 5a3d5be2..1b874a9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,6 +29,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + audio_service: + dependency: transitive + description: + name: audio_service + url: "https://pub.dartlang.org" + source: hosted + version: "0.18.4" + audio_service_platform_interface: + dependency: transitive + description: + name: audio_service_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + audio_service_web: + dependency: transitive + description: + name: audio_service_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" audio_session: dependency: "direct main" description: @@ -148,6 +169,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + dotenv: + dependency: "direct dev" + description: + name: dotenv + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" fake_async: dependency: transitive description: @@ -195,6 +223,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.18.2+1" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.2" flutter_lints: dependency: "direct dev" description: @@ -303,6 +338,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.20" + just_audio_background: + dependency: "direct main" + description: + name: just_audio_background + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1-beta.5" just_audio_libwinmedia: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 87dc4d5f..f5ad419d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A lightweight free Spotify desktop-client which handles playback ma # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -30,7 +30,6 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 @@ -55,6 +54,7 @@ dependencies: go_router: ^3.0.4 palette_generator: ^0.3.3 audio_session: ^0.1.6+1 + just_audio_background: ^0.0.1-beta.5 dev_dependencies: flutter_test: @@ -67,13 +67,14 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^1.0.0 + flutter_launcher_icons: ^0.9.2 + dotenv: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. @@ -116,4 +117,10 @@ msix_config: identity_name: oss.krtirtho.spotube msix_version: 1.0.1.0 logo_path: .\assets\spotube-logo.png - capabilities: "internetClient,internetClientServer,backgroundMediaPlayback" \ No newline at end of file + capabilities: "internetClient,internetClientServer,backgroundMediaPlayback" + +flutter_icons: + android: true + image_path: "assets/spotube-logo.png" + adaptive_icon_foreground: "assets/spotube-logo-foreground.png" + adaptive_icon_background: "#ffffff"