diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 77a908b0..612a10bb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,11 +22,11 @@ test: - adb shell settings put global transition_animation_scale 0.0 - adb shell settings put global animator_duration_scale 0.0 - - ./gradlew build connectedCheck jacocoTestReport + - ./gradlew build connectedCheck connectedDebugAndroidTest jacocoTestReport - cat app/build/reports/jacoco/jacocoTestReport/html/index.html | grep -o 'Total[^%]*%' artifacts: paths: - ./app/build/reports/jacoco/jacocoTestReport/ - expire_in: 1 week \ No newline at end of file + expire_in: 1 week diff --git a/app/build.gradle b/app/build.gradle index 2b2519ee..5db181a8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' @@ -68,7 +68,7 @@ dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.2.17' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation "androidx.browser:browser:1.2.0" - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.2.0' implementation 'com.github.connyduck:sparkbutton:4.0.0' def room_version = "2.2.5" @@ -140,14 +140,14 @@ dependencies { debugImplementation "androidx.fragment:fragment-testing:$fragment_version" // Use the most recent version of CameraX - def camerax_version = '1.0.0-beta07' + def camerax_version = '1.0.0-beta08' implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:$camerax_version" // CameraX View class - implementation 'androidx.camera:camera-view:1.0.0-alpha14' + implementation 'androidx.camera:camera-view:1.0.0-alpha15' implementation 'com.karumi:dexter:6.2.1' @@ -162,7 +162,7 @@ tasks.withType(Test) { } -task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) { +task jacocoTestReport(type: JacocoReport, dependsOn: ['connectedDebugAndroidTest', 'testDebugUnitTest', 'createDebugCoverageReport']) { reports { xml.enabled = true @@ -181,4 +181,4 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'crea 'jacoco/testDebugUnitTest.exec' ])) -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/h/pixeldroid/HomeFeedTest.kt b/app/src/androidTest/java/com/h/pixeldroid/HomeFeedTest.kt index 18e8d418..b4dd6ba3 100644 --- a/app/src/androidTest/java/com/h/pixeldroid/HomeFeedTest.kt +++ b/app/src/androidTest/java/com/h/pixeldroid/HomeFeedTest.kt @@ -2,6 +2,7 @@ package com.h.pixeldroid import android.content.Context +import android.service.autofill.Validators.and import android.widget.TextView import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider @@ -17,6 +18,7 @@ import com.h.pixeldroid.db.AppDatabase import com.h.pixeldroid.db.InstanceDatabaseEntity import com.h.pixeldroid.db.UserDatabaseEntity import com.h.pixeldroid.fragments.feeds.postFeeds.PostViewHolder +import com.h.pixeldroid.testUtility.CustomMatchers.Companion.atPosition import com.h.pixeldroid.testUtility.CustomMatchers.Companion.clickChildViewWithId import com.h.pixeldroid.testUtility.CustomMatchers.Companion.first import com.h.pixeldroid.testUtility.CustomMatchers.Companion.getText @@ -26,6 +28,8 @@ import com.h.pixeldroid.testUtility.CustomMatchers.Companion.typeTextInViewWithI import com.h.pixeldroid.testUtility.MockServer import com.h.pixeldroid.testUtility.initDB import com.h.pixeldroid.utils.DBUtils +import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.CoreMatchers.not import org.junit.Before import org.junit.Rule import org.junit.Test @@ -217,7 +221,8 @@ class HomeFeedTest { (1, clickChildViewWithId(R.id.sensitiveWarning))) Thread.sleep(1000) - onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE))) + onView(withId(R.id.list)) + .check(matches(atPosition(1, not(withId(R.id.sensitiveWarning))))) } @Test diff --git a/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt b/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt index 0ce5a9ef..a4aa3896 100644 --- a/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt +++ b/app/src/androidTest/java/com/h/pixeldroid/testUtility/CustomMatchers.kt @@ -3,14 +3,18 @@ package com.h.pixeldroid.testUtility import android.view.View import android.widget.EditText import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.action.* +import androidx.test.espresso.matcher.BoundedMatcher import androidx.test.espresso.matcher.ViewMatchers import org.hamcrest.BaseMatcher +import org.hamcrest.Description import org.hamcrest.Matcher + abstract class CustomMatchers { companion object { fun first(matcher: Matcher): Matcher? { @@ -50,6 +54,24 @@ abstract class CustomMatchers { } } + + fun atPosition(position: Int, itemMatcher: Matcher): Matcher? { + return object : BoundedMatcher(RecyclerView::class.java) { + override fun describeTo(description: Description) { + description.appendText("has item at position $position: ") + itemMatcher.describeTo(description) + } + + override fun matchesSafely(view: RecyclerView): Boolean { + val viewHolder = view.findViewHolderForAdapterPosition(position) + ?: // has no item on such position + return false + return itemMatcher.matches(viewHolder.itemView) + } + } + } + + /** * @param percent can be 1 or 0 * 1: swipes all the way up @@ -60,8 +82,9 @@ abstract class CustomMatchers { GeneralSwipeAction( Swipe.SLOW, GeneralLocation.BOTTOM_CENTER, - if(percent) GeneralLocation.TOP_CENTER else GeneralLocation.CENTER, - Press.FINGER) + if (percent) GeneralLocation.TOP_CENTER else GeneralLocation.CENTER, + Press.FINGER + ) ) } @@ -75,8 +98,9 @@ abstract class CustomMatchers { GeneralSwipeAction( Swipe.SLOW, GeneralLocation.CENTER_RIGHT, - if(percent) GeneralLocation.CENTER_LEFT else GeneralLocation.CENTER, - Press.FINGER) + if (percent) GeneralLocation.CENTER_LEFT else GeneralLocation.CENTER, + Press.FINGER + ) ) } diff --git a/app/src/main/java/com/h/pixeldroid/LoginActivity.kt b/app/src/main/java/com/h/pixeldroid/LoginActivity.kt index 29d71d53..db714126 100644 --- a/app/src/main/java/com/h/pixeldroid/LoginActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/LoginActivity.kt @@ -1,11 +1,13 @@ package com.h.pixeldroid +import android.app.AlertDialog import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Bundle +import android.util.Log import android.view.View import android.view.inputmethod.InputMethodManager import androidx.appcompat.app.AppCompatActivity @@ -13,21 +15,38 @@ import androidx.browser.customtabs.CustomTabsIntent import com.h.pixeldroid.api.PixelfedAPI import com.h.pixeldroid.db.AppDatabase import com.h.pixeldroid.di.PixelfedAPIHolder -import com.h.pixeldroid.objects.Account -import com.h.pixeldroid.objects.Application -import com.h.pixeldroid.objects.Instance -import com.h.pixeldroid.objects.Token +import com.h.pixeldroid.objects.* import com.h.pixeldroid.utils.DBUtils -import com.h.pixeldroid.utils.DBUtils.Companion.storeInstance import com.h.pixeldroid.utils.Utils import com.h.pixeldroid.utils.Utils.Companion.normalizeDomain +import io.reactivex.Single +import io.reactivex.SingleObserver +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.functions.BiFunction +import io.reactivex.functions.Function3 +import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.activity_login.* import okhttp3.HttpUrl import retrofit2.Call import retrofit2.Callback import retrofit2.Response import javax.inject.Inject +/** +Overview of the flow of the login process: (boxes are requests done in parallel, +since they do not depend on each other) + _________________________________ +|[PixelfedAPI.registerApplication]| +|[PixelfedAPI.wellKnownNodeInfo] | + ̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.nodeInfoSchema] + +----> [promptOAuth] + +---->____________________________ + |[PixelfedAPI.instance] | + |[PixelfedAPI.obtainToken] | + ̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.verifyCredentials] + + */ class LoginActivity : AppCompatActivity() { @@ -119,31 +138,85 @@ class LoginActivity : AppCompatActivity() { hideKeyboard() loadingAnimation(true) - apiHolder.setDomain(normalizedDomain).registerApplication( - appName,"$oauthScheme://$PACKAGE_ID", SCOPE - ).enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - if (!response.isSuccessful) { - return failedRegistration() - } - preferences.edit() - .putString("domain", normalizedDomain) - .apply() - val credentials = response.body() as Application - val clientId = credentials.client_id ?: return failedRegistration() - preferences.edit() - .putString("clientID", clientId) - .putString("clientSecret", credentials.client_secret) - .apply() - promptOAuth(normalizedDomain, clientId) - } + pixelfedAPI = apiHolder.setDomain(normalizedDomain) + + Single.zip( + pixelfedAPI.registerApplication( + appName,"$oauthScheme://$PACKAGE_ID", SCOPE + ), + pixelfedAPI.wellKnownNodeInfo(), + BiFunction> { application, nodeInfoJRD -> + // we get here when both results have come in: + Pair(application, nodeInfoJRD) + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver> { + override fun onSuccess(pair: Pair) { + val (credentials, nodeInfoJRD) = pair + val clientId = credentials.client_id ?: return failedRegistration() + preferences.edit() + .putString("domain", normalizedDomain) + .putString("clientID", clientId) + .putString("clientSecret", credentials.client_secret) + .apply() - override fun onFailure(call: Call, t: Throwable) { - return failedRegistration() + //c.f. https://nodeinfo.diaspora.software/protocol.html for more info + val nodeInfoSchemaUrl = nodeInfoJRD.links.firstOrNull { + it.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" + }?.href ?: return failedRegistration(getString(R.string.instance_error)) + + nodeInfoSchema(normalizedDomain, clientId, nodeInfoSchemaUrl) + } + + override fun onError(e: Throwable) { + //Error in any of the two requests will get to this + Log.e("registerAppToServer", e.message.toString()) + failedRegistration() + } + + override fun onSubscribe(d: Disposable) {} + }) + } + + private fun nodeInfoSchema( + normalizedDomain: String, + clientId: String, + nodeInfoSchemaUrl: String + ) { + pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.body() == null || !response.isSuccessful) { + return failedRegistration(getString(R.string.instance_error)) + } + val nodeInfo = response.body() as NodeInfo + + if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) { + val builder = AlertDialog.Builder(this@LoginActivity) + builder.apply { + setMessage(R.string.instance_not_pixelfed_warning) + setPositiveButton(R.string.instance_not_pixelfed_continue) { _, _ -> + promptOAuth(normalizedDomain, clientId) + } + setNegativeButton(R.string.instance_not_pixelfed_cancel) { _, _ -> + loadingAnimation(false) + wipeSharedSettings() + } + } + // Create the AlertDialog + builder.show() + return + } else { + promptOAuth(normalizedDomain, clientId) + } + } + override fun onFailure(call: Call, t: Throwable) { + failedRegistration(getString(R.string.instance_error)) } }) } + private fun promptOAuth(normalizedDomain: String, client_id: String) { val url = "$normalizedDomain/oauth/authorize?" + @@ -178,29 +251,45 @@ class LoginActivity : AppCompatActivity() { } //Successful authorization - val callback = object : Callback { - override fun onResponse(call: Call, response: Response) { - if (!response.isSuccessful || response.body() == null) { - return failedRegistration(getString(R.string.token_error)) - } - authenticationSuccessful(response.body()!!.access_token) - } - - override fun onFailure(call: Call, t: Throwable) { - return failedRegistration(getString(R.string.token_error)) - } - } - pixelfedAPI = apiHolder.setDomain(domain) - pixelfedAPI.obtainToken( - clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code, - "authorization_code" - ).enqueue(callback) - } - private fun authenticationSuccessful(accessToken: String) { - saveUserAndInstance(accessToken) - wipeSharedSettings() + //TODO check why we can't do onErrorReturn { null } which would make more sense ¯\_(ツ)_/¯ + //Also, maybe find a nicer way to do this, this feels hacky (although it can work fine) + val nullInstance = Instance(null, null, null, null, null, null, null, null) + val nullToken = Token(null, null, null, null) + + Single.zip( + pixelfedAPI.instance().onErrorReturn { nullInstance }, + pixelfedAPI.obtainToken( + clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code, + "authorization_code" + ).onErrorReturn { nullToken }, + BiFunction> { instance, token -> + // we get here when all results have come in: + Pair(instance, token) + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : SingleObserver> { + override fun onSuccess(triple: Pair) { + val (instance, token) = triple + if(token == nullToken || token.access_token == null){ + return failedRegistration(getString(R.string.token_error)) + } else if(instance == nullInstance || instance.uri == null){ + return failedRegistration(getString(R.string.instance_error)) + } + + DBUtils.storeInstance(db, instance) + storeUser(token.access_token, instance.uri) + wipeSharedSettings() + } + + override fun onError(e: Throwable) { + Log.e("saveUserAndInstance", e.message.toString()) + failedRegistration(getString(R.string.token_error)) + } + override fun onSubscribe(d: Disposable) {} + }) } private fun failedRegistration(message: String = getString(R.string.registration_failed)) { @@ -225,24 +314,6 @@ class LoginActivity : AppCompatActivity() { } } - private fun saveUserAndInstance(accessToken: String) { - pixelfedAPI.instance().enqueue(object : Callback { - override fun onFailure(call: Call, t: Throwable) { - return failedRegistration(getString(R.string.instance_error)) - } - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful && response.body() != null) { - val instance = response.body() as Instance - storeInstance(db, instance) - storeUser(accessToken, instance.uri) - } else { - return failedRegistration(getString(R.string.instance_error)) - } - } - }) - } - private fun storeUser(accessToken: String, instance: String) { pixelfedAPI.verifyCredentials("Bearer $accessToken") .enqueue(object : Callback { diff --git a/app/src/main/java/com/h/pixeldroid/api/PixelfedAPI.kt b/app/src/main/java/com/h/pixeldroid/api/PixelfedAPI.kt index 78759b8c..151c13e1 100644 --- a/app/src/main/java/com/h/pixeldroid/api/PixelfedAPI.kt +++ b/app/src/main/java/com/h/pixeldroid/api/PixelfedAPI.kt @@ -1,8 +1,8 @@ package com.h.pixeldroid.api -import com.h.pixeldroid.db.AppDatabase import com.h.pixeldroid.objects.* import io.reactivex.Observable +import io.reactivex.Single import okhttp3.MultipartBody import retrofit2.Call import retrofit2.Retrofit @@ -10,9 +10,6 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.* import retrofit2.http.Field -import javax.inject.Inject -import javax.inject.Provider - /* Implements the Pixelfed API @@ -23,7 +20,12 @@ import javax.inject.Provider interface PixelfedAPI { + companion object { + @Deprecated( + "Use the DI-d PixelfedAPIHolder instead", + ReplaceWith("apiHolder.api") + ) fun create(baseUrl: String): PixelfedAPI { return Retrofit.Builder() .baseUrl(baseUrl) @@ -41,7 +43,8 @@ interface PixelfedAPI { @Field("redirect_uris") redirect_uris: String, @Field("scopes") scopes: String? = null, @Field("website") website: String? = null - ): Call + ): Single + @FormUrlEncoded @POST("/oauth/token") @@ -52,7 +55,25 @@ interface PixelfedAPI { @Field("scope") scope: String? = "read", @Field("code") code: String? = null, @Field("grant_type") grant_type: String? = null - ): Call + ): Single + + // get instance configuration + @GET("/api/v1/instance") + fun instance() : Single + + /** + * Instance info from the Nodeinfo .well_known (https://nodeinfo.diaspora.software/protocol.html) endpoint + */ + @GET("/.well-known/nodeinfo") + fun wellKnownNodeInfo() : Single + + /** + * Instance info from [NodeInfo] (https://nodeinfo.diaspora.software/schema.html) endpoint + */ + @GET + fun nodeInfoSchema( + @Url nodeInfo_schema_url: String + ) : Call @FormUrlEncoded @POST("/api/v1/accounts/{id}/follow") @@ -240,10 +261,6 @@ interface PixelfedAPI { @Part file: MultipartBody.Part ): Observable - // get instance configuration - @GET("/api/v1/instance") - fun instance() : Call - // get discover @GET("/api/v2/discover/posts") fun discover( diff --git a/app/src/main/java/com/h/pixeldroid/objects/Instance.kt b/app/src/main/java/com/h/pixeldroid/objects/Instance.kt index 1fbf51e4..3d9094e4 100644 --- a/app/src/main/java/com/h/pixeldroid/objects/Instance.kt +++ b/app/src/main/java/com/h/pixeldroid/objects/Instance.kt @@ -1,14 +1,14 @@ package com.h.pixeldroid.objects data class Instance ( - val description: String, - val email: String, - val max_toot_chars: String = DEFAULT_MAX_TOOT_CHARS.toString(), - val registrations: Boolean, - val thumbnail: String, - val title: String, - val uri: String, - val version: String + val description: String?, + val email: String?, + val max_toot_chars: String? = DEFAULT_MAX_TOOT_CHARS.toString(), + val registrations: Boolean?, + val thumbnail: String?, + val title: String?, + val uri: String?, + val version: String? ) { companion object { const val DEFAULT_MAX_TOOT_CHARS = 500 diff --git a/app/src/main/java/com/h/pixeldroid/objects/NodeInfo.kt b/app/src/main/java/com/h/pixeldroid/objects/NodeInfo.kt new file mode 100644 index 00000000..51fb7567 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/objects/NodeInfo.kt @@ -0,0 +1,69 @@ +package com.h.pixeldroid.objects + +/* + See https://nodeinfo.diaspora.software/schema.html and https://pixelfed.social/api/nodeinfo/2.0.json + A lot of attributes we don't need are omitted, if in the future they are needed we + can make new data classes for them. +*/ + +data class NodeInfo ( + val version: String?, + val software: Software?, + val protocols: List?, + val openRegistrations: Boolean?, + val metadata: PixelfedMetadata? +){ + data class Software( + val name: String?, + val version: String? + ) + data class PixelfedMetadata( + val nodeName: String?, + val software: Software?, + val config: PixelfedConfig + ){ + data class Software( + val homepage: String?, + val repo: String? + ) + } + data class PixelfedConfig( + val open_registration: Boolean?, + val uploader: Uploader?, + val activitypub: ActivityPub?, + val features: Features? + ){ + data class Uploader( + val max_photo_size: String?, + val max_caption_length: String?, + val album_limit: String?, + val image_quality: String?, + val optimize_image: Boolean?, + val optimize_video: Boolean?, + val media_types: String?, + val enforce_account_limit: Boolean? + ) + + data class ActivityPub( + val enabled: Boolean?, + val remote_follow: Boolean? + ) + + data class Features( + val mobile_apis: Boolean?, + val circles: Boolean?, + val stories: Boolean?, + val video: Boolean? + ) + } +} + +data class NodeInfoJRD( + val links: List +){ + data class Link( + val rel: String?, + val href: String? + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/objects/Token.kt b/app/src/main/java/com/h/pixeldroid/objects/Token.kt index 79248e19..e22e170d 100644 --- a/app/src/main/java/com/h/pixeldroid/objects/Token.kt +++ b/app/src/main/java/com/h/pixeldroid/objects/Token.kt @@ -1,8 +1,8 @@ package com.h.pixeldroid.objects data class Token( - val access_token: String, - val token_type: String, - val scope: String, - val created_at: Int + val access_token: String?, + val token_type: String?, + val scope: String?, + val created_at: Int? ) \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/utils/DBUtils.kt b/app/src/main/java/com/h/pixeldroid/utils/DBUtils.kt index 3d31b6cf..115a615e 100644 --- a/app/src/main/java/com/h/pixeldroid/utils/DBUtils.kt +++ b/app/src/main/java/com/h/pixeldroid/utils/DBUtils.kt @@ -37,13 +37,13 @@ class DBUtils { } fun storeInstance(db: AppDatabase, instance: Instance) { - val maxTootChars = instance.max_toot_chars.toInt() + val maxTootChars = instance.max_toot_chars?.toInt() ?: Instance.DEFAULT_MAX_TOOT_CHARS val dbInstance = InstanceDatabaseEntity( //make sure not to normalize to https when localhost, to allow testing - uri = normalizeOrNot(instance.uri), - title = instance.title, + uri = normalizeOrNot(instance.uri.orEmpty()), + title = instance.title.orEmpty(), max_toot_chars = maxTootChars, - thumbnail = instance.thumbnail + thumbnail = instance.thumbnail.orEmpty() ) db.instanceDao().insertInstance(dbInstance) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88badefa..f3db9f76 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,9 @@ "Could not authenticate" "Error getting token" "Could not get instance information" + "This doesn't seem to be a Pixelfed instance, so the app could break in unexpected ways." + "OK, continue anyway" + "Cancel logging in" Settings Application Theme diff --git a/app/src/test/java/com/h/pixeldroid/APIUnitTest.kt b/app/src/test/java/com/h/pixeldroid/APIUnitTest.kt index 546b2971..99247c67 100644 --- a/app/src/test/java/com/h/pixeldroid/APIUnitTest.kt +++ b/app/src/test/java/com/h/pixeldroid/APIUnitTest.kt @@ -4,6 +4,8 @@ import com.github.tomakehurst.wiremock.client.WireMock.* import com.github.tomakehurst.wiremock.junit.WireMockRule import com.h.pixeldroid.api.PixelfedAPI import com.h.pixeldroid.objects.* +import io.reactivex.Single +import okhttp3.internal.wait import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test @@ -106,9 +108,10 @@ class APIUnitTest { .withHeader("Content-Type", "application/json") .withBody(""" {"id":3197,"name":"Pixeldroid","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":3197,"client_secret":"hhRwLupqUJPghKsZzpZtxNV67g5DBdPYCqW6XE3m","vapid_key":null}""" ))) - val call: Call = PixelfedAPI.create("http://localhost:8089") + val call: Single = PixelfedAPI.create("http://localhost:8089") .registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow") - val application: Application = call.execute().body()!! + + val application: Application = call.toFuture().get() assertEquals("3197", application.client_id) assertEquals("hhRwLupqUJPghKsZzpZtxNV67g5DBdPYCqW6XE3m", application.client_secret) assertEquals("Pixeldroid", application.name) @@ -133,10 +136,10 @@ class APIUnitTest { val OAUTH_SCHEME = "oauth2redirect" val SCOPE = "read write follow" val PACKAGE_ID = "com.h.pixeldroid" - val call: Call = PixelfedAPI.create("http://localhost:8089") + val call: Single = PixelfedAPI.create("http://localhost:8089") .obtainToken("123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc", "authorization_code") - val token: Token = call.execute().body()!! + val token: Token = call.toFuture().get() assertEquals("ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", token.access_token) assertEquals("Bearer", token.token_type) assertEquals("read write follow push", token.scope) diff --git a/build.gradle b/build.gradle index 6bfa9a5b..43aeea70 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.4.0' repositories { google() jcenter() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f6b961fd..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 363ef62c..b4f9e8a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Jul 26 16:18:03 CEST 2020 +#Fri Aug 21 12:53:39 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip diff --git a/gradlew b/gradlew index 9a27a6ce..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -10,38 +26,38 @@ # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. -while [ -h "$PRG" ]; do - ls=$(ls -ld "$PRG") - link=$(expr "$ls" : '.*-> \(.*\)$') - if expr "$link" : '/.*' >/dev/null; then - PRG="$link" - else - PRG=$(dirname "$PRG")"/$link" - fi +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi done -SAVED="$(pwd)" -cd "$(dirname \"$PRG\")/" >/dev/null -APP_HOME="$(pwd -P)" +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" -APP_BASE_NAME=$(basename "$0") +APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn() { - echo "$*" +warn () { + echo "$*" } -die() { - echo - echo "$*" - echo - exit 1 +die () { + echo + echo "$*" + echo + exit 1 } # OS specific support (must be 'true' or 'false'). @@ -49,124 +65,121 @@ cygwin=false msys=false darwin=false nonstop=false -case "$(uname)" in -CYGWIN*) - cygwin=true - ;; -Darwin*) - darwin=true - ;; -MINGW*) - msys=true - ;; -NONSTOP*) - nonstop=true - ;; +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ]; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." - fi + fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then - MAX_FD_LIMIT=$(ulimit -H -n) - if [ $? -eq 0 ]; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then - MAX_FD="$MAX_FD_LIMIT" +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi - ulimit -n $MAX_FD - if [ $? -ne 0 ]; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - APP_HOME=$(cygpath --path --mixed "$APP_HOME") - CLASSPATH=$(cygpath --path --mixed "$CLASSPATH") - JAVACMD=$(cygpath --unix "$JAVACMD") +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null) - SEP="" - for dir in $ROOTDIRSRAW; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ]; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@"; do - CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -) - CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option + JAVACMD=`cygpath --unix "$JAVACMD"` - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition - eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg") - else - eval $(echo args$i)="\"$arg\"" + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi - i=$((i + 1)) - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac fi # Escape application args -save() { - for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done - echo " " +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell