5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome !
4 | There are many ways you can contribute to Blade : report bugs, suggest features, translate it to
5 | your language, fixing a bug, or implementing a new feature.
6 |
7 | ## Translating Blade
8 |
9 | Like any (i think ?) Android app, Blade uses strings in ressource files : you can find the default (
10 | english) one in [app/src/main/res/values/strings.xml](app/src/main/res/values/strings.xml). To
11 | translate Blade, just get this file and replace every string in it with their translation ; you can
12 | then send the file to me, and i'll update the language files.
13 |
14 | ## Contributing code
15 |
16 | Blade is entirely programmed in Java for Android, using the AndroidX library, with xml layouts and
17 | ressources. Adding code relying on Kotlin, Jetpack Compose, or other libraries doing 'core' Android
18 | job is obviously bad ; please try to keep the design decisions that were made.
19 |
20 | ### Code style
21 |
22 | Blade has a code style that might be considered unusual for Java. Basically, i write like this :
23 |
24 | ```
25 | void thisIsFunction(String arg0, int arg1)
26 | {
27 | if(arg1 == 3) return;
28 | else if(arg1 == 4)
29 | {
30 | System.out.println("4 !");
31 | doSomething();
32 | }
33 |
34 | System.out.println(arg1 + ": " + arg0);
35 | }
36 | ```
37 |
38 | and not like this :
39 |
40 | ```
41 | void thisIsFunction(String arg0, int arg1) {
42 | if(arg1 == 3) {
43 | return;
44 | }
45 | else if(arg1 == 4) {
46 | System.out.println("4 !");
47 | doSomething();
48 | }
49 |
50 | System.out.println(arg1 + ": " + arg0);
51 | }
52 | ```
53 |
54 | Please try to stick to this style ; if you are using Android Studio or IntelliJ Idea, the project is
55 | configured with this code style, you can just code the way you want and autoformat before commiting.
56 |
57 | ### Adding new features
58 |
59 | If you want to add a new feature to Blade, go ahead. However, know that this is a personal project,
60 | and if i don't like your feature, i won't add it to mainline. It does not mean i hate you or your
61 | code, it is just that i have no interest in that feature, so i don't want it in my Blade version ;
62 | but feel free to keep a fork of the project with your feature.
63 |
64 | ## Blade internals : global architecture
65 |
66 | Blade consists of 4 components :
67 | - UI (User Interface) with activities/settings/...
68 | - Library, handling library and Blade internals Song, Album, Artist, Playlist types
69 | - Source, handling source submodules
70 | - Player, that handles the music playing
71 |
72 | ## Blade internals : adding a new source
73 |
74 | A Source component has to :
75 | - Register songs in library
76 | - Provide a way to play songs of such source in a SourcePlayer
77 | - Have an explore/search adapter
78 | - Provide ways of interacting with it (adding songs to playlists, removing elements, ...)
79 |
80 | All of this is done extending the Source class.
81 |
82 | ## GitHub actions setup
83 |
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Blade Player
2 | 
3 | 
4 | 
5 | 
6 |
7 |
8 |
9 | Blade is an open source music player for Android, allowing you to play music from multiple
10 | services : files on your phone, [Spotify], and more.
11 |
12 | Blade is available on [Google Play], or [here on GitHub].
13 | It seems to also be on [IzzySoft F-Droid], and to stay up to date there.
14 |
15 | Other repos like ~~[Aptoide]~~ or even ~~[ApkPure]~~, ~~[ApkCombo]~~ are not recommended because
16 | they don't seem
17 | to stay up to date fast, and i can't check if they modified the `apk` before publishing it.
18 |
19 |
20 |
21 |
22 |
23 |
24 | ## Feature overview
25 |
26 | Blade Player is developed by me ([@vhaudiquet]) alone, so the project cannot be tested on many
27 | devices and scenarios ; if you find an issue, open one here.
28 |
29 | - The app will open on your library, categorized as Artists, Albums, Songs, and Playlists (available
30 | in the navigation drawer).
31 | - It allows you to manage libraries for songs (add/remove from source library) and playlists (
32 | add/remove from playlist, create/remove playlists)
33 | - It supports Android 'dark theme' (the screenshots above are done on a dark themed system).
34 | - It is completely free (there are no ads, no limited version)
35 | - It caches the library locally, so launching Blade requires virtually no data (only refreshing
36 | tokens and status of sources servers)
37 | - **(TODO)** It has a 'data saving' mode that allows you to listen to music while consuming very low
38 | mobile data (by not loading album arts)
39 | - The search feature allows you to search the local library instantly
40 | - The "explore" mode allows you to search and browse sources for new music (for example
41 | search all Spotify and look for new releases)
42 | - It has a layout that can adapt for tablet users (landscape layout)
43 | - It is available in different languages : English, French, German, Turkish ([and you can easily help me traduce it](CONTRIBUTING.md))
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ### Supported services (music sources)
56 |
57 | - Local (i.e. files on your phone)
58 | - Spotify
59 |
60 | ### About Spotify
61 |
62 | You will need a **Spotify Premium** account to play music from [Spotify], but you can use Blade
63 | without a premium account (to play your Spotify playlists from other sources, for example)
64 |
65 | Blade is using the official [Spotify Android Auth] library and [Retrofit] to access
66 | the [Spotify Web API], i.e. to obtain user library and playlists. In order to play music from
67 | Spotify, Blade uses the [librespot-java] library.
68 |
69 | For now, Blade will act as a Spotify Connect peripheral. **Please do not try to control Blade with
70 | Spotify Connect**. It won't work, and it will glitch the app.
71 |
72 | When connecting Blade and Spotify, i am for now obligated to ask directly for your username and
73 | password, 2 times : one for [librespot-java], which requires them directly (Spotify does not allow
74 | streaming music from their API authentification), and one for [Spotify Android Auth], which uses the
75 | secure OAuth2 protocol. I can only promise you that i am not stealing your credentials ; if you are
76 | paranoid, you may build Blade from sources or use a network analyzer to see that all network traffic
77 | goes to [Spotify] servers.
78 |
79 | Special thanks to the people at [librespot-org] and [librespot-java] ; without them, Spotify support
80 | would not have been possible.
81 |
82 | ## Future updates
83 |
84 | New features and bug fixes or improvements are coming. Here is a list of what i want to implement :
85 |
86 | - SPOTIFY: Use Android Native Decoders, and/or libtremolo if native decoder not present
87 | - UI: Show albums in an 'album view' instead of a list
88 | - Add new services : SoundCloud, YouTube Music, Amazon Music, Tidal, WebDAV/FTP Servers...
89 | - CORE: 'Blade' playlists, that can contain song from all sources
90 |
91 | ## Contributing
92 |
93 | See [CONTRIBUTING.md]
94 |
95 | ## Older versions
96 |
97 | If you want older (i.e. < 2.0) versions of Blade, you can check the [old repository].
98 |
99 | [Google Play]:https://play.google.com/store/apps/details?id=v.blade
100 |
101 | [here on GitHub]:https://github.com/vhaudiquet/BladePlayer/releases
102 |
103 | [IzzySoft F-Droid]:https://apt.izzysoft.de/fdroid/index/apk/v.blade
104 |
105 | [Aptoide]:https://blade-blade.en.aptoide.com/app
106 |
107 | [ApkPure]:https://apkpure.com/blade-player/v.blade/
108 |
109 | [ApkCombo]:https://apkcombo.com/blade-player/v.blade/
110 |
111 | [Spotify]:https://www.spotify.com
112 |
113 | [old repository]:https://github.com/vhaudiquet/blade-player
114 |
115 | [Spotify Android Auth]:https://github.com/spotify/android-auth
116 |
117 | [Retrofit]:https://github.com/square/retrofit
118 |
119 | [Spotify Web API]:https://developer.spotify.com/documentation/web-api/
120 |
121 | [librespot-java]:https://github.com/librespot-org/librespot-java
122 |
123 | [librespot-org]:https://github.com/librespot-org
124 |
125 | [@vhaudiquet]:https://github.com/vhaudiquet
126 |
127 | [CONTRIBUTING.md]:CONTRIBUTING.md
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'kotlin-android'
3 | id 'com.android.application'
4 | }
5 |
6 | android {
7 | compileSdk 34
8 |
9 | defaultConfig {
10 | applicationId "v.blade"
11 | minSdk 21
12 | targetSdk 34
13 | versionCode 31
14 | versionName "2.2.2"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | android.buildFeatures.buildConfig true
18 | manifestPlaceholders = [redirectSchemeName: "spotify-sdk", redirectHostName: "auth"]
19 | }
20 |
21 | signingConfigs {
22 | release {
23 | storeFile file("../blade-keystore.jks")
24 | keyAlias "blade-release"
25 | if (project.hasProperty("releaseStorePassword"))
26 | storePassword project.getProperty("releaseStorePassword")
27 | if (project.hasProperty("releaseKeyPassword"))
28 | keyPassword project.getProperty("releaseKeyPassword")
29 | }
30 | }
31 |
32 | buildTypes {
33 | release {
34 | buildConfigField 'String', 'SPOTIFY_CLIENT_ID', '\"2f95bc7168584e7aa67697418a684bae\"'
35 | buildConfigField 'String', 'SPOTIFY_CLIENT_SECRET', '\"3166d3b40ff74582b03cb23d6701c297\"'
36 |
37 | buildConfigField 'String', 'DEEZER_CLIENT_ID', '\"172365\"'
38 | buildConfigField 'String', 'DEEZER_CLIENT_SECRET', '\"fb0bec7ccc063dab0417eb7b0d847f34\"'
39 |
40 | signingConfig signingConfigs.release
41 | minifyEnabled true
42 | shrinkResources true
43 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
44 | }
45 |
46 | debug {
47 | /* Note : these are MY Spotify debug app settings, you should change that if you want debug builds to work with Spotify
48 | * Later, if more people work on the project, those 2 variables should be passed via command-line for debug builds */
49 | buildConfigField 'String', 'SPOTIFY_CLIENT_ID', '\"048adc76814146e7bb049d89813bd6e0\"'
50 | buildConfigField 'String', 'SPOTIFY_CLIENT_SECRET', '\"854982b95dc04100a2009ebb8a8df758\"'
51 |
52 | buildConfigField 'String', 'DEEZER_CLIENT_ID', '\"\"'
53 | buildConfigField 'String', 'DEEZER_CLIENT_SECRET', '\"\"'
54 |
55 | debuggable true
56 | }
57 | }
58 | compileOptions {
59 | sourceCompatibility 17
60 | targetCompatibility 17
61 | }
62 | buildFeatures {
63 | viewBinding true
64 | }
65 | packagingOptions {
66 | resources {
67 | excludes += ['log4j2.xml', 'META-INF/DEPENDENCIES']
68 | }
69 | }
70 | namespace 'v.blade'
71 | }
72 |
73 | dependencies {
74 | /* stdlib */
75 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21'
76 | implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.21'
77 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.21'
78 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21'
79 |
80 | /* AndroidX (Jetpack) : compat libraries, and Material */
81 | implementation "androidx.exifinterface:exifinterface:1.3.7"
82 | implementation 'androidx.appcompat:appcompat:1.6.1'
83 | implementation 'com.google.android.material:material:1.11.0'
84 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
85 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
86 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
87 | implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
88 | implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
89 | implementation 'androidx.preference:preference-ktx:1.2.1'
90 | implementation 'androidx.media:media:1.7.0'
91 |
92 | /* Image loading : Picasso */
93 | implementation 'com.squareup.picasso:picasso:2.8'
94 |
95 | /* Spotify : librespot + auth (+ retrofit) */
96 | /*implementation('xyz.gianlu.librespot:librespot-player:1.6.2:thin') {
97 | exclude group: 'xyz.gianlu.librespot', module: 'librespot-sink'
98 | exclude group: 'com.lmax', module: 'disruptor'
99 | exclude group: 'org.apache.logging.log4j'
100 | }*/
101 | implementation files('libs/librespot-player-1.6.4-20231011.152351-29-thin.jar')
102 | implementation files('libs/librespot-lib-1.6.4-20231011.152351-32.jar')
103 | implementation files('libs/librespot-sink-api-1.6.4-20231011.152351-35.jar')
104 | implementation files('libs/librespot-dacp-1.6.4-20231011.152351-33.jar')
105 | implementation files('libs/librespot-decoder-api-1.6.4-20231011.152351-32.jar')
106 | implementation 'org.jcraft:jorbis:0.0.17'
107 | implementation 'com.badlogicgames.jlayer:jlayer:1.0.2-gdx'
108 | implementation 'com.google.protobuf:protobuf-java:3.25.1'
109 | implementation 'commons-net:commons-net:3.10.0'
110 | implementation 'com.google.code.gson:gson:2.10.1'
111 |
112 | implementation 'uk.uuid.slf4j:slf4j-android:2.0.9-0' //Needed to log librespot-player
113 |
114 | implementation 'com.spotify.android:auth:2.1.0'
115 | implementation 'com.squareup.retrofit2:retrofit:2.9.0'
116 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
117 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
118 |
119 | testImplementation 'junit:junit:4.13.2'
120 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
121 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
122 | androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1'
123 | androidTestImplementation 'androidx.test.espresso:espresso-web:3.5.1'
124 | }
125 |
--------------------------------------------------------------------------------
/app/libs/librespot-dacp-1.6.4-20231011.152351-33.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/libs/librespot-dacp-1.6.4-20231011.152351-33.jar
--------------------------------------------------------------------------------
/app/libs/librespot-decoder-api-1.6.4-20231011.152351-32.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/libs/librespot-decoder-api-1.6.4-20231011.152351-32.jar
--------------------------------------------------------------------------------
/app/libs/librespot-lib-1.6.4-20231011.152351-32.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/libs/librespot-lib-1.6.4-20231011.152351-32.jar
--------------------------------------------------------------------------------
/app/libs/librespot-player-1.6.4-20231011.152351-29-thin.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/libs/librespot-player-1.6.4-20231011.152351-29-thin.jar
--------------------------------------------------------------------------------
/app/libs/librespot-sink-api-1.6.4-20231011.152351-35.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/libs/librespot-sink-api-1.6.4-20231011.152351-35.jar
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Project-specific ProGuard rules
2 |
3 | # We optimize, so we obfuscate ; to be able to read
4 | # debug stack trace, we need this mapping
5 | -keepattributes LineNumberTable,SourceFile
6 | -renamesourcefileattribute SourceFile
7 |
8 | # We save sources using their class, and instanciate
9 | # them using reflection : we need their class to stay
10 | # the same
11 | -keep public class * extends v.blade.sources.Source
12 | -keepclassmembers public class * extends v.blade.sources.Source
13 | {
14 | public static final int NAME_RESOURCE;
15 | public static final int DESCRIPTION_RESOURCE;
16 | public static final int IMAGE_RESOURCE;
17 | }
18 | -keep class v.blade.sources.spotify.Spotify$SpotifyTokenResponse {*;}
19 | -keep class v.blade.sources.spotify.SpotifyService$* {*;}
20 | -keep class v.blade.sources.deezer.Deezer$DeezerTokenResponse {*;}
21 | -keep class v.blade.sources.deezer.Deezer$DeezerErrorObject {*;}
22 | -keep class v.blade.sources.deezer.DeezerService$* {*;}
23 |
24 | # The spotify librespot player needs an 'output class'
25 | # for it's audio output ; we need to keep that
26 | -keep class v.blade.sources.spotify.SpotifyPlayer$BladeSinkOutput
27 |
28 | # librespot needs reflexion to work internally
29 | # Here we keep the classes it needs
30 | -keep class xyz.gianlu.librespot.mercury.MercuryRequests$GenericJson
31 | {
32 | (com.google.gson.JsonObject); # method i.e. constructor
33 | }
34 | -keep class xyz.gianlu.librespot.json.GenericJson
35 | {
36 | (com.google.gson.JsonObject); # method i.e. constructor
37 | }
38 | -keep class xyz.gianlu.librespot.mercury.MercuryRequests$ResolvedContextWrapper
39 | {
40 | (com.google.gson.JsonObject); # method i.e. constructor
41 | }
42 | -keep class xyz.gianlu.librespot.json.ResolvedContextWrapper
43 | {
44 | (com.google.gson.JsonObject); # method i.e. constructor
45 | }
46 | -keep class com.spotify.** {*;}
47 | -keep class xyz.gianlu.librespot.audio.decoders.** {*;}
48 |
49 | # We load settings fragment using reflexion
50 | # We need to keep the SettingsActivity class for that
51 | -keep class v.blade.ui.SettingsActivity$AboutFragment
52 | -keep class v.blade.ui.SettingsActivity$SourcesFragment
53 |
54 | # For lyrics, we use Genius API, as a retrofit service : reflexion
55 | -keep class v.blade.ui.GeniusService$* {*;}
56 |
57 | # OkHttp version < 5.0 warns for this missing ; this is
58 | # a bug (https://github.com/square/okhttp/issues/6258)
59 | # We can safely ignore (those classes are not needed on Android)
60 | -dontwarn org.bouncycastle.jsse.BCSSLParameters
61 | -dontwarn org.bouncycastle.jsse.BCSSLSocket
62 | -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
63 | -dontwarn org.conscrypt.Conscrypt$Version
64 | -dontwarn org.conscrypt.Conscrypt
65 | -dontwarn org.conscrypt.ConscryptHostnameVerifier
66 | -dontwarn org.openjsse.javax.net.ssl.SSLParameters
67 | -dontwarn org.openjsse.javax.net.ssl.SSLSocket
68 | -dontwarn org.openjsse.net.ssl.OpenJSSE
69 |
70 | # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
71 | # EnclosingMethod is required to use InnerClasses.
72 | -keepattributes Signature, InnerClasses, EnclosingMethod
73 |
74 | # Retrofit does reflection on method and parameter annotations.
75 | -keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
76 |
77 | # Keep annotation default values (e.g., retrofit2.http.Field.encoded).
78 | -keepattributes AnnotationDefault
79 |
80 | # Retain service method parameters when optimizing.
81 | -keepclassmembers,allowshrinking,allowobfuscation interface * {
82 | @retrofit2.http.* ;
83 | }
84 |
85 | # Ignore annotation used for build tooling.
86 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
87 |
88 | # Ignore JSR 305 annotations for embedding nullability information.
89 | -dontwarn javax.annotation.**
90 |
91 | # Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
92 | -dontwarn kotlin.Unit
93 |
94 | # Top-level functions that can only be used by Kotlin.
95 | -dontwarn retrofit2.KotlinExtensions
96 | -dontwarn retrofit2.KotlinExtensions$*
97 |
98 | # With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
99 | # and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
100 | -if interface * { @retrofit2.http.* ; }
101 | -keep,allowobfuscation interface <1>
102 |
103 | # Keep inherited services.
104 | -if interface * { @retrofit2.http.* ; }
105 | -keep,allowobfuscation interface * extends <1>
106 |
107 | # With R8 full mode generic signatures are stripped for classes that are not
108 | # kept. Suspend functions are wrapped in continuations where the type argument
109 | # is used.
110 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
111 |
112 | # R8 full mode strips generic signatures from return types if not kept.
113 | -if interface * { @retrofit2.http.* public *** *(...); }
114 | -keep,allowoptimization,allowshrinking,allowobfuscation class <3>
--------------------------------------------------------------------------------
/app/src/androidTest/java/v/blade/SpotifyConnectionTest.java:
--------------------------------------------------------------------------------
1 | package v.blade;
2 |
3 | import static androidx.test.espresso.Espresso.closeSoftKeyboard;
4 | import static androidx.test.espresso.Espresso.onView;
5 | import static androidx.test.espresso.action.ViewActions.click;
6 | import static androidx.test.espresso.action.ViewActions.typeText;
7 | import static androidx.test.espresso.matcher.ViewMatchers.withId;
8 | import static androidx.test.espresso.matcher.ViewMatchers.withResourceName;
9 | import static androidx.test.espresso.matcher.ViewMatchers.withText;
10 | import static androidx.test.espresso.web.sugar.Web.onWebView;
11 | import static androidx.test.espresso.web.webdriver.DriverAtoms.findElement;
12 | import static androidx.test.espresso.web.webdriver.DriverAtoms.webClick;
13 | import static androidx.test.espresso.web.webdriver.DriverAtoms.webKeys;
14 |
15 | import androidx.test.espresso.contrib.RecyclerViewActions;
16 | import androidx.test.espresso.web.webdriver.Locator;
17 | import androidx.test.ext.junit.rules.ActivityScenarioRule;
18 | import androidx.test.ext.junit.runners.AndroidJUnit4;
19 | import androidx.test.filters.LargeTest;
20 | import androidx.test.platform.app.InstrumentationRegistry;
21 |
22 | import org.junit.Before;
23 | import org.junit.Rule;
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 |
27 | import v.blade.sources.Source;
28 | import v.blade.sources.spotify.Spotify;
29 | import v.blade.ui.SettingsActivity;
30 |
31 | @RunWith(AndroidJUnit4.class)
32 | @LargeTest
33 | public class SpotifyConnectionTest
34 | {
35 | /*
36 | * This is an Espresso test, testing that connecting to a Spotify account works as intended
37 | *
38 | * It is also useful to automate screenshots, as once Spotify connected it can populate the
39 | * library and thus we generate nice screenshots :))
40 | */
41 |
42 | @Rule
43 | public ActivityScenarioRule settingsActivityRule = new ActivityScenarioRule<>(SettingsActivity.class);
44 |
45 | @Before
46 | public void setup()
47 | {
48 |
49 | }
50 |
51 | private int getSpotifyAmount()
52 | {
53 | int amount = 0;
54 | for(Source source : Source.SOURCES)
55 | if(source instanceof Spotify)
56 | amount++;
57 | return amount;
58 | }
59 |
60 | @Test
61 | public void addSpotifyConnection() throws InterruptedException
62 | {
63 | //Check Spotify amount
64 | int beforeSpotifyAmount = getSpotifyAmount();
65 |
66 | //Go to source
67 | onView(withText(R.string.sources)).perform(click());
68 | //Click on button
69 | onView(withId(R.id.settings_source_add)).perform(click());
70 | //Click on 'Spotify'
71 | onView(withText(R.string.spotify)).perform(click());
72 |
73 | //Check if Spotify source was added
74 | int afterSpotifyAmount = getSpotifyAmount();
75 | assert afterSpotifyAmount == beforeSpotifyAmount + 1;
76 |
77 | //Open the added Spotify settings fragment
78 | int newSpotifyIndex = Source.SOURCES.size() - 1;
79 | onView(withId(R.id.settings_sources_listview)).perform(RecyclerViewActions
80 | .actionOnItemAtPosition(newSpotifyIndex, click()));
81 |
82 | //Fill login informations
83 | String spotifyUser = InstrumentationRegistry.getArguments().getString("spotify_user");
84 | String spotifyPass = InstrumentationRegistry.getArguments().getString("spotify_password");
85 | onView(withId(R.id.settings_spotify_user)).perform(typeText(spotifyUser));
86 | closeSoftKeyboard();
87 | onView(withId(R.id.settings_spotify_password)).perform(typeText(spotifyPass));
88 | closeSoftKeyboard();
89 |
90 | //Start connection procedure
91 | onView(withId(R.id.settings_spotify_sign_in)).perform(click());
92 |
93 | /* In theory, there are now multiple cases :
94 | * - The user does not have Spotify application installed
95 | * - It's first launch : enter credentials (again) and accept
96 | * - Authcode accepted before : login is done after a short amount of time
97 | * - The user does have Spotify app installed :
98 | * - It's first launch : accept
99 | * - Authcode accepted before : login is done after a (not so short) amount of time
100 | *
101 | * We will handle only 'no spotify app' case for now
102 | */
103 |
104 | //Wait for window showup
105 | boolean windowOn = false;
106 | while(!windowOn)
107 | {
108 | try
109 | {
110 | onWebView(withResourceName("com_spotify_sdk_login_webview"))
111 | .withElement(findElement(Locator.ID, "login-username"))
112 | .perform(webKeys(spotifyUser));
113 | closeSoftKeyboard();
114 | windowOn = true;
115 | }
116 | catch(RuntimeException e)
117 | {
118 | Thread.sleep(1000);
119 | }
120 | }
121 |
122 | //Fill webview login infos and click on connect
123 | onWebView(withResourceName("com_spotify_sdk_login_webview"))
124 | .withElement(findElement(Locator.ID, "login-password"))
125 | .perform(webKeys(spotifyPass));
126 | closeSoftKeyboard();
127 |
128 | onWebView(withResourceName("com_spotify_sdk_login_webview"))
129 | .withElement(findElement(Locator.ID, "login-button"))
130 | .perform(webClick());
131 |
132 | //Now there are 2 cases : either login is done, or webview is still here
133 | // and we have to click accept
134 | try
135 | {
136 | onWebView(withResourceName("com_spotify_sdk_login_webview"))
137 | .withElement(findElement(Locator.ID, "auth-accept"))
138 | .perform(webClick());
139 | }
140 | catch(RuntimeException ignored)
141 | {
142 | }
143 |
144 | Thread.sleep(1000);
145 |
146 | //Done : check that it went well
147 | assert Source.SOURCES.get(newSpotifyIndex).getStatus() == Source.SourceStatus.STATUS_READY;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
8 |
9 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
35 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/BladeApplication.java:
--------------------------------------------------------------------------------
1 | /* Copyright 2022 Valentin HAUDIQUET
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package v.blade;
16 |
17 | import android.annotation.SuppressLint;
18 | import android.app.Activity;
19 | import android.app.Application;
20 | import android.content.ComponentName;
21 | import android.content.Context;
22 | import android.content.Intent;
23 | import android.content.ServiceConnection;
24 | import android.os.Bundle;
25 | import android.os.IBinder;
26 |
27 | import androidx.annotation.NonNull;
28 | import androidx.annotation.Nullable;
29 | import androidx.appcompat.app.AppCompatDelegate;
30 | import androidx.preference.PreferenceManager;
31 |
32 | import java.util.concurrent.ExecutorService;
33 | import java.util.concurrent.LinkedBlockingQueue;
34 | import java.util.concurrent.ThreadPoolExecutor;
35 | import java.util.concurrent.TimeUnit;
36 |
37 | import v.blade.library.Library;
38 | import v.blade.player.MediaBrowserService;
39 | import v.blade.sources.Source;
40 |
41 | public class BladeApplication extends Application
42 | {
43 | public static abstract class Callback
44 | {
45 | public abstract void run(T arg0);
46 | }
47 |
48 | private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4, 4, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
49 | private static final ExecutorService executorService = threadPoolExecutor;
50 | public static Context appContext;
51 | public static boolean shouldDisplayFirstLaunchDialog = false;
52 |
53 | @SuppressLint("StaticFieldLeak")
54 | public static Activity currentActivity = null;
55 |
56 | @Override
57 | protected void attachBaseContext(Context base)
58 | {
59 | super.attachBaseContext(base);
60 |
61 | this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks()
62 | {
63 | @Override
64 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle)
65 | {
66 | }
67 |
68 | @Override
69 | public void onActivityStarted(@NonNull Activity activity)
70 | {
71 | currentActivity = activity;
72 | }
73 |
74 | @Override
75 | public void onActivityResumed(@NonNull Activity activity)
76 | {
77 | currentActivity = activity;
78 | }
79 |
80 | @Override
81 | public void onActivityPaused(@NonNull Activity activity)
82 | {
83 | if(currentActivity == activity)
84 | currentActivity = null;
85 |
86 | // NOTE: This happens even when switching between Main/Play activities
87 | // TODO: Maybe find a better place to save playlist ? (this will cost disk usage...)
88 | // (it's not as bad as it seems, we have kernel cache...)
89 | System.out.println("BLADE: onActivityPaused....");
90 | if(MediaBrowserService.getInstance() != null)
91 | MediaBrowserService.getInstance().savePlaylist();
92 | }
93 |
94 | @Override
95 | public void onActivityStopped(@NonNull Activity activity)
96 | {
97 | if(currentActivity == activity)
98 | currentActivity = null;
99 | }
100 |
101 | @Override
102 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle)
103 | {
104 | }
105 |
106 | @Override
107 | public void onActivityDestroyed(@NonNull Activity activity)
108 | {
109 | if(currentActivity == activity)
110 | currentActivity = null;
111 | }
112 | });
113 |
114 | //Restore theme from preferences
115 | String dark_theme = PreferenceManager.getDefaultSharedPreferences(base).getString("dark_theme", "system_default");
116 | switch(dark_theme)
117 | {
118 | case "system_default":
119 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
120 | break;
121 | case "dark_theme":
122 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
123 | break;
124 | case "light_theme":
125 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
126 | break;
127 | }
128 |
129 | //Provide static access to application context (eg. for 'Local' source, needing ContentProvider)
130 | appContext = base;
131 |
132 | //Load sources
133 | executorService.execute(() ->
134 | {
135 | Source.loadSourcesFromSave();
136 | Source.initSources();
137 |
138 | // Bind MediaBrowserService to application
139 | Intent serviceIntent = new Intent(this, MediaBrowserService.class);
140 | bindService(serviceIntent, new ServiceConnection()
141 | {
142 | @Override
143 | public void onServiceConnected(ComponentName name, IBinder service)
144 | {
145 | }
146 |
147 | @Override
148 | public void onServiceDisconnected(ComponentName name)
149 | {
150 | }
151 | }, 0);
152 |
153 | Library.loadFromCache();
154 |
155 | if(Source.SOURCES.size() == 0)
156 | shouldDisplayFirstLaunchDialog = true;
157 | });
158 | }
159 |
160 | public static ExecutorService obtainExecutorService()
161 | {
162 | return executorService;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/library/Album.java:
--------------------------------------------------------------------------------
1 | package v.blade.library;
2 |
3 | import com.squareup.picasso.Picasso;
4 | import com.squareup.picasso.RequestCreator;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public class Album extends LibraryObject
10 | {
11 | Artist[] artists;
12 | List songList;
13 | String imageBigStr;
14 | RequestCreator imageBig;
15 | int imageLevel;
16 |
17 | public Album(String name, Artist[] artists, String imageMiniature, String imageBig, int imageLevel)
18 | {
19 | this.name = name;
20 | this.songList = new ArrayList<>();
21 | this.artists = artists;
22 | setImage(imageMiniature, imageBig, imageLevel);
23 | }
24 |
25 | protected void addSong(Song s)
26 | {
27 | this.songList.add(s);
28 | }
29 |
30 | public Artist[] getArtists()
31 | {
32 | return artists;
33 | }
34 |
35 | public String getArtistsString()
36 | {
37 | StringBuilder sb = new StringBuilder();
38 | for(int i = 0; i < artists.length; i++)
39 | {
40 | sb = sb.append(artists[i].name);
41 | if(i != artists.length - 1) sb = sb.append(", ");
42 | }
43 | return sb.toString();
44 | }
45 |
46 | public List getSongs()
47 | {
48 | return songList;
49 | }
50 |
51 | public String getImageBigStr()
52 | {
53 | return imageBigStr;
54 | }
55 |
56 | public void setImage(String imageMiniature, String imageBig, int imageLevel)
57 | {
58 | if(this.imageLevel > imageLevel) return;
59 |
60 | this.imageStr = imageMiniature;
61 | this.imageBigStr = imageBig;
62 | this.imageLevel = imageLevel;
63 | this.imageBig = (imageBig == null || imageBig.equals("")) ? null : Picasso.get().load(imageBig);
64 | this.imageRequest = (imageMiniature == null || imageMiniature.equals("")) ? null : Picasso.get().load(imageMiniature);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/library/Artist.java:
--------------------------------------------------------------------------------
1 | package v.blade.library;
2 |
3 | import com.squareup.picasso.Picasso;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class Artist extends LibraryObject
9 | {
10 | List albums;
11 | int track_count;
12 |
13 | public Artist(String name, String image)
14 | {
15 | this.albums = new ArrayList<>();
16 | this.name = name;
17 | this.imageRequest = (image == null || image.equals("")) ? null : Picasso.get().load(image);
18 | this.imageStr = image;
19 | this.track_count = 0;
20 | }
21 |
22 | protected void addAlbum(Album album)
23 | {
24 | this.albums.add(album);
25 | }
26 |
27 | public int getTrackCount()
28 | {
29 | return track_count;
30 | }
31 |
32 | public List getAlbums()
33 | {
34 | return albums;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/library/LibraryObject.java:
--------------------------------------------------------------------------------
1 | package v.blade.library;
2 |
3 | import com.squareup.picasso.RequestCreator;
4 |
5 | import java.util.ArrayList;
6 |
7 | import v.blade.sources.SourceInformation;
8 |
9 | public abstract class LibraryObject
10 | {
11 | protected String name;
12 | protected ArrayList sources;
13 | protected RequestCreator imageRequest = null;
14 | protected String imageStr = null;
15 |
16 | public String getName()
17 | {
18 | return name;
19 | }
20 |
21 | public RequestCreator getImageRequest()
22 | {
23 | return imageRequest;
24 | }
25 |
26 | public void setImageRequest(RequestCreator request)
27 | {
28 | this.imageRequest = request;
29 | }
30 |
31 | public String getImageStr()
32 | {
33 | return imageStr;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/library/Playlist.java:
--------------------------------------------------------------------------------
1 | package v.blade.library;
2 |
3 | import com.squareup.picasso.Picasso;
4 |
5 | import java.util.List;
6 |
7 | import v.blade.sources.SourceInformation;
8 |
9 | public class Playlist extends LibraryObject
10 | {
11 | final List songs;
12 | private final SourceInformation sourceInformation;
13 | private final String playlistSubtitle;
14 |
15 | public Playlist(String name, List songList, String image, String subtitle, SourceInformation sourceInformation)
16 | {
17 | this.name = name;
18 | this.imageStr = image;
19 | this.imageRequest = (image == null || image.equals("")) ? null : Picasso.get().load(image);
20 | this.songs = songList;
21 | this.sourceInformation = sourceInformation;
22 | this.playlistSubtitle = subtitle;
23 | }
24 |
25 | public SourceInformation getSource()
26 | {
27 | return sourceInformation;
28 | }
29 |
30 | public List getSongs()
31 | {
32 | return songs;
33 | }
34 |
35 | public String getSubtitle()
36 | {
37 | return playlistSubtitle;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/library/Separator.java:
--------------------------------------------------------------------------------
1 | package v.blade.library;
2 |
3 | public class Separator extends LibraryObject
4 | {
5 | public Separator(String name)
6 | {
7 | this.name = name;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/library/Song.java:
--------------------------------------------------------------------------------
1 | package v.blade.library;
2 |
3 | import com.squareup.picasso.RequestCreator;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import v.blade.sources.Source;
9 | import v.blade.sources.SourceInformation;
10 |
11 | public class Song extends LibraryObject
12 | {
13 | List sources;
14 | Artist[] artists;
15 | Album album;
16 | int track_number;
17 |
18 | protected Song(String name, Album album, Artist[] artists, int track_number)
19 | {
20 | this.name = name;
21 | this.artists = artists;
22 | this.album = album;
23 | this.track_number = track_number;
24 | this.sources = new ArrayList<>();
25 | }
26 |
27 | protected void addSource(Source source, Object id, boolean handled)
28 | {
29 | if(source == null || id == null) return;
30 |
31 | //check if song contains same source
32 | for(SourceInformation si : sources) if(si.source == source) return;
33 |
34 | sources.add(new SourceInformation(source, id, handled));
35 | }
36 |
37 | public Artist[] getArtists()
38 | {
39 | return artists;
40 | }
41 |
42 | public String getArtistsString()
43 | {
44 | StringBuilder sb = new StringBuilder();
45 | for(int i = 0; i < artists.length; i++)
46 | {
47 | sb = sb.append(artists[i].name);
48 | if(i != artists.length - 1) sb = sb.append(", ");
49 | }
50 | return sb.toString();
51 | }
52 |
53 | public SourceInformation getBestSource()
54 | {
55 | if(sources.size() == 0) return null;
56 |
57 | SourceInformation best = null;
58 | int min = Source.SOURCES.size();
59 | for(int i = 0; i < sources.size(); i++)
60 | {
61 | if(sources.get(i).source.getIndex() < min
62 | && sources.get(i).source.getStatus() == Source.SourceStatus.STATUS_READY)
63 | {
64 | best = sources.get(i);
65 | min = best.source.getIndex();
66 | }
67 | }
68 | return best;
69 | }
70 |
71 | public int getTrackNumber()
72 | {
73 | return track_number;
74 | }
75 |
76 | public Album getAlbum()
77 | {
78 | return album;
79 | }
80 |
81 | public List getSources()
82 | {
83 | return sources;
84 | }
85 |
86 | @Override
87 | public RequestCreator getImageRequest()
88 | {
89 | return album.imageRequest;
90 | }
91 |
92 | public RequestCreator getBigImageRequest()
93 | {
94 | return album.imageBig;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/sources/SourceInformation.java:
--------------------------------------------------------------------------------
1 | package v.blade.sources;
2 |
3 | public class SourceInformation
4 | {
5 | public Source source;
6 | public Object id;
7 | //Whether or not this is a 'handle' for the song, i.e. not in source library
8 | public boolean handled;
9 |
10 | public SourceInformation(Source source, Object id, boolean handled)
11 | {
12 | this.source = source;
13 | this.id = id;
14 | this.handled = handled;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/sources/deezer/DeezerPlayer.java:
--------------------------------------------------------------------------------
1 | package v.blade.sources.deezer;
2 |
3 | import v.blade.library.Song;
4 | import v.blade.sources.Source;
5 |
6 | public class DeezerPlayer extends Source.Player
7 | {
8 | @Override
9 | public void init()
10 | {
11 |
12 | }
13 |
14 | @Override
15 | public void play()
16 | {
17 |
18 | }
19 |
20 | @Override
21 | public void pause()
22 | {
23 |
24 | }
25 |
26 | @Override
27 | public void playSong(Song song)
28 | {
29 |
30 | }
31 |
32 | @Override
33 | public void seekTo(long millis)
34 | {
35 |
36 | }
37 |
38 | @Override
39 | public long getCurrentPosition()
40 | {
41 | return 0;
42 | }
43 |
44 | @Override
45 | public long getDuration()
46 | {
47 | return 0;
48 | }
49 |
50 | @Override
51 | public boolean isPaused()
52 | {
53 | return false;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/sources/deezer/DeezerSettingsFragment.java:
--------------------------------------------------------------------------------
1 | package v.blade.sources.deezer;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import v.blade.BladeApplication;
12 | import v.blade.R;
13 | import v.blade.databinding.SettingsFragmentDeezerBinding;
14 | import v.blade.sources.Source;
15 |
16 | public class DeezerSettingsFragment extends Fragment
17 | {
18 | private final Deezer deezer;
19 | private SettingsFragmentDeezerBinding binding;
20 |
21 | public DeezerSettingsFragment(Deezer deezer)
22 | {
23 | this.deezer = deezer;
24 | }
25 |
26 | public void refreshStatus()
27 | {
28 | Source.SourceStatus status = deezer.getStatus();
29 |
30 | // Set status label
31 | switch(status)
32 | {
33 | case STATUS_DOWN:
34 | binding.settingsDeezerStatus.setText(R.string.source_down_desc);
35 | break;
36 | case STATUS_NEED_INIT:
37 | binding.settingsDeezerStatus.setText(R.string.source_need_init_desc);
38 | break;
39 | case STATUS_CONNECTING:
40 | binding.settingsDeezerStatus.setText(R.string.source_connecting_desc);
41 | break;
42 | case STATUS_READY:
43 | binding.settingsDeezerStatus.setText(R.string.source_ready_desc);
44 | break;
45 | }
46 |
47 | // Set account text
48 | if(status == Source.SourceStatus.STATUS_DOWN)
49 | {
50 | binding.settingsDeezerAccount.setText(R.string.disconnected);
51 | binding.settingsDeezerAccount.setTextColor(getResources().getColor(R.color.errorRed));
52 |
53 | // Hide audio quality and force init buttons
54 | binding.settingsDeezerInit.setVisibility(View.GONE);
55 | }
56 | else
57 | {
58 | binding.settingsDeezerAccount.setText(deezer.account_name);
59 | binding.settingsDeezerAccount.setTextColor(getResources().getColor(R.color.okGreen));
60 |
61 | // Hide login buttons (we are already connected)
62 | binding.settingsDeezerUser.setVisibility(View.GONE);
63 | binding.settingsDeezerPassword.setVisibility(View.GONE);
64 | binding.settingsDeezerSignIn.setVisibility(View.GONE);
65 | }
66 | }
67 |
68 | @Override
69 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
70 | {
71 | binding = SettingsFragmentDeezerBinding.inflate(inflater, container, false);
72 |
73 | refreshStatus();
74 |
75 | // Set 'sign in' button action
76 | binding.settingsDeezerSignIn.setOnClickListener(v ->
77 | {
78 | // Set deezer parameters
79 | deezer.account_login = binding.settingsDeezerUser.getText().toString();
80 | deezer.account_password = binding.settingsDeezerPassword.getText().toString();
81 |
82 | deezer.setStatus(Source.SourceStatus.STATUS_NEED_INIT);
83 | deezer.initSource();
84 | });
85 |
86 | // Set 'force init' button action
87 | binding.settingsDeezerInit.setOnClickListener(v ->
88 | {
89 | //tODO CHANGE
90 | BladeApplication.obtainExecutorService().execute(deezer::synchronizeLibrary);
91 | //deezer.setStatus(Source.SourceStatus.STATUS_NEED_INIT);
92 | //deezer.initSource();
93 | });
94 |
95 | return binding.getRoot();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/sources/local/LocalPlayer.java:
--------------------------------------------------------------------------------
1 | package v.blade.sources.local;
2 |
3 | import android.content.ContentUris;
4 | import android.media.MediaPlayer;
5 | import android.net.Uri;
6 | import android.provider.MediaStore;
7 |
8 | import androidx.core.content.ContextCompat;
9 |
10 | import java.io.IOException;
11 |
12 | import v.blade.BladeApplication;
13 | import v.blade.library.Song;
14 | import v.blade.player.MediaBrowserService;
15 | import v.blade.sources.Source;
16 | import v.blade.sources.SourceInformation;
17 |
18 | public class LocalPlayer extends Source.Player
19 | {
20 | private final Local local;
21 |
22 | private final MediaPlayer mediaPlayer;
23 |
24 | protected LocalPlayer(Local local)
25 | {
26 | this.local = local;
27 | mediaPlayer = new MediaPlayer();
28 | init();
29 | }
30 |
31 | @Override
32 | public void init()
33 | {
34 | mediaPlayer.setOnCompletionListener(mp ->
35 | ContextCompat.getMainExecutor(MediaBrowserService.getInstance())
36 | .execute(MediaBrowserService.getInstance()::notifyPlaybackEnd));
37 | }
38 |
39 | @Override
40 | public void play()
41 | {
42 | mediaPlayer.start();
43 | }
44 |
45 | @Override
46 | public void pause()
47 | {
48 | mediaPlayer.pause();
49 | }
50 |
51 | @Override
52 | public void playSong(Song song)
53 | {
54 | //Obtain id
55 | SourceInformation current = null;
56 | for(SourceInformation si : song.getSources())
57 | {
58 | if(si.source == local)
59 | {
60 | current = si;
61 | break;
62 | }
63 | }
64 | if(current == null) return;
65 |
66 | Uri songUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ((Number) current.id).longValue());
67 |
68 | try
69 | {
70 | mediaPlayer.reset();
71 | mediaPlayer.setDataSource(BladeApplication.appContext, songUri);
72 | mediaPlayer.prepare();
73 | play();
74 | }
75 | catch(IOException | RuntimeException e)
76 | {
77 | e.printStackTrace();
78 | }
79 | }
80 |
81 | @Override
82 | public void seekTo(long millis)
83 | {
84 | mediaPlayer.seekTo((int) millis);
85 | }
86 |
87 | @Override
88 | public long getCurrentPosition()
89 | {
90 | return mediaPlayer.getCurrentPosition();
91 | }
92 |
93 | @Override
94 | public long getDuration()
95 | {
96 | try
97 | {
98 | return mediaPlayer.getDuration();
99 | }
100 | catch(IllegalStateException e)
101 | {
102 | return 0;
103 | }
104 | }
105 |
106 | @Override
107 | public boolean isPaused()
108 | {
109 | return !mediaPlayer.isPlaying();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/ui/ExploreFragment.java:
--------------------------------------------------------------------------------
1 | package v.blade.ui;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 | import android.widget.Toast;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.fragment.app.Fragment;
13 | import androidx.recyclerview.widget.LinearLayoutManager;
14 | import androidx.recyclerview.widget.RecyclerView;
15 |
16 | import java.util.Stack;
17 |
18 | import v.blade.R;
19 | import v.blade.databinding.FragmentExploreBinding;
20 | import v.blade.sources.Source;
21 |
22 | public class ExploreFragment extends Fragment
23 | {
24 | private static class BackInformation
25 | {
26 | final RecyclerView.Adapter> adapter;
27 | final String title;
28 |
29 | BackInformation(String title, RecyclerView.Adapter> adapter)
30 | {
31 | this.adapter = adapter;
32 | this.title = title;
33 | }
34 | }
35 |
36 | public FragmentExploreBinding binding;
37 | private Stack backStack;
38 | public Source current;
39 |
40 | private class SourceAdapter extends RecyclerView.Adapter
41 | {
42 | class ViewHolder extends RecyclerView.ViewHolder
43 | {
44 | ImageView elementImage;
45 | TextView elementTitle;
46 |
47 | public ViewHolder(@NonNull View itemView)
48 | {
49 | super(itemView);
50 |
51 | elementImage = itemView.findViewById(R.id.item_element_image);
52 | elementTitle = itemView.findViewById(R.id.item_element_title);
53 | }
54 | }
55 |
56 | @NonNull
57 | @Override
58 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
59 | {
60 | View view = LayoutInflater.from(parent.getContext())
61 | .inflate(R.layout.item_layout, parent, false);
62 | ViewHolder viewHolder = new ViewHolder(view);
63 |
64 | view.setOnClickListener(v ->
65 | {
66 | int pos = binding.exploreSourcesListview.getChildAdapterPosition(v);
67 | Source current = Source.SOURCES.get(pos);
68 |
69 | ExploreFragment.this.current = current;
70 | current.explore(ExploreFragment.this);
71 | });
72 |
73 | return viewHolder;
74 | }
75 |
76 | @Override
77 | public void onBindViewHolder(@NonNull ViewHolder holder, int position)
78 | {
79 | Source current = Source.SOURCES.get(position);
80 | holder.elementTitle.setText(current.getName());
81 | holder.elementImage.setImageResource(current.getImageResource());
82 | }
83 |
84 | @Override
85 | public int getItemCount()
86 | {
87 | return Source.SOURCES.size();
88 | }
89 | }
90 |
91 | public String getTitle()
92 | {
93 | return ((MainActivity) requireActivity()).binding == null ? "" : ((MainActivity) requireActivity()).binding.appBarMain.toolbar.getTitle().toString();
94 | }
95 |
96 | @Override
97 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
98 | Bundle savedInstanceState)
99 | {
100 | binding = FragmentExploreBinding.inflate(inflater, container, false);
101 |
102 | binding.exploreSourcesListview.setLayoutManager(new LinearLayoutManager(requireContext()));
103 | binding.exploreSourcesListview.setAdapter(new SourceAdapter());
104 |
105 | current = null;
106 | backStack = new Stack<>();
107 |
108 | return binding.getRoot();
109 | }
110 |
111 | boolean lastSearched = false;
112 |
113 | protected void onSearch(String query)
114 | {
115 | if(current == null)
116 | {
117 | Toast.makeText(requireContext(), getString(R.string.cant_search_here), Toast.LENGTH_SHORT).show();
118 | return;
119 | }
120 | else if(lastSearched)
121 | {
122 | //TODO : this temp fixes '2 intent receiving' ; find a better way
123 | lastSearched = false;
124 | return;
125 | }
126 |
127 | current.exploreSearch(query, this);
128 | lastSearched = true;
129 | }
130 |
131 | public void updateContent(RecyclerView.Adapter> adapter, String title, boolean shouldSaveBackInformation)
132 | {
133 | if(shouldSaveBackInformation)
134 | backStack.push(new BackInformation(getTitle(), binding.exploreSourcesListview.getAdapter()));
135 |
136 | binding.exploreSourcesListview.setAdapter(adapter);
137 | if(((MainActivity) requireActivity()).binding != null)
138 | ((MainActivity) requireActivity()).binding.appBarMain.toolbar.setTitle(title);
139 | }
140 |
141 | private void updateContent(BackInformation backInformation)
142 | {
143 | updateContent(backInformation.adapter, backInformation.title, false);
144 | }
145 |
146 | public void onBackPressed()
147 | {
148 | if(backStack.empty())
149 | {
150 | requireActivity().finish();
151 | return;
152 | }
153 |
154 | updateContent(backStack.pop());
155 |
156 | //if we went back to root, current is null
157 | if(backStack.empty()) current = null;
158 | }
159 | }
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/ui/GeniusService.java:
--------------------------------------------------------------------------------
1 | package v.blade.ui;
2 |
3 | import retrofit2.Call;
4 | import retrofit2.http.GET;
5 | import retrofit2.http.Header;
6 | import retrofit2.http.Query;
7 |
8 | public interface GeniusService
9 | {
10 | class SearchResultObject
11 | {
12 | int annotation_count;
13 | String api_path;
14 | String artist_names;
15 | String full_title;
16 | long id;
17 | String lyrics_state;
18 | String title;
19 | String title_with_featured;
20 | String path;
21 | }
22 |
23 | class SearchHitObject
24 | {
25 | String index;
26 | String type;
27 | SearchResultObject result;
28 | }
29 |
30 | class SearchResponse
31 | {
32 | SearchHitObject[] hits;
33 | }
34 |
35 | class MetaObject
36 | {
37 | int status;
38 | }
39 |
40 | class SearchApiResponse
41 | {
42 | MetaObject meta;
43 | SearchResponse response;
44 | }
45 |
46 |
47 | @GET("search")
48 | Call search(@Header("Authorization") String authorization, @Query("q") String query);
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/ui/LyricsActivity.java:
--------------------------------------------------------------------------------
1 | package v.blade.ui;
2 |
3 | import android.os.Bundle;
4 | import android.webkit.WebView;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 |
8 | import java.io.IOException;
9 |
10 | import retrofit2.Call;
11 | import retrofit2.Response;
12 | import retrofit2.Retrofit;
13 | import retrofit2.converter.gson.GsonConverterFactory;
14 | import v.blade.BladeApplication;
15 | import v.blade.R;
16 | import v.blade.library.Song;
17 | import v.blade.player.MediaBrowserService;
18 |
19 | public class LyricsActivity extends AppCompatActivity
20 | {
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState)
23 | {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_lyrics);
26 |
27 | WebView webView = findViewById(R.id.lyrics_webview);
28 |
29 | Song song = MediaBrowserService.getInstance().getPlaylist().get(MediaBrowserService.getInstance().getIndex());
30 | BladeApplication.obtainExecutorService().execute(() ->
31 | {
32 | Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.genius.com")
33 | .addConverterFactory(GsonConverterFactory.create()).build();
34 | GeniusService service = retrofit.create(GeniusService.class);
35 |
36 | Call search =
37 | service.search("Bearer wTGF45NZElaOrhC1LIEhdBq9ISwX7SgNLBkp_74fjUo-uwUJNrENnCJ2Uj4tJeVo",
38 | song.getName() + " " + song.getArtists()[0].getName());
39 |
40 |
41 | try
42 | {
43 | Response response = search.execute();
44 |
45 | if(response.code() != 200)
46 | {
47 | return;
48 | }
49 |
50 | GeniusService.SearchApiResponse r = response.body();
51 | if(r == null || r.response.hits.length == 0)
52 | {
53 | return;
54 | }
55 |
56 | runOnUiThread(() ->
57 | webView.loadUrl("https://genius.com" + r.response.hits[0].result.path));
58 | }
59 | catch(IOException e)
60 | {
61 | e.printStackTrace();
62 | }
63 | });
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/v/blade/ui/TouchHelperCallback.java:
--------------------------------------------------------------------------------
1 | package v.blade.ui;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.recyclerview.widget.ItemTouchHelper;
5 | import androidx.recyclerview.widget.RecyclerView;
6 |
7 | import java.util.Collections;
8 | import java.util.List;
9 |
10 | @SuppressWarnings("rawtypes")
11 | public class TouchHelperCallback extends ItemTouchHelper.Callback
12 | {
13 | private Object[] toOrderObject;
14 | private List toOrderList;
15 |
16 | public TouchHelperCallback(Object[] toOrder)
17 | {
18 | this.toOrderObject = toOrder;
19 | }
20 |
21 | public TouchHelperCallback(List toOrder)
22 | {
23 | this.toOrderList = toOrder;
24 | }
25 |
26 | @Override
27 | public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder)
28 | {
29 | int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
30 | return makeMovementFlags(dragFlags, 0);
31 | }
32 |
33 | @Override
34 | public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target)
35 | {
36 | if(toOrderObject == null && toOrderList == null) return false;
37 | if(recyclerView.getAdapter() == null) return false;
38 |
39 | int from = viewHolder.getAdapterPosition();
40 | int to = target.getAdapterPosition();
41 |
42 | if(toOrderObject != null)
43 | {
44 | Object toMove = toOrderObject[from];
45 | if(to - from >= 0)
46 | System.arraycopy(toOrderObject, from + 1, toOrderObject, from, to - from);
47 | else if(from - to >= 0)
48 | System.arraycopy(toOrderObject, to, toOrderObject, to + 1, from - to);
49 | toOrderObject[to] = toMove;
50 | }
51 | else Collections.swap(toOrderList, from, to);
52 |
53 | recyclerView.getAdapter().notifyItemMoved(from, to);
54 | return true;
55 | }
56 |
57 | @Override
58 | public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction)
59 | {
60 | if(toOrderObject == null && toOrderList == null) return;
61 |
62 | int position = viewHolder.getAdapterPosition();
63 | if(toOrderObject != null)
64 | {
65 | System.arraycopy(toOrderObject, position + 1, toOrderObject, position, toOrderObject.length - position);
66 | toOrderObject[toOrderObject.length - 1] = null;
67 | }
68 | else toOrderList.remove(position);
69 |
70 | //NOTE: Adapter still needs to be notified ; this method needs to be overloaded
71 | }
72 |
73 | @Override
74 | public boolean isLongPressDragEnabled()
75 | {
76 | //NOTE : we disable long press drag; i find that not intuitive, and it is bugged anyway
77 | return false;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v24/ic_pause_notification.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v24/ic_play_notification.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v24/ic_skip_next_notification.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v24/ic_skip_previous_notification.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_album_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-hdpi/ic_album_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_blade_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-hdpi/ic_blade_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_pause_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-hdpi/ic_pause_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_play_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-hdpi/ic_play_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_skip_next_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-hdpi/ic_skip_next_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_skip_previous_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-hdpi/ic_skip_previous_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_album_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-mdpi/ic_album_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_blade_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-mdpi/ic_blade_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_pause_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-mdpi/ic_pause_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_play_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-mdpi/ic_play_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_skip_next_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-mdpi/ic_skip_next_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_skip_previous_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-mdpi/ic_skip_previous_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_album_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xhdpi/ic_album_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_blade_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xhdpi/ic_blade_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_pause_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xhdpi/ic_pause_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_play_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xhdpi/ic_play_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_skip_next_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xhdpi/ic_skip_next_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_skip_previous_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xhdpi/ic_skip_previous_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_album_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxhdpi/ic_album_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_blade_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxhdpi/ic_blade_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_pause_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxhdpi/ic_pause_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_play_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxhdpi/ic_play_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_skip_next_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxhdpi/ic_skip_next_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_skip_previous_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxhdpi/ic_skip_previous_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_album_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxxhdpi/ic_album_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_blade_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/drawable-xxxhdpi/ic_blade_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/fastscroll_thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/fastscroll_track.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_album.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_album_artist_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_artist.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dark_theme.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_deezer.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
12 |
18 |
21 |
24 |
27 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
61 |
64 |
67 |
68 |
69 |
70 |
71 |
72 |
78 |
81 |
84 |
85 |
86 |
87 |
88 |
89 |
95 |
98 |
101 |
102 |
103 |
104 |
105 |
106 |
112 |
115 |
118 |
119 |
120 |
121 |
122 |
123 |
129 |
132 |
135 |
136 |
137 |
138 |
139 |
140 |
146 |
149 |
152 |
153 |
154 |
155 |
156 |
157 |
163 |
166 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_explore.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_hourglass.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_local.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_more_vert.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pause_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_playlist.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_playlist_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_reorder_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_repeat.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_repeat_one.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_24px.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_24px.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_shuffle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_next.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_skip_previous.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_song.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sources_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_spotify.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_spotify_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
21 |
26 |
31 |
36 |
41 |
46 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sync_24px.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/track.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_lyrics.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/app_bar_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
27 |
28 |
34 |
35 |
47 |
48 |
59 |
60 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_create_playlist.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_explore.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_library.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_label_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
26 |
27 |
37 |
38 |
49 |
50 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_simple_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_switch.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_activity.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_fragment_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
19 |
20 |
27 |
28 |
34 |
35 |
42 |
43 |
49 |
50 |
56 |
57 |
63 |
64 |
70 |
71 |
79 |
80 |
88 |
89 |
97 |
98 |
106 |
107 |
115 |
116 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_fragment_deezer.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
18 |
19 |
24 |
25 |
30 |
31 |
38 |
39 |
46 |
47 |
54 |
55 |
63 |
64 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_fragment_local.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
16 |
17 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_fragment_sources.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_fragment_spotify.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
16 |
17 |
22 |
23 |
28 |
29 |
36 |
37 |
44 |
45 |
52 |
53 |
59 |
60 |
68 |
69 |
73 |
74 |
79 |
80 |
81 |
82 |
90 |
91 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/currentplay_more.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/item_more.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
12 |
16 |
20 |
24 |
28 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
15 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/mobile_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
18 |
23 |
28 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Blade Player
3 |
4 | Otwórz panel nawigacji
5 | Zamknij panel nawigacji
6 |
7 | Zsynchronizuj
8 | Ustawienia
9 |
10 | Biblioteka
11 | Artyści
12 | Albumy
13 | Piosenki
14 | Playlisty
15 |
16 | Źródła
17 | Informacje
18 |
19 | Dodaj źródło…
20 |
21 | Spotify
22 | Odtwarzaj muzykę z konta Spotify
23 | Lokalne
24 | Odtwarzaj muzykę z plików na telefonie
25 | Deezer
26 | Odtwarzaj muzykę z konta Deezer
27 |
28 | Zdjęcie elementu
29 | Więcej…
30 |
31 | Niedostępne - Proszę, sprawdź konfigurację
32 | Potrzebna inicjalizacja - Wymuś lub sprawdź konfigurację
33 | Łączenie…
34 | Gotowe
35 |
36 | Zaloguj się
37 | Rozłączono
38 | Problem uwierzytelniania
39 | Nie udało się zainicjować źródła
40 | Wymuś inicjalizację
41 | Usuń
42 | Wersja
43 | Blade Player to Androidowy odtwarzacz muzyki (stworzony przez @vhaudiquet), aby odtwarzać muzykę z wielu źródeł.
44 | Licencja : Apache 2.0
45 | Logo: @zularizal
46 | Źródło: github.com/vhaudiquet/BladePlayer
47 | Użyte biblioteki:
48 | Nazwa użytkownika
49 | Hasło
50 | (Może być konieczne ponowne wprowadzenie danych uwierzytelniających, dla API i odtwarzacza)
51 | Proszę zsynchronizuj bibliotekę, aby zaaplikować zmiany
52 | Graj
53 | Zatrzymaj
54 | Dalej
55 | Cofnij
56 | Odtwarzanie multimediów
57 | Powiadomienia i sterowanie multimediami
58 | Obecnie gra
59 | Odtwórz po tej piosence
60 | Dodaj do aktualnej playlisty
61 | Losowo
62 | Powtórz
63 | Ta piosenka nie ma gotowego źródła. Coś musi być nie tak z konfiguracją…
64 | Szukaj
65 | Dodaj do playlisty…
66 | Zarządzaj bibliotekami
67 | Usuń z playlisty
68 | Dodano %1$s do listy %2$s
69 | Nie można dodać %1$s do listy %2$s
70 | Nowa playlista
71 | Nazwa
72 | Nie można utworzyć playlisty %1$s
73 | Jesteś pewien, że chcesz usunąć playlistę %1$s ?
74 | Tak
75 | Nie
76 | Usunięto playlistę %1$s
77 | Usunięto playlistę
78 | Nie można było usunąć playlisty %1$s
79 | Piosenka %1$s została dodana do biblioteki
80 | Piosenka %1$s nie mogła zostać dodana do biblioteki
81 | Piosenka %1$s została usunięta z biblioteki
82 | Piosenka %1$s nie mogła zostać usunięta z biblioteki
83 | Jesteś pewien, że chcesz usunąć %1$s z playlisty %2$s ?
84 | %1$s usunięto z playlisty %2$s
85 | %1$s nie można było usunąć z playlisty %2$s
86 | Błąd odtwarzacza Spotify, coś musi być nie tak (internet/konfiguracja?)
87 | Udziel uprawnień dostępu do pamięci masowej
88 | Uprawnienia już udzielone
89 | Uprawnienia udzielone!
90 | Współpraca
91 | %1$s odtwarzacz nie jest gotowy
92 | Witamy w Blade Player!
93 | Wygląda na to, że nie skonfigurowałeś żadnych \'źródeł\' muzyki. Proszę, wejdź w ustawienia (ikona w prawym górnym rogu), \'Źródła\', dodaj źródło używając przycisku + i kliknij na nie, aby je skonfigurować. Po zakończeniu, wróć, naciśnij ikonę \'synchronizacji\' obok ustawień i czekaj na synchronizację. Potem delektuj się swoją muzyką!
94 | Czarny motyw
95 | Jasny motyw
96 | Motyw systemowy
97 | Odkryj
98 | Odkryj źródła
99 | Nie ma czego tutaj szukać
100 | Nie udało się szukać %1$s
101 | Nie można przeglądać albumu %1$s
102 | Nie można przeglądać artysty %1$s
103 | Nie można wyświetlić zakładki Odkryj
104 | Nie można przeglądać playlisty %1$s
105 | Zaznaczenie jest za duże
106 | Tekst
107 | Jakość dźwięku
108 | Normalna
109 | Wysoka
110 | Bardzo wysoka
111 | Pokaż tylko twórców albumu
112 | Ukrywaj artystów, którzy nie są twórcami albumów w bibliotece
113 |
114 |
--------------------------------------------------------------------------------
/app/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Blade Player
4 | Gezinti çubuğunu aç
5 | Gezinti çubuğunu kapat
6 | Senkronize et
7 | Ayarlar
8 | Kitaplık
9 | Sanatçılar
10 | Albümler
11 | Şarkılar
12 | Çalma Listeleri
13 | Kaynaklar
14 | Hakkında
15 | Kaynak ekle…
16 | Spotify
17 | Spotify hesabından müzik çal
18 | Yerel
19 | Telefonundaki dosyalardan müzik çal
20 | Deezer
21 | Deezer hesabından müzik çal
22 | Ürün resmi
23 | Daha…
24 | Aşağı - Lütfen yapılandırmayı kontrol edin
25 | Başlatma gerek - Ya zorlayın ya da yapılandırmayı kontrol edin
26 | Bağlanıyor…
27 | Hazır
28 | Giriş yap
29 | Bağlantı kesildi
30 | Doğrulama hatası
31 | Kaynak başlatılamadı
32 | Başlatmaya zorla
33 | Kaldır
34 | Sürüm
35 | Blade Player bir Android müzik oynatıcısıdır (@vhaudiquet tarafından) birden fazla hizmetten şarkı çalmak için yapılmıştır.
36 | Lisans : Apache 2.0
37 | Logo : @zularizal
38 | Kaynaklar : github.com/vhaudiquet/BladePlayer
39 | Kullanılan kütüphaneler :
40 | Kullanıcı adı
41 | Parola
42 | (API ve Oynatıcı için kimlik bilgilerinizi tekrar girmeniz gerekebilir)
43 | Değişikliklerinizi uygulamak için lütfen kitaplığı senkronize edin
44 | Oynat
45 | Durdur
46 | Sonrakine atla
47 |
48 | Öncekine atla
49 | Medya oynatma
50 | Medya bildirimi ve kontroller
51 | Şu anda çalıyor
52 | Geçerli şarkıdan sonra çal
53 | Geçerli çalma listesine ekle
54 | Karıştır
55 | Tekrarla
56 | Bu şarkının hazır bir kaynağı yok. Kaynak yapılandırmasında bir sorun olmalı…
57 | Ara
58 | Çalma listesine ekle…
59 | Kitaplıkları yönet
60 | Çalma listesinden kaldır
61 | %2$s listesine %1$s eklendi
62 | %2$s listesine %1$s eklenemedi
63 | Yeni çalma listesi
64 | İsim
65 | %1$s çalma listesi oluşturulamadı
66 | %1$s çalma listesini silmek istediğinizden emin misiniz?
67 | Evet
68 | Hayır
69 | %1$s çalma listesi kaldırıldı
70 | Çalma listesini sil
71 | %1$s çalma listesi kaldırılamadı
72 | %1$s şarkısı kitaplığa eklendi
73 | %1$s şarkısı kitaplığa eklenemedi
74 |
75 | %1$s şarkısı kitaplıktan kaldırıldı
76 |
77 | %1$s şarkısı kitaplıktan kaldırılamadı
78 | %1$s şarkısını %2$s çalma listesinden kaldırmak istediğinizden emin misiniz?
79 |
80 | %1$s, %2$s çalma listesinden kaldırıldı
81 | %1$s, %2$s çalma listesinden kaldırılamadı
82 | Spotify oynatıcı hatası, bir sorun olmalı (internet/ayarlar ?)
83 | Depolama alanına erişim izni verin
84 | İzin zaten verildi
85 |
86 | İzin verildi !
87 |
88 | Ortak
89 | %1$s oynatıcısı hazır değildi
90 |
91 | Blade Player\'a hoş geldiniz!
92 | Görünüşe göre herhangi bir müzik "kaynağı" yapılandırmamışsınız (Blade\'in müziğinizi alacağı hizmet). Lütfen ayarlara gidin (sağ üstteki simgeyi kullanarak), \'Kaynaklar\', + düğmesini kullanarak bir kaynak ekleyin ve yapılandırmak için üzerine tıklayın. İşiniz bittiğinde geri dönün, ayarların yanındaki \'senkronizasyon\' simgesine basın ve senkronizasyonu bekleyin. Her şey hazır, müziğinizi dinleyin!
93 |
94 | Koyu tema
95 | Açık tema
96 | Sistem varsayılanı
97 | Keşfet
98 | Kaynakları keşfet
99 | Burada aranacak bir şey yok
100 | %1$s aranamadı
101 |
102 | %1$s albümüne göz atılamadı
103 |
104 | %1$s sanatçısına göz atılamadı
105 | Keşfet sekmesi gösterilemedi
106 | %1$s çalma listesine göz atılamadı
107 | Seçim çok büyük
108 | Sözler
109 | Ses kalitesi
110 | Normal
111 | Yüksek
112 | Çok yüksek
113 | Yalnızca albüm sanatçılarını göster
114 | Yalnızca kitaplığınızdaki diğer sanatçıların albümlerinde yer alan sanatçıları gizleyin
115 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Blade 播放器
3 |
4 | 打开导航栏
5 | 关闭导航栏
6 |
7 | 同步
8 | 设置
9 |
10 | 库
11 | 歌手
12 | 专辑
13 | 歌曲
14 | 播放列表
15 |
16 | 来源
17 | 关于
18 |
19 | 添加源…
20 |
21 | Spotify
22 | 播放来自 Spotify 账户的音乐
23 | 本地
24 | 播放手机上存储的音乐
25 |
26 | Deezer
27 | 播放来自 Deezer 账户的音乐
28 | 项目图片
29 | 更多…
30 |
31 | 已离线 - 请检查配置文件
32 | 需初始化 - 强制或检查配置
33 | 连接中…
34 | Ready
35 |
36 | 登入
37 | 已断开
38 | 身份验证错误
39 | 初始化失败
40 | 强制初始化
41 | 删除
42 | 版本
43 | Blade Player 是一个安卓音乐播放器(由 @vhaudiquet 制作),用于播放来自多个服务的歌曲。
44 | 许可证 : Apache 2.0
45 | 图标 : @zularizal
46 | 源码 : github.com/vhaudiquet/BladePlayer
47 | 使用的库 :
48 | 用户名
49 | 密码
50 | (当使用 API 或播放器时,你也许需要重新登录)
51 | 请同步库以应用您的更改
52 | 播放
53 | 暂停
54 | 下一个
55 | 上一个
56 | 媒体播放
57 | 媒体通知与控制
58 | 当前播放
59 | 下一首播放
60 | 加入当前播放列表
61 | Shuffle
62 | 单曲循环
63 | 这首歌没有任何可用来源,难道是来源配置出了点问题?
64 | 搜索
65 | 加入播放列表…
66 | 管理库
67 | 从播放列表移除
68 | 已添加 %1$s 至 %2$s
69 | 无法将 %1$s 添加至 %2$s
70 | 新建播放列表
71 | 名字
72 | 无法创建 %1$s
73 | 你确定要删除 %1$s 吗?
74 | 好的
75 | 不要
76 | 已删除 %1$s
77 | 删除播放列表
78 | 无法删除 %1$s
79 | %1$s 已加入库
80 | %1$s 无法被加入库
81 | %1$s 已从库中移除
82 | %1$s 无法从库中移除
83 | 你确定要将 %1$s 从 %2$s 中删除吗?
84 | %1$s 已从 %2$s 中移除
85 | %1$s 无法从 %2$s 中移除
86 | Spotify 播放出错,难道 互联网 / 设置 出了问题?
87 | 授予存储空间权限
88 | 权限已授予
89 | 拿到权限啦!
90 | Collaborative
91 | %1$s 播放器还没准备好呢
92 | 欢迎使用 Blade 播放器!
93 | 看来你没有配置任何音乐来源(Blade 将播放其中的音乐)。请进入设置(右上角图标),来源,使用 + 按钮添加一个来源,并配置它。完成后回到主页,点击设置旁边的 "同步" 图标,等待片刻,享受你的音乐吧!
94 | 深色模式
95 | 亮色模式
96 | 系统默认
97 | 探索
98 | 探索来源
99 | 这里…好像什么都没有?
100 | 无法搜索 %1$s
101 | 无法查看专辑 %1$s
102 | 无法查看歌手 %1$s
103 | 无法显示探索页
104 | 无法查看播放列表 %1$s
105 | 你选的太多啦
106 | 歌词
107 | 音质
108 | 普通的
109 | 高的
110 | 很高
111 | 仅显示专辑艺术家
112 | 隐藏仅出现在您资料库中其他艺术家专辑中的艺术家
113 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Blade 播放器
3 |
4 | 開啟導航欄
5 | 關閉導航欄
6 |
7 | 同步
8 | 設定
9 |
10 | 庫
11 | 歌手
12 | 專輯
13 | 歌曲
14 | 播放列表
15 |
16 | 來源
17 | 關於
18 |
19 | 新增源…
20 |
21 | Spotify
22 | 播放來自 Spotify 賬戶的音樂
23 | 本地
24 | 播放手機上儲存的音樂
25 |
26 | Deezer
27 | 播放來自 Deezer 賬戶的音樂
28 | 專案圖片
29 | 更多…
30 |
31 | 已離線 - 請檢查配置檔案
32 | 需初始化 - 強制或檢查配置
33 | 連線中…
34 | Ready
35 |
36 | 登入
37 | 已斷開
38 | 身份驗證錯誤
39 | 初始化失敗
40 | 強制初始化
41 | 刪除
42 | 版本
43 | Blade Player 是一個安卓音樂播放器(由 @vhaudiquet 製作),用於播放來自多個服務的歌曲。
44 | 許可證 : Apache 2.0
45 | 圖示 : @zularizal
46 | 原始碼 : github.com/vhaudiquet/BladePlayer
47 | 使用的庫 :
48 | 使用者名稱
49 | 密碼
50 | (當使用 API 或播放器時,你也許需要重新登入)
51 | 請同步庫以應用您的更改
52 | 播放
53 | 暫停
54 | 下一個
55 | 上一個
56 | 媒體播放
57 | 媒體通知與控制
58 | 當前播放
59 | 下一首播放
60 | 加入當前播放列表
61 | Shuffle
62 | 單曲迴圈
63 | 這首歌沒有任何可用來源,難道是來源配置出了點問題?
64 | 搜尋
65 | 加入播放列表…
66 | 管理庫
67 | 從播放列表移除
68 | 已新增 %1$s 至 %2$s
69 | 無法將 %1$s 新增至 %2$s
70 | 新建播放列表
71 | 名字
72 | 無法建立 %1$s
73 | 你確定要刪除 %1$s 嗎?
74 | 好的
75 | 不要
76 | 已刪除 %1$s
77 | 刪除播放列表
78 | 無法刪除 %1$s
79 | %1$s 已加入庫
80 | %1$s 無法被加入庫
81 | %1$s 已從庫中移除
82 | %1$s 無法從庫中移除
83 | 你確定要將 %1$s 從 %2$s 中刪除嗎?
84 | %1$s 已從 %2$s 中移除
85 | %1$s 無法從 %2$s 中移除
86 | Spotify 播放出錯,難道 網際網路 / 設定 出了問題?
87 | 授予儲存空間許可權
88 | 許可權已授予
89 | 拿到許可權啦!
90 | Collaborative
91 | %1$s 播放器還沒準備好呢
92 | 歡迎使用 Blade 播放器!
93 | 看來你沒有配置任何音樂來源(Blade 將播放其中的音樂)。請進入設定(右上角圖示),來源,使用 + 按鈕新增一個來源,並配置它。完成後回到主頁,點選設定旁邊的 "同步" 圖示,等待片刻,享受你的音樂吧!
94 | 深色模式
95 | 亮色模式
96 | 系統預設
97 | 探索
98 | 探索來源
99 | 這裡…好像什麼都沒有?
100 | 無法搜尋 %1$s
101 | 無法檢視專輯 %1$s
102 | 無法檢視歌手 %1$s
103 | 無法顯示探索頁
104 | 無法檢視播放列表 %1$s
105 | 你選的太多啦
106 | 歌詞
107 | 音质
108 | 普通的
109 | 高的
110 | 很高
111 | 仅显示专辑艺术家
112 | 隐藏仅出现在您资料库中其他艺术家专辑中的艺术家
113 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @string/light_theme
5 | @string/dark_theme
6 | @string/system_default
7 |
8 |
9 | light_theme
10 | dark_theme
11 | system_default
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF2C3D4F
4 | #FF616161
5 | #FF212121
6 | #FFbdbdbd
7 | #FFe0e0e0
8 | #FFffffff
9 | #FF000000
10 | #FF424242
11 | #FFf44336
12 | #FF76ff03
13 | #FFb3e5fc
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 12dp
6 | 120dp
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2C3D4F
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/spotify_audio_quality.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @string/quality_normal
5 | @string/quality_high
6 | @string/quality_very_high
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Blade Player
3 |
4 | Open navigation drawer
5 | Close navigation drawer
6 |
7 | Synchronize
8 | Settings
9 |
10 | Library
11 | Artists
12 | Albums
13 | Songs
14 | Playlists
15 |
16 | Sources
17 | About
18 |
19 | Add source…
20 |
21 | Spotify
22 | Play music from a Spotify account
23 | Local
24 | Play music from files on your phone
25 | Deezer
26 | Play music from a Deezer account
27 |
28 | Item image
29 | More…
30 |
31 | Down - Please check configuration
32 | Need initialization - Either force or check config
33 | Connecting…
34 | Ready
35 |
36 | Sign in
37 | Disconnected
38 | Authentication error
39 | Could not init source
40 | Force initialization
41 | Remove
42 | Version
43 | Blade Player is an Android music player (by @vhaudiquet) made to play songs from multiple services.
44 | License : Apache 2.0
45 | Logo : @zularizal
46 | Sources : github.com/vhaudiquet/BladePlayer
47 | Libraries used :
48 | Username
49 | Password
50 | (You might need to enter your credentials again, for API and Player)
51 | Please sync library to apply your changes
52 | Play
53 | Pause
54 | Skip to next
55 | Skip to previous
56 | Media playback
57 | Media notification and controls
58 | Currently playing
59 | Play after current song
60 | Add to current playlist
61 | Shuffle
62 | Repeat
63 | Build type
64 | This song has no ready source. Something must be wrong in sources configuration…
65 | Search
66 | Add to playlist…
67 | Manage libraries
68 | Remove from playlist
69 | Added %1$s to list %2$s
70 | Could not add %1$s to list %2$s
71 | New playlist
72 | Name
73 | OK
74 | Could not create playlist %1$s
75 | Are you sure that you want to delete playlist %1$s ?
76 | Yes
77 | No
78 | Removed playlist %1$s
79 | Delete playlist
80 | Could not remove playlist %1$s
81 | Song %1$s added to library
82 | Song %1$s could not be added to library
83 | Song %1$s removed from library
84 | Song %1$s could not be removed from library
85 | Are you sure that you want to remove %1$s from playlist %2$s ?
86 | %1$s removed from playlist %2$s
87 | %1$s could not be removed from playlist %2$s
88 | Spotify player error, something must be wrong (internet/settings ?)
89 | Grant permission to access storage
90 | Permission already granted
91 | Permission granted !
92 | Collaborative
93 | %1$s player was not ready
94 | Welcome to Blade player !
95 | It seems like you did not configure any \'source\' of music (service from which Blade will get your music). Please go to settings (using the top right icon), \'Sources\', add a source using the + button, and click on it to configure it. When you\'re done, go back, hit the \'sync\' icon next to settings, and wait for synchronization. You\'re all set, listen to your music !
96 | Dark theme
97 | Light theme
98 | System default
99 | Explore
100 | Explore sources
101 | There is nothing to search here
102 | Could not search for %1$s
103 | Could not browse album %1$s
104 | Could not browse artist %1$s
105 | Could not show explore tab
106 | Could not browse playlist %1$s
107 | Selection is too big
108 | Lyrics
109 | Audio quality
110 | Normal
111 | High
112 | Very high
113 | Show album artists only
114 | Hide artists that are only featured in other artists albums in your library
115 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/header_preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
17 |
18 |
26 |
27 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | spotify.com
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | sourceCompatibility = JavaVersion.VERSION_1_8
3 | targetCompatibility = JavaVersion.VERSION_1_8
4 | }// Top-level build file
5 | buildscript {
6 | ext.kotlin_version = '1.9.21'
7 | repositories {
8 | google()
9 | mavenCentral()
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:8.2.0'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | tasks.register('clean', Delete) {
18 | delete rootProject.buildDir
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=false
20 | android.nonTransitiveRClass=false
21 | android.nonFinalResIds=false
22 | org.gradle.configuration-cache=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhaudiquet/BladePlayer/d8762be6b8c2d3737630e3fc26393b9638a3b130/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 27 23:44:45 CET 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | rootProject.name = "Blade Player"
9 | include ':app'
10 |
--------------------------------------------------------------------------------