Merge branch 'master' into build
Adds the latest modifications and features for build environment
6
.github/workflows/flutter-build.yml
vendored
@ -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
|
||||
|
10
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
|
||||

|
||||
|
||||
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
|
||||

|
||||
- Generate & copy access token
|
||||

|
||||
- Paste the copied access token in Spotube's Settings
|
||||

|
||||
|
||||
> **Note!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself
|
||||
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<queries>
|
||||
<!-- If your app opens https URLs -->
|
||||
@ -10,8 +12,8 @@
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application android:label="spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true">
|
||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
<application android:label="Spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true">
|
||||
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
@ -23,6 +25,19 @@
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<service android:name="com.ryanheise.audioservice.AudioService">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 74 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 28 KiB |
4
android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#ffffff</color>
|
||||
</resources>
|
BIN
assets/spotube-logo-foreground.png
Normal file
After Width: | Height: | Size: 114 KiB |
19
bin/create-secrets.dart
Normal file
@ -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<String> secrets = ${env["SECRET"]};");
|
||||
}
|
@ -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!
|
||||
|
6
lib/helpers/get-random-element.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
final Random _random = Random();
|
||||
T getRandomElement<T>(List<T> list) {
|
||||
return list[_random.nextInt(list.length)];
|
||||
}
|
@ -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<String?> extractLyrics(Uri url) async {
|
||||
Future<List?> 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<String, String> headers = {"Authorization": 'Bearer ' + apiKey};
|
||||
Map<String, String> 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<String?> getLyrics(
|
||||
} catch (e, stack) {
|
||||
print("[getLyrics] $e");
|
||||
print(stack);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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(() {
|
||||
|
@ -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 ?? <ArtistSimple>[]),
|
||||
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) {
|
||||
|
42
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:
|
||||
|
15
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"
|
||||
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"
|
||||
|