Commit 7bca413d authored by Matthieu's avatar Matthieu

Use nodeinfo endpoint to get info about the capabilities of the instance

parent bd39bc68
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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 <T> first(matcher: Matcher<T>): Matcher<T>? {
......@@ -50,6 +54,24 @@ abstract class CustomMatchers {
}
}
fun atPosition(position: Int, itemMatcher: Matcher<View?>): Matcher<View?>? {
return object : BoundedMatcher<View?, RecyclerView>(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
)
)
}
......
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<Application>
): Single<Application>
@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<Token>
): Single<Token>
// get instance configuration
@GET("/api/v1/instance")
fun instance() : Single<Instance>
/**
* Instance info from the Nodeinfo .well_known (https://nodeinfo.diaspora.software/protocol.html) endpoint
*/
@GET("/.well-known/nodeinfo")
fun wellKnownNodeInfo() : Single<NodeInfoJRD>
/**
* Instance info from [NodeInfo] (https://nodeinfo.diaspora.software/schema.html) endpoint
*/
@GET
fun nodeInfoSchema(
@Url nodeInfo_schema_url: String
) : Call<NodeInfo>
@FormUrlEncoded
@POST("/api/v1/accounts/{id}/follow")
......@@ -240,10 +261,6 @@ interface PixelfedAPI {
@Part file: MultipartBody.Part
): Observable<Attachment>
// get instance configuration
@GET("/api/v1/instance")
fun instance() : Call<Instance>
// get discover
@GET("/api/v2/discover/posts")
fun discover(
......
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
......
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<String>?,
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<Link>
){
data class Link(
val rel: String?,
val href: String?
)
}
\ No newline at end of file
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
......@@ -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)
}
......
......@@ -9,6 +9,9 @@
<string name="auth_failed">"Could not authenticate"</string>
<string name="token_error">"Error getting token"</string>
<string name="instance_error">"Could not get instance information"</string>
<string name="instance_not_pixelfed_warning">"This doesn't seem to be a Pixelfed instance, so the app could break in unexpected ways."</string>
<string name="instance_not_pixelfed_continue">"OK, continue anyway"</string>
<string name="instance_not_pixelfed_cancel">"Cancel logging in"</string>
<string name="title_activity_settings2">Settings</string>
<!-- Theme Preferences -->
<string name="theme_title">Application Theme</string>
......
......@@ -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<Application> = PixelfedAPI.create("http://localhost:8089")
val call: Single<Application> = 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<Token> = PixelfedAPI.create("http://localhost:8089")
val call: Single<Token> = 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)
......
// 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()
......
#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
#!/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"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ]; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
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
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")
# 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