├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── vpaliy │ │ │ └── soundcloud_api │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_single.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── vpaliy │ └── soundcloud │ └── ExampleUnitTest.java ├── art └── sound_pop_up.png ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── soundcloud-api@ ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── vpaliy │ │ └── soundcloud │ │ ├── Endpoints.java │ │ ├── SoundCloud.java │ │ ├── SoundCloudService.java │ │ ├── auth │ │ ├── AuthService.java │ │ ├── Connect.java │ │ ├── LoginActivity.java │ │ └── SoundCloudAuth.java │ │ ├── model │ │ ├── AppEntity.java │ │ ├── CommentEntity.java │ │ ├── ConnectionEntity.java │ │ ├── MeEntity.java │ │ ├── MiniUserEntity.java │ │ ├── MyActivityEntity.java │ │ ├── Page.java │ │ ├── PlaylistEntity.java │ │ ├── SecretToken.java │ │ ├── Token.java │ │ ├── TrackEntity.java │ │ ├── UserEntity.java │ │ └── WebProfileEntity.java │ │ └── utils │ │ └── Adapter.java └── res │ ├── layout │ └── activity_web.xml │ └── values │ └── strings.xml └── test └── java └── com └── vpaliy └── soundcloud └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2017 Vasyl Paliy 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SoundCloud-API for Android. 2 | [![](https://jitpack.io/v/vpaliyX/SoundCloud-API.svg)](https://jitpack.io/#vpaliyX/SoundCloud-API) 3 | 4 | This project is a wrapper for the [SoundCloud API](https://developers.soundcloud.com/). 5 | 6 | The SoundCloud API exposes SoundCloud resources like *tracks*, *playlists*, *users*, *comments*, etc. 7 | The API gives you the ability to access a **sound's stream URL** and use your own player to play sounds from SoundCloud. 8 | 9 | This repository uses Retrofit2 to create Java interfaces from API endpoints. It returns a `Single` which makes it very easy 10 | to handle asynchronous operations and you can convert an existing data structure into another Single . Please, refer to [Single Utility Operators](http://reactivex.io/documentation/single.html) for more details. 11 | 12 | ## How do I use this wrapper? ## 13 | 14 | ### Step 1 ### 15 | 16 | Add this to your root `build.gradle` file: 17 | 18 | ``` gradle 19 | allprojects { 20 | repositories { 21 | maven { url 'https://jitpack.io' } 22 | } 23 | } 24 | ``` 25 | ### Step 2 ### 26 | 27 | Add the dependency 28 | 29 | ``` gradle 30 | dependencies { 31 | compile 'com.github.vpaliyX:SoundCloud-API:-SNAPSHOT' 32 | } 33 | 34 | ``` 35 | 36 | ### Making Request ### 37 | You need to get your `client_id` and `client_secret` by registering your app [here](https://developers.soundcloud.com/docs/api/guide). After you have obtained that, you can start using the API. 38 | 39 | 40 | Basically, most of the calls will look like this one: 41 | 42 | ```java 43 | final SoundCloud api = new SoundCloud.Builder(context, Config.CLIENT_ID) 44 | .setToken(token) 45 | .setInterceptor(interceptor) 46 | .build(); 47 | final SoundCloudService service = api.getSoundCloudService(); 48 | service.fetchTrack("123456678") //some dummy track id 49 | .subscribeOn(Schedulers.io()) 50 | .observeOn(AndroidSchedulers.mainThread()) 51 | .subscribe(track->{ 52 | //do something with the track 53 | }); 54 | 55 | ``` 56 | 57 | It's pretty simple. If you want to pass query paramaters, use the following structure: 58 | 59 | ```java 60 | service.searchTracks(Track.Filter.start() 61 | .byName("Imagine Dragons") 62 | .byGenres("rock","indie","alternative") 63 | .byTags("popular","rock","imagine","dragons") 64 | .byLicense(Track.License.ALL_RIGHTS_RESERVED) 65 | .byTypes(Track.Type.ORIGINAL, Track.Type.LIVE) 66 | .limit(30) 67 | .offset(10).createOptions()) 68 | .subscribeOn(Schedulers.io()) 69 | .observeOn(AndroidSchedulers.mainThread()) 70 | .subscribe(tracks->{ 71 | //do something 72 | }); 73 | 74 | ``` 75 | The `Filter` class is a nested class inside of a model class like `User`, `Playlist` or `Track`. Most of them offer a different set of methods because not all models can be filtered in the same way. 76 | Whenever you need to filter a request, just use the `Filter` class and its available methods. After you've listed all things you need, just call the `Filter.createOptions()` method. 77 | 78 | 79 | ## Authentication ## 80 | 81 | SoundCloud authentication uses **OAuth 2.0**, a popular open standard used by many popular API providers. 82 | OAuth 2.0 allows users to authorize your application without disclosing their username and password. 83 | 84 | I've created a class called `SoundCloudAuth` which is responsible for the authentication. 85 | The main purpose of this class is to obtain an **access token** for your app. 86 | 87 | There are 3 ways you can do this: 88 | - with user's credentials (username, password) 89 | - with the authorization code 90 | - refresh token 91 | 92 | **1.** Use the credentials to obtain a token: 93 | ```java 94 | SoundCloudAuth.create(Config.CLIENT_ID,Config.CLIENT_SECRET_ID) 95 | .addRedirectUri(Config.REDIRECT_URI) 96 | .tokenWithCredentials("username","password") 97 | .subscribeOn(Schedulers.io()) 98 | .observeOn(AndroidSchedulers.mainThread()) 99 | .subscribe(token->{ 100 | //use your token 101 | //launch another activity passing the token 102 | //or save using the shared preferences 103 | //eventually you will use the token to do this SoundCloud.appendToken(token) 104 | }); 105 | ``` 106 | **2**. In order to get an authorization code, you need to open their url in a `WebView`. 107 | A pop-up window will be opened allowing the user to log in to SoundCloud and approve your app's authorization request. 108 | 109 | Approximately it will look like this: 110 | 111 | ![](https://github.com/vpaliyX/SoundCloud-API/blob/master/art/sound_pop_up.png) 112 | 113 | To make the flow smoother, you can use a `redirect_uri` with a custom protocol scheme and set your app as a handler for that protocol scheme. 114 | That's how you should set up your activity in the manifest file: 115 | ```XML 116 | 117 | 118 | 119 | 120 | 121 | 124 | 125 | 126 | ``` 127 | In the case above my `redirect_url` looks like this: `app_name://soundcloud/redirect`. 128 | 129 | Basically, I did this for you, you just need to handle the response in your activity. 130 | That's how the entire process should look like: 131 | 132 | ```java 133 | SoundCloudAuth.create(Config.CLIENT_ID,Config.CLIENT_SECRET_ID) 134 | .loginWithActivity(this,Config.REDIRECT_URI,REQUEST_CODE); 135 | ``` 136 | It launches the login activity(that popup), which returns the authorization code wrapped into Intent.class. 137 | You need to handle this in the `onActivityResult()` method. Here's an example: 138 | 139 | ```java 140 | @Override 141 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 142 | super.onActivityResult(requestCode, resultCode, data); 143 | if(requestCode==REQUEST_CODE){ 144 | if(resultCode==RESULT_OK){ 145 | String string=data.getDataString(); 146 | String code= Uri.parse(string).getQueryParameter("code"); 147 | //get your token 148 | SoundCloudAuth.create(Config.CLIENT_ID,Config.CLIENT_SECRET_ID) 149 | .addRedirectUri(Config.REDIRECT_URI) 150 | .tokenWithAuthCode(code) 151 | .subscribeOn(Schedulers.io()) 152 | .observeOn(AndroidSchedulers.mainThread()) 153 | .subscribe(token->{ 154 | 155 | }); 156 | } 157 | } 158 | } 159 | ``` 160 | Once you have received your authorization code, request an access token as showed above. 161 | 162 | **3**.If you received your token from using user's credentials, you will need to periodically refresh your access token when it expires. In order to achieve that, just call this method: 163 | 164 | ```java 165 | SoundCloudAuth.create(Config.CLIENT_ID, Config.CLIENT_SECRET_ID) 166 | .refreshToken(expiredToken) 167 | .subscribeOn(Schedulers.io()) 168 | .observeOn(AndroidSchedulers.mainThread()) 169 | .subscribe(token->{ 170 | //use your token here 171 | }); 172 | ``` 173 | 174 | ## Additional Documentation And Support ## 175 | - The [SoundCloud API Documentation](https://developers.soundcloud.com/docs/api/reference). 176 | - The [SoundCloud API Discussion Group](https://groups.google.com/forum/#!forum/soundcloudapi). 177 | - If you have any questions or you have found some issues, feel free to write in the [Issue Section](https://github.com/vpaliyX/SoundCloud-API/issues). 178 | 179 | ## Even More Examples ## 180 | Let's suppose that you want to fetch playlists and you have an array of different names; you can solve the problem this way: 181 | 182 | ```java 183 | //just a dummy Single object 184 | Single> start = Single.just(new LinkedList<>()); 185 | for(String name:names){ 186 | //combine all of them 187 | start=Single.zip(start,service.searchPlaylists(PlaylistEntity 188 | .Filter.start() 189 | .byName(name) 190 | .createOptions()) 191 | //if an error occurs, we want to skip it 192 | .onErrorResumeNext(Single.just(new ArrayList<>())),(first,second)->{ 193 | if(second!=null){ 194 | first.addAll(second); 195 | } 196 | return first; 197 | }); 198 | 199 | //call all of them 200 | start.subscribeOn(Schedulers.io()) 201 | .observeOn(AndroidSchedulers.mainThread()) 202 | .subscribe(list->{ 203 | //do something 204 | }); 205 | ``` 206 | 207 | 208 | 209 | ## The End. ## 210 | 211 | `````` 212 | MIT License 213 | 214 | Copyright (c) 2017 Vasyl Paliy 215 | 216 | Permission is hereby granted, free of charge, to any person obtaining a copy 217 | of this software and associated documentation files (the "Software"), to deal 218 | in the Software without restriction, including without limitation the rights 219 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 220 | copies of the Software, and to permit persons to whom the Software is 221 | furnished to do so, subject to the following conditions: 222 | 223 | The above copyright notice and this permission notice shall be included in all 224 | copies or substantial portions of the Software. 225 | 226 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 227 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 228 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 229 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 230 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 231 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 232 | SOFTWARE. 233 | `````` 234 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | 5 | } 6 | android { 7 | compileSdkVersion 29 8 | 9 | defaultConfig { 10 | applicationId "com.example.myapplication" 11 | minSdkVersion 21 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | 30 | packagingOptions { 31 | exclude 'META-INF/DEPENDENCIES.txt' 32 | exclude 'META-INF/LICENSE.txt' 33 | exclude 'META-INF/NOTICE.txt' 34 | exclude 'META-INF/NOTICE' 35 | exclude 'META-INF/LICENSE' 36 | exclude 'META-INF/DEPENDENCIES' 37 | exclude 'META-INF/notice.txt' 38 | exclude 'META-INF/license.txt' 39 | exclude 'META-INF/dependencies.txt' 40 | exclude 'META-INF/LGPL2.1' 41 | exclude 'META-INF/rxjava.properties' 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation project(':soundcloud-api@') 47 | implementation 'androidx.core:core-ktx:1.3.2' 48 | implementation 'androidx.appcompat:appcompat:1.2.0' 49 | implementation 'com.google.android.material:material:1.3.0' 50 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 51 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3' 52 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.3' 53 | testImplementation 'junit:junit:4.+' 54 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 55 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 56 | } 57 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/vpaliy/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/vpaliy/soundcloud_api/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud_api; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | public class MainActivity extends AppCompatActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_single.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SoundCloud-API 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/vpaliy/soundcloud/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /art/sound_pop_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/art/sound_pop_up.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'dependencies.gradle' 2 | 3 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 4 | buildscript { 5 | ext.kotlin_version = "1.3.72" 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath "com.android.tools.build:gradle:4.1.3" 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | classpath 'me.tatarka:gradle-retrolambda:3.6.0' 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } -------------------------------------------------------------------------------- /dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | /* Android Configuration */ 3 | configuration = 4 | [ 5 | package : "com.vpaliy.soundcloud_api", 6 | compileSdkVersion : 29, 7 | minSdkVersion : 21, 8 | targetSdkVersion : 29, 9 | versionCode : 1, 10 | versionName : "1.0", 11 | testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner" 12 | 13 | ] 14 | 15 | /* Library versions */ 16 | rxJavaVersion = '3.0.12' 17 | rxAndroidVersion = '3.0.0' 18 | javaxAnnotationVersion = '1.0' 19 | gsonVersion = '2.8.6' 20 | okHttpVersion = '4.9.0' 21 | retrofitVersion = "2.9.0" 22 | retrofitConverterVersion = "2.1.0" 23 | retrofitAdapterVersion = "2.2.0" 24 | 25 | 26 | /* Testing versions */ 27 | robolectricVersion = '3.1.1' 28 | jUnitVersion = '4.12' 29 | assertJVersion = '1.7.1' 30 | mockitoVersion = '2.8.47' 31 | dexmakerVersion = '1.0' 32 | espressoVersion = '2.0' 33 | testingSupportLibVersion = '0.1' 34 | 35 | /* Other stuff */ 36 | leakCanaryVersion = '1.4' 37 | 38 | /** Main dependencies **/ 39 | dependencies = [ 40 | RxJava : "io.reactivex.rxjava3:rxjava:${rxJavaVersion}", 41 | RxAndroid : "io.reactivex.rxjava3:rxandroid:${rxAndroidVersion}", 42 | javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}", 43 | gson : "com.google.code.gson:gson:${gsonVersion}", 44 | okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}", 45 | retrofit : "com.squareup.retrofit2:retrofit:${retrofitVersion}", 46 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitConverterVersion}", 47 | retrofitAdapter : 'com.squareup.retrofit2:adapter-rxjava2:2.2.0', 48 | leakCanary : "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}", 49 | ] 50 | 51 | testDependencies = [ 52 | jUnit : "junit:junit:${jUnitVersion}", 53 | mockito : "org.mockito:mockito-core:${mockitoVersion}", 54 | testingSupportLib: "com.android.support.test:testing-support-lib:${testingSupportLibVersion}", 55 | ] 56 | } -------------------------------------------------------------------------------- /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=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vpaliy/SoundCloud-API/f11c2018619c2210892e2c457bf8743ba3cdf882/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 07 13:28:48 EDT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':soundcloud-api@' 2 | -------------------------------------------------------------------------------- /soundcloud-api@/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /soundcloud-api@/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def config = rootProject.ext.configuration; 4 | def libs = rootProject.ext.dependencies; 5 | def testLibs = rootProject.ext.testDependencies; 6 | 7 | android { 8 | compileSdkVersion config.compileSdkVersion 9 | 10 | defaultConfig { 11 | minSdkVersion config.minSdkVersion 12 | targetSdkVersion config.targetSdkVersion 13 | versionCode config.versionCode 14 | } 15 | 16 | compileOptions { 17 | sourceCompatibility JavaVersion.VERSION_1_8 18 | targetCompatibility JavaVersion.VERSION_1_8 19 | } 20 | 21 | } 22 | 23 | dependencies { 24 | implementation 'androidx.appcompat:appcompat:1.2.0' 25 | 26 | implementation libs.RxJava 27 | implementation libs.retrofit 28 | implementation libs.retrofitAdapter 29 | implementation libs.retrofitConverter 30 | implementation libs.okHttp 31 | implementation libs.gson 32 | implementation libs.RxAndroid 33 | implementation libs.javaxAnnotation 34 | testImplementation testLibs.jUnit 35 | } 36 | -------------------------------------------------------------------------------- /soundcloud-api@/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/vpaliy/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/Endpoints.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud; 2 | 3 | @SuppressWarnings({"UnusedDeclaration"}) 4 | public interface Endpoints { 5 | String TOKEN = "oauth2/token"; 6 | 7 | String TRACKS = "tracks"; 8 | String TRACK_DETAILS = "tracks/{id}"; 9 | String TRACK_COMMENTS = "tracks/{id}/comments"; 10 | String TRACK_COMMENT = "tracks/{id}/comments/{comment-id}"; 11 | String TRACK_FAVORITERS = "tracks/{id}/favoriters"; 12 | String TRACK_FAVORITER = "tracks/{id}/favoriters/{user-id}"; 13 | String TRACK_SECRET_TOKEN = "tracks/{id}/secret-token"; 14 | 15 | String PLAYLISTS = "playlists"; 16 | String PLAYLIST_DETAILS = "playlists/{id}"; 17 | String PLAYLIST_SECRET_TOKEN = "playlists/{id}/secret-token"; 18 | String PLAYLIST_TRACKS = "playlists/{id}/tracks"; 19 | 20 | String USERS = "users"; 21 | String USER_DETAILS = "users/{id}"; 22 | String USER_FOLLOWINGS = "users/{id}/followings"; 23 | String USER_FOLLOWERS = "users/{id}/followers"; 24 | String USER_FOLLOWING = "users/{id}/followings/{following-id}"; 25 | String USER_FOLLOWER = "users/{id}/followers/{follower-id}"; 26 | String USER_TRACKS = "users/{id}/tracks"; 27 | String USER_COMMENTS = "user/{id}/comments"; 28 | String USER_FAVORITES = "users/{id}/favorites"; 29 | String USER_FAVORITE = "users/{id}/favorite/{favorite-id}"; 30 | String USER_PLAYLISTS = "users/{id}/playlists"; 31 | String USER_WEB_PROFILES = "users/{id}/web-profiles"; 32 | 33 | String ME = "me"; 34 | String ME_CONNECTIONS = "me/connections"; 35 | String ME_CONNECTION = "me/connections/{id}"; 36 | String ME_ACTIVITIES = "me/activities/tracks"; 37 | String ME_EXCLUSIVE_TRACKS = "me/activities/tracks/exclusive"; 38 | String ME_NEWS = "me/activities/all/own"; 39 | String ME_CONFIRMATION = "me/email-confirmations"; 40 | String ME_FRIENDS = "me/connections/friends"; 41 | String ME_DEVICES = "me/devices"; 42 | String ME_FAVORITE_TRACK = "me/favorites/{id}"; 43 | String ME_FOLLOW = "me/followings/{id}"; 44 | String ME_FAVORITE_TRACKS = "me/favorites"; 45 | String ME_FOLLOWERS = "me/followings"; 46 | 47 | String SUGGESTED_USERS = "users/suggested"; 48 | 49 | String RESOLVE = "resolve"; 50 | 51 | String APPS = "apps"; 52 | 53 | String SEND_PASSWORD = "passwords/reset-instructions"; 54 | String CONNECT = "connect"; 55 | String FACEBOOK_CONNECT = "connect/via/facebook"; 56 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/SoundCloud.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud; 2 | 3 | 4 | import android.content.Context; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.GsonBuilder; 8 | import com.vpaliy.soundcloud.model.Token; 9 | import com.vpaliy.soundcloud.utils.Adapter; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import okhttp3.Cache; 14 | import okhttp3.HttpUrl; 15 | import okhttp3.Interceptor; 16 | import okhttp3.OkHttpClient; 17 | import okhttp3.Request; 18 | import retrofit2.Retrofit; 19 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 20 | import retrofit2.converter.gson.GsonConverterFactory; 21 | 22 | public class SoundCloud { 23 | 24 | private final SoundCloudService soundCloudService; 25 | 26 | private SoundCloud(SoundCloudService soundCloudService) { 27 | this.soundCloudService = soundCloudService; 28 | } 29 | 30 | public SoundCloudService getSoundCloudService() { 31 | return soundCloudService; 32 | } 33 | 34 | public static class Builder { 35 | private static final String API_QUERY = "client_id"; 36 | private static final String OAUTH_TOKEN = "oauth_token"; 37 | 38 | private static final long CACHE_SIZE = 10 * 1024 * 1024; 39 | private static final int CONNECT_TIMEOUT = 60; 40 | private static final int WRITE_TIMEOUT = 60; 41 | private static final int READ_TIMEOUT = 60; 42 | 43 | private final String apiKey; 44 | private final Context context; 45 | 46 | private Token token; 47 | private Interceptor interceptor; 48 | private OkHttpClient okHttpClient; 49 | 50 | public Builder(Context context, String apiKey) { 51 | if (apiKey == null || context == null) { 52 | throw new IllegalArgumentException("ClientId or Context cannot be null"); 53 | } 54 | this.apiKey = apiKey; 55 | this.context = context; 56 | } 57 | 58 | public Builder setInterceptor(Interceptor interceptor) { 59 | this.interceptor = interceptor; 60 | return this; 61 | } 62 | 63 | public Builder setOkHttpClient(OkHttpClient client) { 64 | this.okHttpClient = okHttpClient; 65 | return this; 66 | } 67 | 68 | public Builder setToken(Token token) { 69 | this.token = token; 70 | return this; 71 | } 72 | 73 | private OkHttpClient provideOkHttpClient(Context context, Interceptor interceptor) { 74 | OkHttpClient.Builder builder = new OkHttpClient.Builder() 75 | .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) 76 | .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) 77 | .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) 78 | .addInterceptor(provideDefaultInterceptor()) 79 | .cache(new Cache(context.getCacheDir(), CACHE_SIZE)); 80 | if (interceptor != null) { 81 | builder.addInterceptor(interceptor); 82 | } 83 | return builder.build(); 84 | } 85 | 86 | private Interceptor provideDefaultInterceptor() { 87 | return chain -> { 88 | final Request request = chain.request(); 89 | final HttpUrl url = request.url(); 90 | final HttpUrl.Builder newUrlBuilder = url.newBuilder() 91 | .addEncodedQueryParameter(API_QUERY, apiKey); 92 | if (token != null) { 93 | newUrlBuilder.addEncodedQueryParameter(OAUTH_TOKEN, token.access); 94 | } 95 | final Request newRequest = request.newBuilder() 96 | .url(newUrlBuilder.build()) 97 | .build(); 98 | return chain.proceed(newRequest); 99 | }; 100 | } 101 | 102 | private Retrofit provideRetrofit(OkHttpClient okHttpClient) { 103 | Gson gson = new GsonBuilder() 104 | .registerTypeAdapterFactory(new Adapter()) 105 | .create(); 106 | return new Retrofit.Builder() 107 | .baseUrl(buildBaseUrl()) 108 | .addConverterFactory(GsonConverterFactory.create(gson)) 109 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 110 | .client(okHttpClient) 111 | .build(); 112 | } 113 | 114 | private HttpUrl buildBaseUrl() { 115 | return new HttpUrl.Builder() 116 | .scheme("https") 117 | .host("api.soundcloud.com") 118 | .build(); 119 | } 120 | 121 | public SoundCloud build() { 122 | if (okHttpClient == null) { 123 | okHttpClient = provideOkHttpClient(context, interceptor); 124 | } 125 | final SoundCloudService service = provideRetrofit(okHttpClient).create(SoundCloudService.class); 126 | return new SoundCloud(service); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/SoundCloudService.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud; 2 | 3 | import com.vpaliy.soundcloud.model.AppEntity; 4 | import com.vpaliy.soundcloud.model.MyActivityEntity; 5 | import com.vpaliy.soundcloud.model.CommentEntity; 6 | import com.vpaliy.soundcloud.model.ConnectionEntity; 7 | import com.vpaliy.soundcloud.model.Page; 8 | import com.vpaliy.soundcloud.model.PlaylistEntity; 9 | import com.vpaliy.soundcloud.model.SecretToken; 10 | import com.vpaliy.soundcloud.model.TrackEntity; 11 | import com.vpaliy.soundcloud.model.UserEntity; 12 | import com.vpaliy.soundcloud.model.WebProfileEntity; 13 | 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | import io.reactivex.Completable; 18 | import io.reactivex.Single; 19 | import retrofit2.http.DELETE; 20 | import retrofit2.http.GET; 21 | import retrofit2.http.PUT; 22 | import retrofit2.http.Path; 23 | import retrofit2.http.QueryMap; 24 | 25 | @SuppressWarnings("unused") 26 | public interface SoundCloudService { 27 | 28 | /** 29 | * Tracks 30 | **/ 31 | 32 | @GET(Endpoints.TRACKS) 33 | Single> searchTracks(@QueryMap Map options); 34 | 35 | @GET(Endpoints.TRACKS) 36 | Single> searchTracksPage(@QueryMap Map options); 37 | 38 | @GET(Endpoints.TRACK_DETAILS) 39 | Single fetchTrack(@Path("id") String id); 40 | 41 | @GET(Endpoints.TRACK_COMMENTS) 42 | Single> fetchTrackComments(@Path("id") String id); 43 | 44 | @GET(Endpoints.TRACK_COMMENT) 45 | Single fetchTrackComment(@Path("id") String id); 46 | 47 | @GET(Endpoints.TRACK_FAVORITERS) 48 | Single> fetchTrackFavoriters(@Path("id") String id); 49 | 50 | @GET(Endpoints.TRACK_FAVORITER) 51 | Single fetchTrackFavoriter(@Path("id") String id, @Path("user-id") String userId); 52 | 53 | @GET(Endpoints.TRACK_SECRET_TOKEN) 54 | Single fetchTrackSecretToken(@Path("id") String id); 55 | 56 | /** 57 | * Playlists 58 | **/ 59 | 60 | @GET(Endpoints.PLAYLISTS) 61 | Single> searchPlaylists(@QueryMap Map options); 62 | 63 | @GET(Endpoints.PLAYLISTS) 64 | Single> searchPlaylistsPage(@QueryMap Map options); 65 | 66 | @GET(Endpoints.PLAYLIST_DETAILS) 67 | Single fetchPlaylist(@Path("id") String id); 68 | 69 | @GET(Endpoints.PLAYLIST_TRACKS) 70 | Single> fetchPlaylistTracks(@Path("id") String id); 71 | 72 | @GET(Endpoints.PLAYLIST_SECRET_TOKEN) 73 | Single fetchPlaylistSecretToken(@Path("id") String id); 74 | 75 | /** 76 | * Users 77 | **/ 78 | 79 | @GET(Endpoints.USERS) 80 | Single> searchUsers(@QueryMap Map options); 81 | 82 | @GET(Endpoints.USERS) 83 | Single> searchUsersPage(@QueryMap Map options); 84 | 85 | @GET(Endpoints.USER_DETAILS) 86 | Single fetchUser(@Path("id") String id); 87 | 88 | @GET(Endpoints.USER_TRACKS) 89 | Single> fetchUserTracks(@Path("id") String id); 90 | 91 | @GET(Endpoints.USER_PLAYLISTS) 92 | Single> fetchUserPlaylists(@Path("id") String id); 93 | 94 | @GET(Endpoints.USER_FOLLOWERS) 95 | Single> fetchUserFollowers(@Path("id") String id); 96 | 97 | @GET(Endpoints.USER_FOLLOWINGS) 98 | Single> fetchUserFollowings(@Path("id") String id); 99 | 100 | @GET(Endpoints.USER_FOLLOWER) 101 | Single fetchUserFollower(@Path("id") String userId, @Path("follower-id") String followerId); 102 | 103 | @GET(Endpoints.USER_FOLLOWING) 104 | Single fetchUserFollowing(@Path("id") String userId, @Path("following-id") String followingId); 105 | 106 | @GET(Endpoints.USER_COMMENTS) 107 | Single> fetchUserComments(@Path("id") String id); 108 | 109 | @GET(Endpoints.USER_FAVORITES) 110 | Single> fetchUserFavoriteTracks(@Path("id") String id); 111 | 112 | @GET(Endpoints.USER_FAVORITE) 113 | Single fetchUserFavoriteTrack(@Path("id") String id, @Path("favorite-id") String trackId); 114 | 115 | @GET(Endpoints.USER_WEB_PROFILES) 116 | Single> fetchUserWebProfiles(@Path("id") String id); 117 | 118 | 119 | /** 120 | * ME 121 | **/ 122 | 123 | @GET(Endpoints.ME) 124 | Single me(); 125 | 126 | @GET(Endpoints.ME_ACTIVITIES) 127 | Single> fetchMyConnections(); 128 | 129 | @GET(Endpoints.ME_FOLLOWERS) 130 | Single> fetchMyFollowings(); 131 | 132 | @GET(Endpoints.ME_FAVORITE_TRACKS) 133 | Single> fetchMyFavoriteTracks(); 134 | 135 | @GET(Endpoints.ME_CONNECTION) 136 | Single fetchMyConnection(@Path("id") String connectionId); 137 | 138 | @GET(Endpoints.ME_ACTIVITIES) 139 | Single> fetchMyActivities(); 140 | 141 | @GET(Endpoints.SUGGESTED_USERS) 142 | Single> fetchSuggestedUsers(); 143 | 144 | @PUT(Endpoints.ME_FAVORITE_TRACK) 145 | Completable loveTrack(@Path("id") String id); 146 | 147 | @DELETE(Endpoints.ME_FAVORITE_TRACK) 148 | Completable unloveTrack(@Path("id") String id); 149 | 150 | @PUT(Endpoints.ME_FOLLOW) 151 | Completable followUser(@Path("id") String id); 152 | 153 | @DELETE(Endpoints.ME_FOLLOW) 154 | Completable unfollowUser(@Path("id") String id); 155 | 156 | @GET(Endpoints.ME_FOLLOW) 157 | Completable amIFollowing(@Path("id") String id); 158 | 159 | @GET(Endpoints.ME_FAVORITE_TRACK) 160 | Completable didILike(@Path("id") String id); 161 | 162 | /** 163 | * APPS 164 | **/ 165 | 166 | @GET(Endpoints.APPS) 167 | Single> fetchApps(); 168 | } 169 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/auth/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.auth; 2 | 3 | import com.vpaliy.soundcloud.Endpoints; 4 | import com.vpaliy.soundcloud.model.Token; 5 | 6 | import java.util.Map; 7 | 8 | import io.reactivex.Single; 9 | import retrofit2.http.FormUrlEncoded; 10 | import retrofit2.http.FieldMap; 11 | import retrofit2.http.POST; 12 | 13 | interface AuthService { 14 | @FormUrlEncoded 15 | @POST(Endpoints.TOKEN) 16 | Single requestToken(@FieldMap Map authMap); 17 | } 18 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/auth/Connect.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.auth; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | @SuppressWarnings({"unused", "WeakerAccess"}) 7 | class Connect implements Parcelable { 8 | final String url; 9 | final String redirectUri; 10 | 11 | public Connect(Parcel parcel) { 12 | this.url = parcel.readString(); 13 | this.redirectUri = parcel.readString(); 14 | } 15 | 16 | public Connect(String url, String redirectUri) { 17 | this.url = url; 18 | this.redirectUri = redirectUri; 19 | } 20 | 21 | @Override 22 | public int describeContents() { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public void writeToParcel(Parcel dest, int flags) { 28 | dest.writeString(url); 29 | dest.writeString(redirectUri); 30 | } 31 | 32 | public static Creator CREATOR = new Creator() { 33 | @Override 34 | public Connect createFromParcel(Parcel source) { 35 | return new Connect(source); 36 | } 37 | 38 | @Override 39 | public Connect[] newArray(int size) { 40 | return new Connect[size]; 41 | } 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/auth/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.auth; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.webkit.WebView; 8 | import android.webkit.WebViewClient; 9 | 10 | import com.vpaliy.soundcloud.R; 11 | 12 | import androidx.annotation.Nullable; 13 | import androidx.appcompat.app.AppCompatActivity; 14 | 15 | public class LoginActivity extends AppCompatActivity { 16 | 17 | static final String EXTRA_CONNECT_DATA = LoginActivity.class.getCanonicalName() + "Extra.Connect.Data"; 18 | private Connect connect; 19 | 20 | @SuppressLint("SetJavaScriptEnabled") 21 | @Override 22 | protected void onCreate(@Nullable Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_web); 25 | WebView webView = findViewById(R.id.web); 26 | webView.getSettings().setJavaScriptEnabled(true); 27 | webView.setWebViewClient(new WebViewAuthClient()); 28 | connect = getIntent().getParcelableExtra(EXTRA_CONNECT_DATA); 29 | if (connect.url != null) { 30 | webView.loadUrl(connect.url); 31 | } else { 32 | finish(); 33 | } 34 | } 35 | 36 | private class WebViewAuthClient extends WebViewClient { 37 | 38 | private final Intent resultIntent = new Intent(); 39 | 40 | @Override 41 | public void onPageFinished(WebView view, String url) { 42 | super.onPageFinished(view, url); 43 | 44 | if (url.startsWith(connect.redirectUri)) { 45 | Uri data = Uri.parse(url); 46 | resultIntent.setData(data); 47 | if (url.contains("?code=")) { 48 | setResult(RESULT_OK, resultIntent); 49 | } else { 50 | setResult(RESULT_CANCELED, resultIntent); 51 | } 52 | finish(); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/auth/SoundCloudAuth.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.auth; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.text.TextUtils; 6 | 7 | import com.vpaliy.soundcloud.model.Token; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import io.reactivex.Single; 13 | import okhttp3.HttpUrl; 14 | import okhttp3.Interceptor; 15 | import okhttp3.OkHttpClient; 16 | import okhttp3.Request; 17 | import retrofit2.Retrofit; 18 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 19 | import retrofit2.converter.gson.GsonConverterFactory; 20 | 21 | @SuppressWarnings({"unused"}) 22 | public class SoundCloudAuth { 23 | 24 | private String clientId; 25 | private String clientSecret; 26 | private String redirectUri; 27 | 28 | private String GRANT_TYPE = "grant_type"; 29 | private String CLIENT_ID = "client_id"; 30 | private String CLIENT_SECRET = "client_secret"; 31 | private String USERNAME = "username"; 32 | private String REDIRECT_URI = "redirect_uri"; 33 | private String CODE = "code"; 34 | private String RESPONSE_TYPE = "response_type"; 35 | private String SCOPE = "scope"; 36 | private String DISPLAY = "display"; 37 | private String STATE = "state"; 38 | private String PASSWORD = "password"; 39 | 40 | 41 | private SoundCloudAuth(String clientId, String clientSecret) { 42 | if (clientId == null || clientSecret == null) { 43 | throw new IllegalArgumentException("client_id or client_secret is null"); 44 | } 45 | this.clientId = clientId; 46 | this.clientSecret = clientSecret; 47 | } 48 | 49 | @SuppressWarnings("WeakerAccess") 50 | public class GrantType { 51 | public static final String AUTH_CODE = "authorization_code"; 52 | public static final String REFRESH_TOKEN = "refresh_token"; 53 | public static final String PASSWORD = "password"; 54 | public static final String CLIENT_CREDENTIALS = "client_credentials"; 55 | public static final String OAUTH1_TOKEN = "oauth1_token"; 56 | } 57 | 58 | 59 | private Interceptor buildInterceptor() { 60 | return (chain -> { 61 | Request originalRequest = chain.request(); 62 | HttpUrl originalHttpUrl = originalRequest.url(); 63 | HttpUrl newHttpUrl = originalHttpUrl.newBuilder() 64 | .setQueryParameter(CLIENT_ID, clientId) 65 | .build(); 66 | Request newRequest = originalRequest.newBuilder() 67 | .url(newHttpUrl).build(); 68 | return chain.proceed(newRequest); 69 | }); 70 | } 71 | 72 | private Retrofit buildRetrofit() { 73 | OkHttpClient okHttpClient = new OkHttpClient.Builder() 74 | .addInterceptor(buildInterceptor()) 75 | .build(); 76 | return new Retrofit.Builder() 77 | .baseUrl("https://api.soundcloud.com/") 78 | .addConverterFactory(GsonConverterFactory.create()) 79 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 80 | .client(okHttpClient) 81 | .build(); 82 | } 83 | 84 | public SoundCloudAuth addRedirectUri(String redirectUri) { 85 | this.redirectUri = redirectUri; 86 | return this; 87 | } 88 | 89 | private AuthService createService() { 90 | return buildRetrofit().create(AuthService.class); 91 | } 92 | 93 | public Single tokenWithAuthCode(String code) { 94 | if (TextUtils.isEmpty(code)) { 95 | throw new IllegalArgumentException("auth is empty"); 96 | } 97 | AuthService service = createService(); 98 | Map fieldMap = new HashMap<>(); 99 | fieldMap.put(CLIENT_ID, clientId); 100 | fieldMap.put(CLIENT_SECRET, clientSecret); 101 | fieldMap.put(GRANT_TYPE, GrantType.AUTH_CODE); 102 | fieldMap.put(CODE, code); 103 | if (redirectUri != null) { 104 | fieldMap.put(REDIRECT_URI, redirectUri); 105 | } 106 | return service.requestToken(fieldMap); 107 | } 108 | 109 | public Single tokenWithCredentials(String username, String password) { 110 | if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { 111 | throw new IllegalArgumentException("username or password is null"); 112 | } 113 | AuthService service = createService(); 114 | Map fieldMap = new HashMap<>(); 115 | fieldMap.put(CLIENT_ID, clientId); 116 | fieldMap.put(CLIENT_SECRET, clientSecret); 117 | fieldMap.put(GRANT_TYPE, GrantType.PASSWORD); 118 | fieldMap.put(USERNAME, username); 119 | fieldMap.put(PASSWORD, password); 120 | if (redirectUri != null) { 121 | fieldMap.put(REDIRECT_URI, redirectUri); 122 | } 123 | return service.requestToken(fieldMap); 124 | } 125 | 126 | public Single refreshToken(Token token) { 127 | if (token == null || TextUtils.isEmpty(token.refresh)) { 128 | throw new IllegalArgumentException("Wrong token"); 129 | } 130 | AuthService service = createService(); 131 | Map fieldMap = new HashMap<>(); 132 | fieldMap.put(CLIENT_ID, clientId); 133 | fieldMap.put(CLIENT_SECRET, clientSecret); 134 | fieldMap.put(GrantType.REFRESH_TOKEN, token.refresh); 135 | fieldMap.put(GRANT_TYPE, GrantType.REFRESH_TOKEN); 136 | if (redirectUri != null) { 137 | fieldMap.put(REDIRECT_URI, redirectUri); 138 | } 139 | return service.requestToken(fieldMap); 140 | } 141 | 142 | public void loginWithActivity(Activity activity, String redirectUri, int requestCode) { 143 | if (TextUtils.isEmpty(redirectUri)) { 144 | throw new IllegalArgumentException("redirectUri is empty"); 145 | } 146 | String url = "https://www.soundcloud.com/connect?" + 147 | "client_id=" + clientId + 148 | "&redirect_uri=" + redirectUri + 149 | "&response_type=" + "code" + 150 | "&scope=" + "non-expiring" + 151 | "&display=" + "popup" + 152 | "&state=" + "asdf"; 153 | Connect connect = new Connect(url, redirectUri); 154 | Intent intent = new Intent(activity, LoginActivity.class); 155 | intent.putExtra(LoginActivity.EXTRA_CONNECT_DATA, connect); 156 | activity.startActivityForResult(intent, requestCode); 157 | } 158 | 159 | public static SoundCloudAuth create(String clientId, String clientSecret) { 160 | return new SoundCloudAuth(clientId, clientSecret); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/AppEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class AppEntity { 4 | public String id; 5 | public String kind; 6 | public String name; 7 | public String uri; 8 | public String permalink_url; 9 | public String external_url; 10 | public String creator; 11 | 12 | public AppEntity() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/CommentEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class CommentEntity { 4 | public String id; 5 | public String uri; 6 | public String created_at; 7 | public String body; 8 | public String timestamp; 9 | public String user_id; 10 | public MiniUserEntity user; 11 | public String track_id; 12 | 13 | public CommentEntity() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/ConnectionEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class ConnectionEntity { 4 | public String created_at; 5 | public String display_name; 6 | public String id; 7 | public String post_favorite; 8 | public String post_publish; 9 | public String service; 10 | public String type; 11 | public String uri; 12 | 13 | public ConnectionEntity() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/MeEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class MeEntity extends UserEntity { 6 | 7 | public String plan; 8 | public long private_tracks_count; 9 | public long private_playlists_count; 10 | @SerializedName("primary_email_confirmed") 11 | public boolean isEmailConfirmed; 12 | 13 | public MeEntity() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/MiniUserEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class MiniUserEntity { 4 | public String avatar_url; 5 | public String id; 6 | public String kind; 7 | public String last_modified; 8 | public String permalink; 9 | public String permalink_url; 10 | public String uri; 11 | public String username; 12 | 13 | public MiniUserEntity() { 14 | } 15 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/MyActivityEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class MyActivityEntity { 4 | 5 | public String type; 6 | public String created_at; 7 | public String tags; 8 | 9 | public Origin origin; 10 | 11 | public MyActivityEntity() { 12 | } 13 | 14 | public class Origin { 15 | public TrackEntity track; 16 | public CommentEntity comment; 17 | public MiniUserEntity miniUser; 18 | 19 | public Origin() { 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/Page.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | import android.net.Uri; 4 | import android.text.TextUtils; 5 | 6 | import com.vpaliy.soundcloud.utils.Adapter; 7 | 8 | import java.util.List; 9 | 10 | import com.google.gson.annotations.SerializedName; 11 | 12 | public class Page implements Adapter.PostProcessable { 13 | 14 | public List collection; 15 | 16 | @SerializedName("next_href") 17 | private String next_href; 18 | 19 | public boolean isLast; 20 | public int futureOffset; 21 | public String query; 22 | 23 | public Page() { 24 | } 25 | 26 | @Override 27 | public void postProcess() { 28 | isLast = next_href == null || TextUtils.isEmpty(next_href); 29 | futureOffset = toNumber("offset="); 30 | query(); 31 | 32 | } 33 | 34 | private void query() { 35 | if (next_href != null) { 36 | int index = next_href.indexOf("&q=") + 3; 37 | if (next_href.length() > index) { 38 | int lastIndex = next_href.indexOf("&", index); 39 | query = next_href.substring(index, lastIndex != -1 ? lastIndex : next_href.length()); 40 | query = Uri.decode(query); 41 | } 42 | } 43 | } 44 | 45 | private int toNumber(String pattern) { 46 | if (next_href != null) { 47 | int index = next_href.indexOf(pattern); 48 | int number = 0; 49 | while (true) { 50 | int tempIndex = index + pattern.length(); 51 | if (next_href.length() <= tempIndex || !Character.isDigit(next_href.charAt(tempIndex))) { 52 | break; 53 | } 54 | number *= 10; 55 | number += next_href.charAt(tempIndex) - '0'; 56 | index++; 57 | } 58 | return number; 59 | } 60 | return -1; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/PlaylistEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @SuppressWarnings("unused") 10 | public class PlaylistEntity { 11 | public String id; 12 | public String created_at; 13 | public String user_id; 14 | public String duration; 15 | public String sharing; 16 | public String tag_list; 17 | public String permalink; 18 | public String track_count; 19 | 20 | @SerializedName("streamable") 21 | public boolean is_streamable; 22 | 23 | @SerializedName("downloadable") 24 | public boolean is_downloadable; 25 | public String embeddable_by; 26 | public String purchase_url; 27 | public String type; 28 | public String label; 29 | public String label_id; 30 | public String label_name; 31 | public String playlist_type; 32 | public String ean; 33 | public String description; 34 | public String genre; 35 | public String release; 36 | public String purchase_title; 37 | public String title; 38 | public String release_year; 39 | public String release_month; 40 | public String release_day; 41 | public String license; 42 | public String uri; 43 | public String permalink_url; 44 | public String artwork_url; 45 | public MiniUserEntity user; 46 | public List tracks; 47 | 48 | public PlaylistEntity() { 49 | } 50 | 51 | public static class PlaylistType { 52 | public static final String EP_SINGLE = "ep single"; 53 | public static final String ALBUM = "album"; 54 | public static final String COMPILATION = "compilation"; 55 | public static final String PROJECT_FILES = "project files"; 56 | public static final String ARCHIVE = "archive"; 57 | public static final String SHOWCASE = "showcase"; 58 | public static final String DEMO = "demo"; 59 | public static final String SAMPLE_PACK = "sample pack"; 60 | public static final String OTHER = "other"; 61 | } 62 | 63 | public static class EmbeddableBy { 64 | public static final String ALL = "all"; 65 | public static final String ME = "me"; 66 | public static final String NONE = "none"; 67 | } 68 | 69 | public enum Representation { 70 | ID("id"), 71 | COMPACT("compact"); 72 | public String name; 73 | 74 | Representation(String name) { 75 | this.name = name; 76 | } 77 | } 78 | 79 | @SuppressWarnings({"unused", "WeakerAccess"}) 80 | public static class Filter { 81 | 82 | private Map options; 83 | private Page page; 84 | 85 | public Filter() { 86 | options = new HashMap<>(); 87 | } 88 | 89 | public Filter byRepresentation(Representation representation) { 90 | options.put("representation", representation.name); 91 | return this; 92 | } 93 | 94 | public Filter byName(String name) { 95 | options.put("q", name); 96 | return this; 97 | } 98 | 99 | public Filter nextPage(Page page) { 100 | this.page = page; 101 | return this; 102 | } 103 | 104 | public Filter limit(int limit) { 105 | options.put("limit", limit); 106 | return this; 107 | } 108 | 109 | public Filter withPagination() { 110 | options.put("linked_partitioning", -1); 111 | return this; 112 | } 113 | 114 | public Filter offset(int offset) { 115 | options.put("offset", offset); 116 | return this; 117 | } 118 | 119 | public static Filter start() { 120 | return new Filter(); 121 | } 122 | 123 | public Map createOptions() { 124 | if (page != null) { 125 | if (!page.isLast) { 126 | withPagination(); 127 | options.put("offset", page.futureOffset); 128 | options.put("q", page.query); 129 | } 130 | } 131 | page = null; 132 | return options; 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/SecretToken.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class SecretToken { 4 | public String kind; 5 | public String token; 6 | public String uri; 7 | public String resource_uri; 8 | 9 | public SecretToken() { 10 | } 11 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/Token.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.Date; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Represents an OAuth2 access/refresh token pair. 12 | */ 13 | @SuppressWarnings("WeakerAccess") 14 | public class Token { 15 | 16 | public static final String SCOPE_DEFAULT = "*"; 17 | 18 | public static final String SCOPE_SIGNUP = "signup"; 19 | public static final String SCOPE_PLAYCOUNT = "playcount"; 20 | public static final String SCOPE_NON_EXPIRING = "non-expiring"; 21 | 22 | private static final String ACCESS_TOKEN = "access_token"; 23 | private static final String REFRESH_TOKEN = "refresh_token"; 24 | private static final String SCOPE = "scope"; 25 | private static final String EXPIRES_IN = "expires_in"; 26 | 27 | @SerializedName("access_token") 28 | public String access; 29 | public String refresh; 30 | public String scope; 31 | public long expiresIn; 32 | 33 | public Token() { 34 | } 35 | 36 | public final Map customParameters = new HashMap(); 37 | 38 | /** 39 | * Invalidates the access token 40 | */ 41 | public void invalidate() { 42 | this.access = null; 43 | } 44 | 45 | /** 46 | * @return null or the date of expiration of this token 47 | */ 48 | public Date getExpiresIn() { 49 | return expiresIn == 0 ? null : new Date(expiresIn); 50 | } 51 | 52 | public boolean defaultScoped() { 53 | return scoped(SCOPE_DEFAULT); 54 | } 55 | 56 | /** 57 | * @return has token the signup scope ("signup") 58 | */ 59 | public boolean signupScoped() { 60 | return scoped(SCOPE_SIGNUP); 61 | } 62 | 63 | public boolean scoped(String scope) { 64 | if (this.scope != null) { 65 | for (String s : this.scope.split(" ")) 66 | if (scope.equals(s)) return true; 67 | } 68 | return false; 69 | } 70 | 71 | public boolean valid() { 72 | return access != null && (scoped(SCOPE_NON_EXPIRING) || refresh != null); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "Token{" + 78 | "access='" + access + '\'' + 79 | ", refresh='" + refresh + '\'' + 80 | ", scope='" + scope + '\'' + 81 | ", expires=" + getExpiresIn() + 82 | '}'; 83 | } 84 | 85 | @Override 86 | public boolean equals(Object o) { 87 | if (this == o) return true; 88 | if (o == null) return false; 89 | 90 | if (o instanceof Token) { 91 | Token token = (Token) o; 92 | if (access != null ? !access.equals(token.access) : token.access != null) return false; 93 | if (refresh != null ? !refresh.equals(token.refresh) : token.refresh != null) 94 | return false; 95 | if (scope != null ? !scope.equals(token.scope) : token.scope != null) return false; 96 | return true; 97 | } else { 98 | return super.equals(o); 99 | } 100 | } 101 | 102 | @Override 103 | public int hashCode() { 104 | int result = access != null ? access.hashCode() : 0; 105 | result = 31 * result + (refresh != null ? refresh.hashCode() : 0); 106 | result = 31 * result + (scope != null ? scope.hashCode() : 0); 107 | return result; 108 | } 109 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/TrackEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | import java.util.Arrays; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import com.google.gson.annotations.SerializedName; 9 | 10 | @SuppressWarnings("unused") 11 | public class TrackEntity { 12 | 13 | public String id; 14 | public String created_at; 15 | public String user_id; 16 | public MiniUserEntity user; 17 | public String title; 18 | public String permalink; 19 | public String permalink_url; 20 | public String uri; 21 | public String sharing; 22 | public String embeddable_by; 23 | public String purchase_url; 24 | public String artwork_url; 25 | public String description; 26 | public String duration; 27 | public String genre; 28 | public String tag_list; 29 | public String label_id; 30 | public String label_name; 31 | public String release; 32 | public String release_day; 33 | public String release_month; 34 | public String release_year; 35 | @SerializedName("streamable") 36 | public boolean is_streamable; 37 | @SerializedName("downloadable") 38 | public boolean is_downloadable; 39 | public String state; 40 | public String license; 41 | public String track_type; 42 | public String waveform_url; 43 | public String download_url; 44 | public String stream_url; 45 | public String video_url; 46 | public String bpm; 47 | public boolean commentable; 48 | public String isrc; 49 | public String key_signature; 50 | public String comment_count; 51 | public String download_count; 52 | public String playback_count; 53 | public String favoritings_count; 54 | public String original_format; 55 | public String original_content_size; 56 | public String asset_data; 57 | public String artwork_data; 58 | public boolean user_favorite; 59 | 60 | public TrackEntity() { 61 | } 62 | 63 | public enum License { 64 | 65 | ALL_RIGHTS_RESERVED("all-rights-reserved"), 66 | NO_RIGHTS_RESERVED("no-rights-reserved"), 67 | CC_ATTRIBUTION("cc-by"), 68 | CC_ATTRIBUTION_NONCOMMERCIAL("cc-by-nc"), 69 | CC_ATTRIBUTION_NO_DERIVATIVES("cc-by-nd"), 70 | CC_ATTRIBUTION_SHARE_ALIKE("cc-by-sa"), 71 | CC_ATTRIBUTION_NONCOMMERCIAL_NO_DERIVATES("cc-by-nc-nd"), 72 | CC_ATTRIBUTION_NONCOMMERCIAL_SHARE_ALIKE("cc-by-nc-sa"); 73 | 74 | private final String license; 75 | 76 | License(String license) { 77 | this.license = license; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return license; 83 | } 84 | } 85 | 86 | public enum Type { 87 | 88 | ORIGINAL("original"), 89 | REMIX("remix"), 90 | LIVE("live"), 91 | RECORDING("recording"), 92 | SPOKEN("spoken"), 93 | PODCAST("podcast"), 94 | DEMO("demo"), 95 | IN_PROGRESS("in progress"), 96 | STEM("stem"), 97 | LOOP("loop"), 98 | SOUND_EFFECT("sound effect"), 99 | SAMPLE("sample"), 100 | OTHER("other"); 101 | 102 | private final String type; 103 | 104 | Type(String type) { 105 | this.type = type; 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return type; 111 | } 112 | } 113 | 114 | 115 | public enum State { 116 | 117 | PROCESSING("processing"), 118 | FAILED("failed"), 119 | FINISHED("finished"); 120 | 121 | private final String state; 122 | 123 | State(String state) { 124 | this.state = state; 125 | } 126 | 127 | @Override 128 | public String toString() { 129 | return state; 130 | } 131 | } 132 | 133 | public enum Visibility { 134 | ALL("all"), 135 | PUBLIC("public"), 136 | PRIVATE("private"); 137 | 138 | private final String filter; 139 | 140 | Visibility(String filter) { 141 | this.filter = filter; 142 | } 143 | 144 | @Override 145 | public String toString() { 146 | return filter; 147 | } 148 | } 149 | 150 | 151 | public enum EmbeddableBy { 152 | ALL("all"), 153 | ME("me"), 154 | NONE("none"); 155 | 156 | private final String embeddableBy; 157 | 158 | EmbeddableBy(String embeddableBy) { 159 | this.embeddableBy = embeddableBy; 160 | } 161 | 162 | @Override 163 | public String toString() { 164 | return embeddableBy; 165 | } 166 | } 167 | 168 | @SuppressWarnings({"WeakerAccess"}) 169 | public static class Filter { 170 | 171 | private Map options; 172 | private Page page; 173 | 174 | public Filter() { 175 | options = new HashMap<>(); 176 | } 177 | 178 | public Filter byName(String name) { 179 | options.put("q", name); 180 | return this; 181 | } 182 | 183 | public Filter limit(int limit) { 184 | options.put("limit", limit); 185 | return this; 186 | } 187 | 188 | public Filter nextPage(Page page) { 189 | this.page = page; 190 | return this; 191 | } 192 | 193 | 194 | public Filter offset(int offset) { 195 | options.put("offset", offset); 196 | return this; 197 | } 198 | 199 | public Filter byGenres(String... genres) { 200 | if (genres != null) { 201 | options.put("genres", convert(genres)); 202 | } 203 | return this; 204 | } 205 | 206 | public Filter byTags(String... tags) { 207 | if (tags != null) { 208 | options.put("tags", convert(tags)); 209 | } 210 | return this; 211 | } 212 | 213 | public Filter byLicense(License license) { 214 | if (license != null) { 215 | options.put("license", license.license); 216 | } 217 | return this; 218 | } 219 | 220 | public Filter byTypes(Type... types) { 221 | if (types != null) { 222 | options.put("types", convert(types)); 223 | } 224 | return this; 225 | } 226 | 227 | public Filter byBPM(int from, int to) { 228 | options.put("bpm[from]", from); 229 | options.put("bpm[to]", to); 230 | return this; 231 | } 232 | 233 | public Filter withPagination() { 234 | options.put("linked_partitioning", -1); 235 | return this; 236 | } 237 | 238 | public Filter byVisibility(Visibility visibility) { 239 | if (visibility != null) { 240 | options.put("filter", visibility.filter); 241 | } 242 | return this; 243 | } 244 | 245 | @SuppressWarnings("all") 246 | private String convert(T... strings) { 247 | return Arrays.toString(strings) 248 | .replaceAll("[\\[.\\].\\s+]", ""); 249 | } 250 | 251 | public Filter byTime(Date from, Date to) { 252 | options.put("created_at[from]", from); 253 | options.put("created_at[to]", to); 254 | return this; 255 | } 256 | 257 | public Filter byDuration(int fromMillis, int toMillis) { 258 | options.put("duration[from]", fromMillis); 259 | options.put("duration[to]", toMillis); 260 | return this; 261 | } 262 | 263 | public Filter byIds(String... ids) { 264 | options.put("ids", convert(ids)); 265 | return this; 266 | } 267 | 268 | public Map createOptions() { 269 | if (page != null) { 270 | if (!page.isLast) { 271 | withPagination(); 272 | options.put("offset", page.futureOffset); 273 | options.put("q", page.query); 274 | } 275 | } 276 | page = null; 277 | return options; 278 | } 279 | 280 | public static Filter start() { 281 | return new Filter(); 282 | } 283 | } 284 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class UserEntity { 9 | public String id; 10 | public String permalink; 11 | public String username; 12 | public String uri; 13 | public String permalink_url; 14 | public String avatar_url; 15 | public String country; 16 | public String full_name; 17 | public String city; 18 | public String description; 19 | 20 | @SerializedName("discogs-name") 21 | public String discogs_name; 22 | 23 | @SerializedName("myspace-name") 24 | public String myspace_name; 25 | public String website; 26 | 27 | @SerializedName("website-tile") 28 | public String website_title; 29 | 30 | @SerializedName("online") 31 | public boolean is_online; 32 | public String track_count; 33 | public String playlist_count; 34 | public String followers_count; 35 | public String followings_count; 36 | public String public_favorites_count; 37 | public String avatar_data; 38 | 39 | public UserEntity() { 40 | } 41 | 42 | @SuppressWarnings({"unused", "WeakerAccess"}) 43 | public static class Filter { 44 | 45 | private Map options; 46 | private Page page; 47 | 48 | public Filter() { 49 | options = new HashMap<>(); 50 | } 51 | 52 | public Filter byName(String name) { 53 | options.put("q", name); 54 | return this; 55 | } 56 | 57 | public Filter nextPage(Page page) { 58 | this.page = page; 59 | return this; 60 | } 61 | 62 | public Filter withPagination() { 63 | options.put("linked_partitioning", -1); 64 | return this; 65 | } 66 | 67 | public Filter limit(int limit) { 68 | options.put("limit", limit); 69 | return this; 70 | } 71 | 72 | public Filter offset(int offset) { 73 | options.put("offset", offset); 74 | return this; 75 | } 76 | 77 | public Filter invalidateAll() { 78 | options.clear(); 79 | return this; 80 | } 81 | 82 | public Map createOptions() { 83 | if (page != null) { 84 | if (!page.isLast) { 85 | withPagination(); 86 | options.put("offset", page.futureOffset); 87 | options.put("q", page.query); 88 | } 89 | } 90 | page = null; 91 | return options; 92 | } 93 | 94 | public static Filter start() { 95 | return new Filter(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/model/WebProfileEntity.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.model; 2 | 3 | public class WebProfileEntity { 4 | private String kind; 5 | private String id; 6 | private String service; 7 | private String title; 8 | private String url; 9 | private String username; 10 | private String created_at; 11 | 12 | public WebProfileEntity() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/java/com/vpaliy/soundcloud/utils/Adapter.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.TypeAdapter; 5 | import com.google.gson.TypeAdapterFactory; 6 | import com.google.gson.reflect.TypeToken; 7 | import com.google.gson.stream.JsonReader; 8 | import com.google.gson.stream.JsonWriter; 9 | 10 | import java.io.IOException; 11 | 12 | public class Adapter implements TypeAdapterFactory { 13 | 14 | public interface PostProcessable { 15 | void postProcess(); 16 | } 17 | 18 | public TypeAdapter create(Gson gson, TypeToken type) { 19 | final TypeAdapter delegate = gson.getDelegateAdapter(this, type); 20 | 21 | return new TypeAdapter() { 22 | public void write(JsonWriter out, T value) throws IOException { 23 | delegate.write(out, value); 24 | } 25 | 26 | public T read(JsonReader in) throws IOException { 27 | T obj = delegate.read(in); 28 | if (obj instanceof PostProcessable) { 29 | ((PostProcessable) obj).postProcess(); 30 | } 31 | return obj; 32 | } 33 | }; 34 | } 35 | } -------------------------------------------------------------------------------- /soundcloud-api@/src/main/res/layout/activity_web.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /soundcloud-api@/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | soundcloud-api 3 | 4 | -------------------------------------------------------------------------------- /soundcloud-api@/src/test/java/com/vpaliy/soundcloud/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vpaliy.soundcloud; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } --------------------------------------------------------------------------------