Merge branch 'master' into profile_feed
This commit is contained in:
commit
10bf68978d
@ -1,8 +1,8 @@
|
||||
image: reactivecircus/android-emulator-26:latest
|
||||
image: reactivecircus/android-emulator-23:latest
|
||||
|
||||
|
||||
variables:
|
||||
API_LEVEL: "26"
|
||||
API_LEVEL: "23"
|
||||
ARCH: "x86"
|
||||
TARGET: "default"
|
||||
|
||||
@ -34,25 +34,24 @@ debugTests:
|
||||
- ./gradlew -Pci --console=plain :app:testDebug
|
||||
|
||||
|
||||
#emulatorTest:
|
||||
# interruptible: true
|
||||
# stage: test
|
||||
# script:
|
||||
# - sdkmanager --sdk_root=${ANDROID_HOME} "system-images;android-${API_LEVEL};${TARGET};${ARCH}"
|
||||
# - echo no | avdmanager create avd --force --name "api-${API_LEVEL}" --abi "${TARGET}/${ARCH}" --package "system-images;android-${API_LEVEL};${TARGET};${ARCH}"
|
||||
# - $ANDROID_HOME/emulator/emulator -avd "api-${API_LEVEL}" -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none &
|
||||
# - chmod +x android-wait-for-emulator.sh
|
||||
# - ./gradlew build
|
||||
# - ./android-wait-for-emulator.sh
|
||||
# - adb shell settings put global window_animation_scale 0.0
|
||||
# - adb shell settings put global transition_animation_scale 0.0
|
||||
# - adb shell settings put global animator_duration_scale 0.0
|
||||
emulatorTest:
|
||||
interruptible: true
|
||||
stage: test
|
||||
script:
|
||||
- echo no | avdmanager create avd --force --name "api-${API_LEVEL}" --abi "${TARGET}/${ARCH}" --package "system-images;android-${API_LEVEL};${TARGET};${ARCH}"
|
||||
- $ANDROID_SDK_ROOT/emulator/emulator -avd "api-${API_LEVEL}" -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none &
|
||||
- chmod +x android-wait-for-emulator.sh
|
||||
- ./gradlew build
|
||||
- ./android-wait-for-emulator.sh
|
||||
- adb shell settings put global window_animation_scale 0.0
|
||||
- adb shell settings put global transition_animation_scale 0.0
|
||||
- adb shell settings put global animator_duration_scale 0.0
|
||||
|
||||
# - ./gradlew build connectedCheck connectedDebugAndroidTest jacocoTestReport
|
||||
- ./gradlew build connectedCheck connectedStagingAndroidTest jacocoTestReport
|
||||
|
||||
# - cat app/build/reports/jacoco/jacocoTestReport/html/index.html | grep -o 'Total[^%]*%'
|
||||
- cat app/build/reports/jacoco/jacocoTestReport/html/index.html | grep -o 'Total[^%]*%'
|
||||
|
||||
# artifacts:
|
||||
# paths:
|
||||
# - ./app/build/reports/jacoco/jacocoTestReport/
|
||||
# expire_in: 1 week
|
||||
artifacts:
|
||||
paths:
|
||||
- ./app/build/reports/jacoco/jacocoTestReport/
|
||||
expire_in: 1 week
|
||||
|
@ -24,8 +24,8 @@ android {
|
||||
applicationId "com.h.pixeldroid"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
versionCode 9
|
||||
versionName "1.0.alpha8"
|
||||
versionCode 10
|
||||
versionName "1.0.alpha9"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
@ -38,16 +38,40 @@ android {
|
||||
test.java.srcDirs += 'src/test/java'
|
||||
androidTest.java.srcDirs += 'src/androidTest/java'
|
||||
}
|
||||
testBuildType "staging"
|
||||
|
||||
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
|
||||
}
|
||||
staging {
|
||||
initWith debug
|
||||
testCoverageEnabled true
|
||||
|
||||
// These values are first looked for in the env variables, and, if not found there,
|
||||
// in the local.properties file (which is not checked into version control of course!).
|
||||
|
||||
// If you are not running the integration tests, you can just set them to dummy values
|
||||
// in local.properties or comment the buildConfigField lines
|
||||
|
||||
def localProperties = new Properties()
|
||||
if (rootProject.file("local.properties").exists()) {
|
||||
localProperties.load(new FileInputStream(rootProject.file("local.properties")))
|
||||
}
|
||||
|
||||
buildConfigField "String", "USER_ID", System.getenv("USER_ID") ?: localProperties['USER_ID']
|
||||
buildConfigField "String", "INSTANCE_URI", System.getenv("INSTANCE_URI") ?: localProperties['INSTANCE_URI']
|
||||
buildConfigField "String", "ACCESS_TOKEN", System.getenv("ACCESS_TOKEN") ?: localProperties['ACCESS_TOKEN']
|
||||
buildConfigField "String", "REFRESH_TOKEN", System.getenv("REFRESH_TOKEN") ?: localProperties['REFRESH_TOKEN']
|
||||
buildConfigField "String", "CLIENT_ID", System.getenv("CLIENT_ID") ?: localProperties['CLIENT_ID']
|
||||
buildConfigField "String", "CLIENT_SECRET", System.getenv("CLIENT_SECRET") ?: localProperties['CLIENT_SECRET']
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
testOptions {
|
||||
@ -64,7 +88,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
/**
|
||||
* AndroidX dependencies:
|
||||
@ -73,32 +96,32 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4'
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.2'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-alpha12'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.4'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.4'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.0.0-beta02'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.0"
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
|
||||
// Use the most recent version of CameraX
|
||||
def cameraX_version = '1.0.0-rc01'
|
||||
def cameraX_version = '1.0.0-rc03'
|
||||
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-alpha20'
|
||||
implementation 'androidx.camera:camera-view:1.0.0-alpha22'
|
||||
|
||||
def room_version = "2.3.0-alpha04"
|
||||
def room_version = "2.3.0-beta03"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
@ -109,7 +132,7 @@ dependencies {
|
||||
*/
|
||||
|
||||
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
|
||||
//Dagger (dependency injection)
|
||||
implementation 'com.google.dagger:dagger-android:2.30.1'
|
||||
@ -124,7 +147,7 @@ dependencies {
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.20'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation 'com.github.connyduck:sparkbutton:4.0.0'
|
||||
implementation 'com.github.connyduck:sparkbutton:4.1.0'
|
||||
|
||||
|
||||
implementation 'info.androidhive:imagefilters:1.0.7'
|
||||
@ -166,7 +189,8 @@ dependencies {
|
||||
*/
|
||||
|
||||
// debugImplementation required vs testImplementation: https://issuetracker.google.com/issues/128612536
|
||||
debugImplementation("androidx.fragment:fragment-testing:1.2.5") {
|
||||
//noinspection FragmentGradleConfiguration
|
||||
stagingImplementation("androidx.fragment:fragment-testing:1.3.1") {
|
||||
exclude group:'androidx.test', module:'monitor'
|
||||
}
|
||||
|
||||
@ -195,7 +219,7 @@ tasks.withType(Test) {
|
||||
}
|
||||
|
||||
|
||||
task jacocoTestReport(type: JacocoReport, dependsOn: ['connectedDebugAndroidTest', 'testDebugUnitTest', 'createDebugCoverageReport']) {
|
||||
task jacocoTestReport(type: JacocoReport, dependsOn: ['connectedStagingAndroidTest', 'testStagingUnitTest', 'createStagingCoverageReport']) {
|
||||
|
||||
reports {
|
||||
xml.enabled = true
|
||||
@ -209,9 +233,9 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['connectedDebugAndroidTest
|
||||
getClassDirectories().from(files([kotlinDebugTree]))
|
||||
getExecutionData().from(fileTree(dir: project.buildDir, includes: [
|
||||
|
||||
'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec',
|
||||
'outputs/code_coverage/stagingAndroidTest/connected/*coverage.ec',
|
||||
|
||||
'jacoco/testDebugUnitTest.exec'
|
||||
'jacoco/testStagingUnitTest.exec'
|
||||
|
||||
]))
|
||||
}
|
@ -765,3 +765,10 @@
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://github.com/ongakuer/CircleIndicator
|
||||
- artifact: androidx.dynamicanimation:dynamicanimation:+
|
||||
name: dynamicanimation
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: http://developer.android.com/tools/extras/support-library.html
|
||||
|
||||
|
83
app/proguard-rules.pro
vendored
83
app/proguard-rules.pro
vendored
@ -1,21 +1,68 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
# proguard file largely copied from Tusky's
|
||||
# GENERAL OPTIONS (inspired from AOSP's proguard-android-optimize.txt)
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# turn on all optimizations except those that are known to cause problems on Android
|
||||
-optimizations !code/simplification/cast,!field/*,!class/merging/*
|
||||
-optimizationpasses 6
|
||||
-allowaccessmodification
|
||||
-dontpreverify
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
-dontusemixedcaseclassnames
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
# keep setters in Views so that animations can still work.
|
||||
# see http://proguard.sourceforge.net/manual/examples.html#beans
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# APP SPECIFIC OPTIONS
|
||||
|
||||
# keep members of our model classes, they are used in json de/serialization
|
||||
-keepclassmembers class com.h.pixeldroid.utils.api.objects.* { *; }
|
||||
|
||||
-keep public enum com.h.pixeldroid.utils.api.objects.*$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
# preserve line numbers for crash reporting
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute SourceFile
|
||||
|
||||
# remove all logging from production apk
|
||||
-assumenosideeffects class android.util.Log {
|
||||
public static *** getStackTraceString(...);
|
||||
public static *** d(...);
|
||||
public static *** w(...);
|
||||
public static *** v(...);
|
||||
public static *** i(...);
|
||||
}
|
||||
-assumenosideeffects class java.lang.String {
|
||||
public static java.lang.String format(...);
|
||||
}
|
||||
|
||||
# remove some kotlin overhead
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
||||
static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
|
||||
static void throwUninitializedPropertyAccessException(java.lang.String);
|
||||
}
|
@ -4,6 +4,7 @@ import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_CHOOSER
|
||||
import android.widget.ImageButton
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.intent.Intents
|
||||
@ -14,7 +15,6 @@ import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.postCreation.camera.CameraFragment
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import kotlinx.android.synthetic.main.camera_ui_container.*
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.After
|
||||
@ -50,9 +50,9 @@ class CameraTest {
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
refreshToken = "refreshToken",
|
||||
clientId = "clientId",
|
||||
clientSecret = "clientSecret"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
@ -88,23 +88,23 @@ class CameraTest {
|
||||
|
||||
val scenario = launchFragmentInContainer<CameraFragment>()
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.photo_view_button.performClick()
|
||||
fragment.view?.findViewById<ImageButton>(R.id.photo_view_button)?.performClick()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
|
||||
Intents.intended(expectedIntent)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchButton() {
|
||||
val scenario = launchFragmentInContainer<CameraFragment>()
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.camera_switch_button.performClick()
|
||||
fragment.view?.findViewById<ImageButton>(R.id.camera_switch_button)?.performClick()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
|
||||
//FIXME this assert doesn't actually do anything...
|
||||
// All this test really does is make sure it doesn't crash
|
||||
scenario.onFragment { fragment ->
|
||||
assert(!fragment.isHidden)
|
||||
}
|
||||
|
@ -12,12 +12,8 @@ import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -28,7 +24,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DrawerMenuTest {
|
||||
|
||||
private lateinit var mockServer: MockServer
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@ -38,33 +33,15 @@ class DrawerMenuTest {
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
testiTesto
|
||||
)
|
||||
db.close()
|
||||
|
||||
@ -78,7 +55,6 @@ class DrawerMenuTest {
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -132,10 +108,11 @@ class DrawerMenuTest {
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
// Check that profile activity was opened.
|
||||
onView(withId(R.id.editButton)).check(matches(isDisplayed()))
|
||||
val followersText = context.getString(R.string.nb_followers)
|
||||
.format(68)
|
||||
val followersText = context.resources.getQuantityString(R.plurals.nb_followers, 2, 2)
|
||||
onView(withText(followersText)).perform(click())
|
||||
onView(withText("Dobios")).check(matches(isDisplayed()))
|
||||
|
||||
waitForView(R.id.account_entry_avatar)
|
||||
onView(withText("PixelDroid Developer")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -144,10 +121,11 @@ class DrawerMenuTest {
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
// Check that profile activity was opened.
|
||||
onView(withId(R.id.editButton)).check(matches(isDisplayed()))
|
||||
val followingText = context.getString(R.string.nb_following)
|
||||
.format(27)
|
||||
val followingText = context.resources.getQuantityString(R.plurals.nb_following, 3, 3)
|
||||
onView(withText(followingText)).perform(click())
|
||||
onView(withText("Dobios")).check(matches(isDisplayed()))
|
||||
|
||||
waitForView(R.id.account_entry_avatar)
|
||||
onView(withText("@User 1")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
/*@Test
|
||||
@ -162,31 +140,38 @@ class DrawerMenuTest {
|
||||
fun clickFollowers() {
|
||||
// Open My Profile from drawer
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
waitForView(R.id.nbFollowersTextView)
|
||||
|
||||
// Open followers list
|
||||
onView(withId(R.id.nbFollowersTextView)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
// Open follower's profile
|
||||
onView(withText("ete2")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("Christian")))
|
||||
waitForView(R.id.account_entry_avatar)
|
||||
|
||||
// Open follower's profile
|
||||
onView(withText("@pixeldroid")).perform(click())
|
||||
|
||||
waitForView(R.id.profilePictureImageView)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("PixelDroid Developer")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickFollowing() {
|
||||
// Open My Profile from drawer
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.nbFollowersTextView)
|
||||
|
||||
// Open followers list
|
||||
onView(withId(R.id.nbFollowingTextView)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
// Open following's profile
|
||||
onView(withText("Dobios")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("Andrew Dobis")))
|
||||
waitForView(R.id.account_entry_avatar)
|
||||
|
||||
// Open following's profile
|
||||
onView(withText("@user2")).perform(click())
|
||||
waitForView(R.id.profilePictureImageView)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("User 2")))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -22,14 +22,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.adapters.ThumbnailAdapter
|
||||
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
|
||||
import com.h.pixeldroid.postCreation.photoEdit.ThumbnailAdapter
|
||||
import com.h.pixeldroid.settings.AboutActivity
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import junit.framework.Assert.assertTrue
|
||||
import kotlinx.android.synthetic.main.fragment_edit_image.*
|
||||
import com.h.pixeldroid.testUtility.clickChildViewWithId
|
||||
import com.h.pixeldroid.testUtility.slowSwipeLeft
|
||||
import com.h.pixeldroid.testUtility.waitForView
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.junit.*
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
@ -72,7 +74,7 @@ class EditPhotoTest {
|
||||
|
||||
activityScenario = ActivityScenario.launch<PhotoEditActivity>(intent).onActivity{a -> activity = a}
|
||||
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.coordinator_edit)
|
||||
}
|
||||
|
||||
@After
|
||||
@ -119,14 +121,15 @@ class EditPhotoTest {
|
||||
|
||||
@Test
|
||||
fun FiltersIsSwipeableAndClickeable() {
|
||||
waitForView(R.id.thumbnail)
|
||||
Espresso.onView(withId(R.id.recycler_view))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(1, CustomMatchers.clickChildViewWithId(R.id.thumbnail)))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(1, clickChildViewWithId(R.id.thumbnail)))
|
||||
Thread.sleep(1000)
|
||||
Espresso.onView(withId(R.id.recycler_view))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(1, CustomMatchers.slowSwipeLeft(false)))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(1, slowSwipeLeft(false)))
|
||||
Thread.sleep(1000)
|
||||
Espresso.onView(withId(R.id.recycler_view))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(5, CustomMatchers.clickChildViewWithId(R.id.thumbnail)))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(5, clickChildViewWithId(R.id.thumbnail)))
|
||||
Espresso.onView(withId(R.id.image_preview)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@ -134,16 +137,16 @@ class EditPhotoTest {
|
||||
fun BrightnessSaturationContrastTest() {
|
||||
Espresso.onView(withId(R.id.tabs)).perform(selectTabAtPosition(1))
|
||||
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.seekbar_brightness)
|
||||
|
||||
var change = 5
|
||||
Espresso.onView(withId(R.id.seekbar_brightness)).perform(setProgress(change))
|
||||
Espresso.onView(withId(R.id.seekbar_contrast)).perform(setProgress(change))
|
||||
Espresso.onView(withId(R.id.seekbar_saturation)).perform(setProgress(change))
|
||||
|
||||
Assert.assertEquals(change, activity.seekbar_brightness.progress)
|
||||
Assert.assertEquals(change, activity.seekbar_contrast.progress)
|
||||
Assert.assertEquals(change, activity.seekbar_saturation.progress)
|
||||
Assert.assertEquals(change, activity.findViewById<SeekBar>(R.id.seekbar_brightness).progress)
|
||||
Assert.assertEquals(change, activity.findViewById<SeekBar>(R.id.seekbar_contrast).progress)
|
||||
Assert.assertEquals(change, activity.findViewById<SeekBar>(R.id.seekbar_saturation).progress)
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
@ -152,28 +155,32 @@ class EditPhotoTest {
|
||||
Espresso.onView(withId(R.id.seekbar_contrast)).perform(setProgress(change))
|
||||
Espresso.onView(withId(R.id.seekbar_saturation)).perform(setProgress(change))
|
||||
|
||||
Assert.assertEquals(change, activity.seekbar_brightness.progress)
|
||||
Assert.assertEquals(change, activity.seekbar_contrast.progress)
|
||||
Assert.assertEquals(change, activity.seekbar_saturation.progress)
|
||||
Assert.assertEquals(change, activity.findViewById<SeekBar>(R.id.seekbar_brightness).progress)
|
||||
Assert.assertEquals(change, activity.findViewById<SeekBar>(R.id.seekbar_contrast).progress)
|
||||
Assert.assertEquals(change, activity.findViewById<SeekBar>(R.id.seekbar_saturation).progress)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveButton() {
|
||||
// The save button saves the edits and goes back to the post creation activity.
|
||||
Espresso.onView(withId(R.id.action_save)).perform(click())
|
||||
Espresso.onView(withId(com.google.android.material.R.id.snackbar_text))
|
||||
.check(matches(withText(R.string.save_image_success)))
|
||||
Thread.sleep(1000)
|
||||
assertTrue(activityScenario.state == Lifecycle.State.DESTROYED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun backButton() {
|
||||
Espresso.onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
assertTrue(activityScenario.state == Lifecycle.State.DESTROYED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun croppingIsPossible() {
|
||||
Espresso.onView(withId(R.id.cropImageButton)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
waitForView(R.id.menu_crop)
|
||||
|
||||
Espresso.onView(withId(R.id.menu_crop)).perform(click())
|
||||
Espresso.onView(withId(R.id.image_preview)).check(matches(isDisplayed()))
|
||||
}
|
||||
@ -181,7 +188,7 @@ class EditPhotoTest {
|
||||
@Test
|
||||
fun alreadyUploadingDialog() {
|
||||
activityScenario.onActivity { a -> a.saving = true }
|
||||
Espresso.onView(withId(R.id.action_upload)).perform(click())
|
||||
Espresso.onView(withId(R.id.action_save)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
Espresso.onView(withText(R.string.busy_dialog_text)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
@ -10,21 +10,11 @@ import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.posts.StatusViewHolder
|
||||
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
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.second
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.slowSwipeUp
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.typeTextInViewWithId
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -37,7 +27,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HomeFeedTest {
|
||||
|
||||
private lateinit var mockServer: MockServer
|
||||
private lateinit var activityScenario: ActivityScenario<MainActivity>
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
@ -47,31 +36,14 @@ class HomeFeedTest {
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
testiTesto
|
||||
)
|
||||
db.close()
|
||||
activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
@ -79,21 +51,59 @@ class HomeFeedTest {
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickingTabOnAlbumShowsNextPhoto() {
|
||||
//Wait for the feed to load
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.postPager)
|
||||
|
||||
activityScenario.onActivity {
|
||||
a -> run {
|
||||
//Pick the second photo
|
||||
a.findViewById<TabLayout>(R.id.postTabs).getTabAt(1)?.select()
|
||||
a.findViewById<ViewPager2>(R.id.postPager).currentItem = 2
|
||||
}
|
||||
}
|
||||
onView(first(withId(R.id.postTabs))).check(matches(isDisplayed()))
|
||||
onView(first(withId(R.id.postPager))).check(matches(isDisplayed()))
|
||||
}
|
||||
/*
|
||||
@Test
|
||||
fun clickingReblogButtonWorks() {
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.reblogger)))
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.reblogger)))
|
||||
onView(first(withId(R.id.nshares)))
|
||||
.check(matches(withText(getText(first(withId(R.id.nshares))))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleTapLikerWorks() {
|
||||
Thread.sleep(1000)
|
||||
//Get initial like count
|
||||
val likes = getText(first(withId(R.id.nlikes)))
|
||||
val nLikes = likes!!.split(" ")[0].toInt()
|
||||
|
||||
//Remove sensitive media warning
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(100)
|
||||
|
||||
//Like the post
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.postPicture)))
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder >
|
||||
(0, clickChildViewWithId(R.id.postPicture)))
|
||||
//...
|
||||
Thread.sleep(100)
|
||||
|
||||
//Profit
|
||||
onView(first(withId(R.id.nlikes))).check(matches((withText("${nLikes + 1} Likes"))))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -114,10 +124,12 @@ class HomeFeedTest {
|
||||
actionOnItemAtPosition<StatusViewHolder>(2, clickChildViewWithId(R.id.liker))
|
||||
)
|
||||
onView((withId(R.id.list))).check(matches(isDisplayed()))
|
||||
}
|
||||
}*/
|
||||
|
||||
@Test
|
||||
fun clickingUsernameOpensProfile() {
|
||||
waitForView(R.id.username)
|
||||
|
||||
onView(withId(R.id.list)).perform(
|
||||
actionOnItemAtPosition<StatusViewHolder>(0, clickChildViewWithId(R.id.username))
|
||||
)
|
||||
@ -126,26 +138,18 @@ class HomeFeedTest {
|
||||
|
||||
@Test
|
||||
fun clickingProfilePicOpensProfile() {
|
||||
waitForView(R.id.profilePic)
|
||||
|
||||
onView(withId(R.id.list)).perform(
|
||||
actionOnItemAtPosition<StatusViewHolder>(0, clickChildViewWithId(R.id.profilePic))
|
||||
)
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickingReblogButtonWorks() {
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.reblogger)))
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.reblogger)))
|
||||
onView(first(withId(R.id.nshares)))
|
||||
.check(matches(withText(getText(first(withId(R.id.nshares))))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickingMentionOpensProfile() {
|
||||
waitForView(R.id.description)
|
||||
|
||||
onView(withId(R.id.list)).perform(
|
||||
actionOnItemAtPosition<StatusViewHolder>(0, clickChildViewWithId(R.id.description))
|
||||
)
|
||||
@ -159,7 +163,7 @@ class HomeFeedTest {
|
||||
)
|
||||
onView(withId(R.id.list)).check(matches(isDisplayed()))
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@Test
|
||||
fun clickingCommentButtonOpensCommentSection() {
|
||||
@ -211,20 +215,17 @@ class HomeFeedTest {
|
||||
Thread.sleep(1000)
|
||||
onView(first(withId(R.id.commentContainer)))
|
||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||
}
|
||||
}*/
|
||||
|
||||
@Test
|
||||
fun performClickOnSensitiveWarning() {
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(1))
|
||||
Thread.sleep(1000)
|
||||
|
||||
waitForView(R.id.username)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(1, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.check(matches(atPosition(1, not(withId(R.id.sensitiveWarning)))))
|
||||
@ -232,45 +233,19 @@ class HomeFeedTest {
|
||||
|
||||
@Test
|
||||
fun performClickOnSensitiveWarningTabs() {
|
||||
waitForView(R.id.username)
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(0))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun doubleTapLikerWorks() {
|
||||
//Get initial like count
|
||||
val likes = getText(first(withId(R.id.nlikes)))
|
||||
val nlikes = likes!!.split(" ")[0].toInt()
|
||||
|
||||
//Remove sensitive media warning
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(100)
|
||||
|
||||
//Like the post
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.postPicture)))
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder >
|
||||
(0, clickChildViewWithId(R.id.postPicture)))
|
||||
//...
|
||||
Thread.sleep(100)
|
||||
|
||||
//Profit
|
||||
onView(first(withId(R.id.nlikes))).check(matches((withText("${nlikes + 1} Likes"))))
|
||||
}
|
||||
/*
|
||||
@Test
|
||||
fun goOfflineShowsPosts() {
|
||||
|
@ -30,9 +30,7 @@ import com.h.pixeldroid.posts.StatusViewHolder
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_TAG
|
||||
import com.h.pixeldroid.settings.AboutActivity
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers
|
||||
@ -47,7 +45,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class IntentTest {
|
||||
|
||||
private lateinit var mockServer: MockServer
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@ -62,34 +59,14 @@ class IntentTest {
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.userDao().insertUser(testiTesto)
|
||||
db.close()
|
||||
|
||||
Intents.init()
|
||||
@ -100,81 +77,32 @@ class IntentTest {
|
||||
fun clickingMentionOpensProfile() {
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
|
||||
val account = Account("1450", "deerbard_photo", "deerbard_photo",
|
||||
"https://pixelfed.social/deerbard_photo", "deerbard photography",
|
||||
val account = Account("265626292148375552", "user2", "user2",
|
||||
"https://testing2.pixeldroid.org/user2", "User 2",
|
||||
"",
|
||||
"https://pixelfed.social/storage/avatars/000/000/001/450/SMSep5NoabDam1W8UDMh_avatar.png?v=4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a",
|
||||
"https://pixelfed.social/storage/avatars/000/000/001/450/SMSep5NoabDam1W8UDMh_avatar.png?v=4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a",
|
||||
"https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0",
|
||||
"https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0",
|
||||
"", "", false, emptyList(), null,
|
||||
"2018-08-01T12:58:21.000000Z", 72, 68, 27,
|
||||
"2021-02-11T23:44:03.000000Z", 0, 1, 1,
|
||||
null, null, false, null)
|
||||
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
|
||||
IntentMatchers.hasExtra(ACCOUNT_TAG, account)
|
||||
)
|
||||
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.description)
|
||||
|
||||
//Click the mention
|
||||
Espresso.onView(ViewMatchers.withId(R.id.list))
|
||||
.perform(RecyclerViewActions.actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickClickableSpanInDescription("@Dobios")))
|
||||
(0, clickClickableSpanInDescription("@user2")))
|
||||
|
||||
//Wait a bit
|
||||
Thread.sleep(1000)
|
||||
|
||||
//Check that the Profile is shown
|
||||
//Check that the right intent was launched
|
||||
intended(expectedIntent)
|
||||
}
|
||||
|
||||
private fun clickClickableSpanInDescription(textToClick: CharSequence): ViewAction {
|
||||
return object : ViewAction {
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return Matchers.instanceOf(TextView::class.java)
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "clicking on a ClickableSpan"
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val textView = view.findViewById<View>(R.id.description) as TextView
|
||||
val spannableString = textView.text as SpannableString
|
||||
|
||||
if (spannableString.isEmpty()) {
|
||||
// TextView is empty, nothing to do
|
||||
throw NoMatchingViewException.Builder()
|
||||
.includeViewHierarchy(true)
|
||||
.withRootView(textView)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Get the links inside the TextView and check if we find textToClick
|
||||
val spans = spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
|
||||
if (spans.isNotEmpty()) {
|
||||
var spanCandidate: ClickableSpan
|
||||
for (span: ClickableSpan in spans) {
|
||||
spanCandidate = span
|
||||
val start = spannableString.getSpanStart(spanCandidate)
|
||||
val end = spannableString.getSpanEnd(spanCandidate)
|
||||
val sequence = spannableString.subSequence(start, end)
|
||||
if (textToClick.toString() == sequence.toString()) {
|
||||
span.onClick(textView)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// textToClick not found in TextView
|
||||
throw NoMatchingViewException.Builder()
|
||||
.includeViewHierarchy(true)
|
||||
.withRootView(textView)
|
||||
.build()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun clickEditProfileMakesIntent() {
|
||||
@ -202,6 +130,5 @@ class IntentTest {
|
||||
fun after() {
|
||||
Intents.release()
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
}
|
@ -1,6 +1,27 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
/*
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiSelector
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginActivityOfflineTest {
|
||||
|
||||
@ -10,7 +31,7 @@ class LoginActivityOfflineTest {
|
||||
fun switchAirplaneMode() {
|
||||
val device = UiDevice.getInstance(getInstrumentation())
|
||||
device.openQuickSettings()
|
||||
device.findObject(UiSelector().textContains("airplane")).click()
|
||||
device.findObject(UiSelector().textContains("Airplane")).click()
|
||||
device.pressHome()
|
||||
}
|
||||
}
|
||||
@ -46,6 +67,4 @@ class LoginActivityOfflineTest {
|
||||
db.close()
|
||||
clearData()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}*/
|
@ -19,9 +19,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import com.h.pixeldroid.testUtility.testiTesto
|
||||
import com.h.pixeldroid.testUtility.testiTestoInstance
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -35,7 +36,6 @@ class LoginActivityOnlineTest {
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
private lateinit var pref: SharedPreferences
|
||||
private lateinit var server: MockServer
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@ -43,8 +43,6 @@ class LoginActivityOnlineTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
server = MockServer()
|
||||
server.start()
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
pref = context.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
pref.edit().clear().apply()
|
||||
@ -55,7 +53,6 @@ class LoginActivityOnlineTest {
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
server.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -102,6 +99,7 @@ class LoginActivityOnlineTest {
|
||||
))
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
fun correctIntentReturnLoadsMainActivity() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
@ -109,36 +107,21 @@ class LoginActivityOnlineTest {
|
||||
db.clearAllTables()
|
||||
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = server.getUrl().toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = server.getUrl().toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.userDao().insertUser(testiTesto)
|
||||
db.close()
|
||||
pref.edit()
|
||||
.putString("domain", server.getUrl().toString())
|
||||
.putString("clientID", "test_id")
|
||||
.putString("clientSecret", "test_secret")
|
||||
.putString("domain", testiTestoInstance.uri)
|
||||
.putString("clientID", testiTesto.clientId)
|
||||
.putString("clientSecret", testiTesto.clientSecret)
|
||||
.apply()
|
||||
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=test_code")
|
||||
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=$testiTesto.")
|
||||
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
|
||||
ActivityScenario.launch<LoginActivity>(intent)
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.main_activity_main_linear_layout)).check(matches(isDisplayed()))
|
||||
}
|
||||
*/
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
@ -8,14 +7,20 @@ import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.scrollTo
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasDataString
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.h.pixeldroid.BuildConfig.INSTANCE_URI
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.waitForView
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.CoreMatchers.containsString
|
||||
import org.hamcrest.Matcher
|
||||
@ -28,17 +33,16 @@ import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginCheckIntent {
|
||||
private lateinit var context: Context
|
||||
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@get:Rule
|
||||
val intentsTestRule = ActivityTestRule(LoginActivity::class.java)
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<LoginActivity>
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
Intents.init()
|
||||
activityScenario = ActivityScenario.launch(LoginActivity::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -46,11 +50,16 @@ class LoginCheckIntent {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
val expectedIntent: Matcher<Intent> = allOf(
|
||||
hasAction(ACTION_VIEW),
|
||||
hasDataString(containsString("pixelfed.de"))
|
||||
hasDataString(containsString(INSTANCE_URI))
|
||||
)
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.editText)).perform(scrollTo()).perform(ViewActions.replaceText("pixelfed.de"), ViewActions.closeSoftKeyboard())
|
||||
waitForView(R.id.editText)
|
||||
|
||||
onView(withId(R.id.editText)).perform(scrollTo()).perform(
|
||||
ViewActions.replaceText(
|
||||
INSTANCE_URI
|
||||
), ViewActions.closeSoftKeyboard()
|
||||
)
|
||||
onView(withId(R.id.connect_instance_button)).perform(scrollTo()).perform(click())
|
||||
|
||||
Thread.sleep(3000)
|
||||
@ -61,22 +70,20 @@ class LoginCheckIntent {
|
||||
@Test
|
||||
fun launchesInstanceInfo() {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
val expectedIntent: Matcher<Intent> = allOf(
|
||||
hasAction(ACTION_VIEW),
|
||||
hasDataString(containsString("pixelfed.org/join"))
|
||||
)
|
||||
|
||||
onView(withId(R.id.whatsAnInstanceTextView)).perform(scrollTo()).perform(click())
|
||||
|
||||
Thread.sleep(3000)
|
||||
|
||||
intended(expectedIntent)
|
||||
waitForView(R.id.whats_an_instance_explanation)
|
||||
|
||||
onView(withText(R.string.whats_an_instance_explanation))
|
||||
.inRoot(isDialog())
|
||||
.check(matches(isDisplayed()));
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
Intents.release()
|
||||
clearData()
|
||||
activityScenario.close()
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,44 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
|
||||
/*
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MockedServerTest {
|
||||
|
||||
private lateinit var mockServer: MockServer
|
||||
private lateinit var activityScenario: ActivityScenario<MainActivity>
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
db.instanceDao().insertInstance(testiTestoInstance)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.userDao().insertUser(testiTesto)
|
||||
db.close()
|
||||
activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
}
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
/*
|
||||
@Test
|
||||
@ -59,90 +54,53 @@ class MockedServerTest {
|
||||
Thread.sleep(3000)
|
||||
onView(first(withId(R.id.username))).check(matches(withText("memo")))
|
||||
}
|
||||
|
||||
*/
|
||||
@Test
|
||||
fun searchHashtags() {
|
||||
activityScenario.onActivity{
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(1)?.select()
|
||||
}
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.searchEditText)).perform(ViewActions.replaceText("#caturday"), ViewActions.closeSoftKeyboard())
|
||||
onView(withId(R.id.search)).perform(typeSearchViewText("#randomNoise"))
|
||||
|
||||
onView(withId(R.id.searchButton)).perform(click())
|
||||
Thread.sleep(3000)
|
||||
onView(first(withId(R.id.tag_name))).check(matches(withText("#caturday")))
|
||||
waitForView(R.id.tag_name)
|
||||
|
||||
onView(first(withId(R.id.tag_name))).check(matches(withText("#randomNoise")))
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
@Test
|
||||
fun openDiscoverPost(){
|
||||
activityScenario.onActivity{
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(1)?.select()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
onView(first(withId(R.id.postPreview))).perform(click())
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.username)).check(matches(withText("Arthur")))
|
||||
|
||||
waitForView(R.id.postPreview)
|
||||
|
||||
onView(first(withId(R.id.postPreview))).perform(click())
|
||||
|
||||
waitForView(R.id.username)
|
||||
|
||||
onView(withId(R.id.username)).check(matches(withSubstring("User ")))
|
||||
}
|
||||
/*
|
||||
|
||||
@Test
|
||||
fun searchAccounts() {
|
||||
activityScenario.onActivity{
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(1)?.select()
|
||||
}
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.searchEditText)).perform(ViewActions.replaceText("@dansup"), ViewActions.closeSoftKeyboard())
|
||||
waitForView(R.id.search)
|
||||
|
||||
onView(withId(R.id.searchButton)).perform(click())
|
||||
Thread.sleep(3000)
|
||||
onView(first(withId(R.id.account_entry_username))).check(matches(withText("dansup")))
|
||||
onView(withId(R.id.search)).perform(typeSearchViewText("@user3"))
|
||||
|
||||
waitForView(R.id.account_entry_username)
|
||||
|
||||
onView(first(withId(R.id.account_entry_username))).check(matches(withText("User 3")))
|
||||
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@Test
|
||||
fun clickFollowButton() {
|
||||
//Get initial like count
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(0, clickChildViewWithId(R.id.username)))
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
// Unfollow
|
||||
onView(withId(R.id.followButton)).perform((click()))
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.followButton)).check(matches(withText("Follow")))
|
||||
|
||||
// Follow
|
||||
onView(withId(R.id.followButton)).perform((click()))
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.followButton)).check(matches(withText("Unfollow")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickOtherUserFollowers() {
|
||||
//Get initial like count
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(0, clickChildViewWithId(R.id.username)))
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
// Open followers list
|
||||
onView(withId(R.id.nbFollowersTextView)).perform((click()))
|
||||
Thread.sleep(1000)
|
||||
// Open follower's profile
|
||||
onView(withText("ete2")).perform((click()))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("Christian")))
|
||||
}
|
||||
/*TODO test notifications (harder since they disappear after 6 months...
|
||||
|
||||
@Test
|
||||
fun testNotificationsList() {
|
||||
@ -156,7 +114,7 @@ class MockedServerTest {
|
||||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeDown())
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withText("Dobios followed you")).check(matches(withId(R.id.notification_type)))
|
||||
onView(withText("user2 followed you")).check(matches(withId(R.id.notification_type)))
|
||||
|
||||
}
|
||||
@Test
|
||||
@ -222,7 +180,7 @@ class MockedServerTest {
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withText("Andrea"))).check(matches(withId(R.id.username)))
|
||||
}
|
||||
}*/
|
||||
|
||||
@Test
|
||||
fun swipingRightStopsAtHomepage() {
|
||||
@ -230,7 +188,8 @@ class MockedServerTest {
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
|
||||
} // go to the last tab
|
||||
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.main_activity_main_linear_layout)
|
||||
|
||||
onView(withId(R.id.main_activity_main_linear_layout))
|
||||
.perform(ViewActions.swipeRight()) // notifications
|
||||
.perform(ViewActions.swipeRight()) // camera
|
||||
@ -246,7 +205,8 @@ class MockedServerTest {
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(0)?.select()
|
||||
}
|
||||
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.main_activity_main_linear_layout)
|
||||
|
||||
onView(withId(R.id.main_activity_main_linear_layout))
|
||||
.perform(ViewActions.swipeLeft()) // notifications
|
||||
.perform(ViewActions.swipeLeft()) // camera
|
||||
@ -262,7 +222,8 @@ class MockedServerTest {
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
|
||||
} // go to the last tab
|
||||
|
||||
Thread.sleep(1000)
|
||||
waitForView(R.id.view_pager)
|
||||
|
||||
onView(withId(R.id.view_pager))
|
||||
.perform(ViewActions.swipeRight()) // notifications
|
||||
.perform(ViewActions.swipeRight()) // camera
|
||||
@ -276,7 +237,7 @@ class MockedServerTest {
|
||||
a -> assert(a.findViewById<TabLayout>(R.id.tabs).getTabAt(0)?.isSelected ?: false)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
fun censorMatrices() {
|
||||
val array: FloatArray = floatArrayOf(
|
||||
@ -287,7 +248,5 @@ class MockedServerTest {
|
||||
|
||||
assert(censorColorMatrix().equals(ColorMatrix(array)))
|
||||
assert(uncensorColorMatrix().equals(ColorMatrix()))
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}*/
|
||||
}
|
@ -1,11 +1,41 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
/*
|
||||
import android.Manifest
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.h.pixeldroid.postCreation.PostCreationActivity
|
||||
import com.h.pixeldroid.postCreation.photoEdit.ThumbnailAdapter
|
||||
import com.h.pixeldroid.settings.AboutActivity
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.*
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PostCreationActivityTest {
|
||||
|
||||
private var testScenario: ActivityScenario<PostCreationActivity>? = null
|
||||
private lateinit var mockServer: MockServer
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@ -26,33 +56,19 @@ class PostCreationActivityTest {
|
||||
@Before
|
||||
fun setup() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
testiTesto
|
||||
)
|
||||
db.close()
|
||||
|
||||
var uri1: String = ""
|
||||
var uri2: String = ""
|
||||
var uri1: Uri? = null
|
||||
var uri2: Uri? = null
|
||||
val scenario = ActivityScenario.launch(AboutActivity::class.java)
|
||||
scenario.onActivity {
|
||||
val image1 = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888)
|
||||
@ -63,28 +79,32 @@ class PostCreationActivityTest {
|
||||
val file1 = File.createTempFile("temp_img1", ".png")
|
||||
val file2 = File.createTempFile("temp_img2", ".png")
|
||||
file1.writeBitmap(image1)
|
||||
uri1 = file1.toUri().toString()
|
||||
uri1 = file1.toUri()
|
||||
file2.writeBitmap(image2)
|
||||
uri2 = file2.toUri().toString()
|
||||
Log.d("test", uri1+"\n"+uri2)
|
||||
uri2 = file2.toUri()
|
||||
}
|
||||
val intent = Intent(context, PostCreationActivity::class.java).putExtra("pictures_uri", arrayListOf(uri1, uri2))
|
||||
val intent = Intent(context, PostCreationActivity::class.java)
|
||||
|
||||
intent.clipData = ClipData("", emptyArray(), ClipData.Item(uri1))
|
||||
intent.clipData!!.addItem(ClipData.Item(uri2))
|
||||
|
||||
testScenario = ActivityScenario.launch(intent)
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Annoying to deal with and also sometimes the intent is not working as it should")
|
||||
fun createPost() {
|
||||
onView(withId(R.id.post_creation_send_button)).perform(click())
|
||||
// should send on main activity
|
||||
onView(withId(R.id.main_activity_main_linear_layout)).check(matches(isDisplayed()))
|
||||
Thread.sleep(3000)
|
||||
onView(withId(R.id.list)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
fun errorShown() {
|
||||
testScenario!!.onActivity { a -> a.upload_error.visibility = VISIBLE }
|
||||
@ -113,9 +133,9 @@ class PostCreationActivityTest {
|
||||
)
|
||||
)
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.action_upload)).perform(click())
|
||||
onView(withId(R.id.action_save)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.image_grid)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.carousel)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -127,5 +147,5 @@ class PostCreationActivityTest {
|
||||
)
|
||||
)
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
}*/
|
||||
}*/
|
||||
}
|
@ -16,13 +16,9 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@ -48,12 +44,12 @@ class PostCreationFragmentTest {
|
||||
onView(withId(R.id.drawer_layout))
|
||||
.perform(swipeLeft())
|
||||
.perform(swipeLeft())
|
||||
Thread.sleep(300)
|
||||
waitForView(R.id.photo_view_button)
|
||||
}
|
||||
|
||||
// upload intent
|
||||
// image choosing intent
|
||||
@Test
|
||||
fun uploadButtonLaunchesGalleryIntent() {
|
||||
fun galleryButtonLaunchesGalleryIntent() {
|
||||
val expectedIntent: Matcher<Intent> = hasAction(Intent.ACTION_CHOOSER)
|
||||
intending(expectedIntent)
|
||||
onView(withId(R.id.photo_view_button)).perform(click())
|
||||
@ -64,7 +60,6 @@ class PostCreationFragmentTest {
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PostFragmentUITests {
|
||||
private lateinit var mockServer: MockServer
|
||||
private lateinit var context: Context
|
||||
|
||||
@get:Rule
|
||||
@ -75,48 +70,31 @@ class PostFragmentUITests {
|
||||
@Before
|
||||
fun setup() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
testiTesto
|
||||
)
|
||||
db.close()
|
||||
Thread.sleep(300)
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun newPostUiTest() {
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
a -> a.tabs.getTabAt(2)!!.select()
|
||||
it.findViewById<TabLayout>(R.id.tabs).getTabAt(2)!!.select()
|
||||
}
|
||||
Thread.sleep(1500)
|
||||
|
||||
waitForView(R.id.photo_view_button)
|
||||
|
||||
onView(withId(R.id.photo_view_button)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.camera_capture_button)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
@ -13,14 +13,16 @@ import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.h.pixeldroid.BuildConfig.INSTANCE_URI
|
||||
import com.h.pixeldroid.posts.PostActivity
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.api.objects.*
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.testUtility.clearData
|
||||
import com.h.pixeldroid.testUtility.initDB
|
||||
import com.h.pixeldroid.testUtility.testiTesto
|
||||
import com.h.pixeldroid.testUtility.testiTestoInstance
|
||||
import org.hamcrest.CoreMatchers.anyOf
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.*
|
||||
@ -34,7 +36,6 @@ class PostTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var mockServer: MockServer
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@ -42,32 +43,13 @@ class PostTest {
|
||||
@Before
|
||||
fun before(){
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
testiTestoInstance
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token",
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
db.userDao().insertUser(testiTesto)
|
||||
db.close()
|
||||
Intents.init()
|
||||
}
|
||||
@ -76,14 +58,15 @@ class PostTest {
|
||||
fun saveToGalleryTestSimplePost() {
|
||||
val attachment = Attachment(
|
||||
id = "12",
|
||||
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
|
||||
url = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
meta = null
|
||||
)
|
||||
val post = Status(
|
||||
id = "12",
|
||||
account = Account(
|
||||
id = "12",
|
||||
username = "douze",
|
||||
url = "https://pixelfed.de/douze"
|
||||
username = "SQDFSQDF",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment)
|
||||
)
|
||||
@ -106,18 +89,20 @@ class PostTest {
|
||||
fun saveToGalleryTestAlbum() {
|
||||
val attachment1 = Attachment(
|
||||
id = "12",
|
||||
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
|
||||
url = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
meta = null
|
||||
)
|
||||
val attachment2 = Attachment(
|
||||
id = "13",
|
||||
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
|
||||
url = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
meta = null
|
||||
)
|
||||
val post = Status(
|
||||
id = "12",
|
||||
account = Account(
|
||||
id = "12",
|
||||
username = "douze",
|
||||
url = "https://pixelfed.de/douze"
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment1, attachment2)
|
||||
)
|
||||
@ -140,17 +125,18 @@ class PostTest {
|
||||
fun shareTestSimplePost() {
|
||||
val expectedIntent: Matcher<Intent> = IntentMatchers.hasAction(Intent.ACTION_CHOOSER)
|
||||
val attachment = Attachment(
|
||||
id = "12",
|
||||
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
|
||||
id = "12",
|
||||
url = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
meta = null
|
||||
)
|
||||
val post = Status(
|
||||
id = "12",
|
||||
account = Account(
|
||||
id = "12",
|
||||
username = "douze",
|
||||
url = "https://pixelfed.de/douze"
|
||||
),
|
||||
media_attachments = listOf(attachment)
|
||||
account = Account(
|
||||
id = "12",
|
||||
username = "douze",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment)
|
||||
)
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
@ -166,20 +152,22 @@ class PostTest {
|
||||
val expectedIntent: Matcher<Intent> = IntentMatchers.hasAction(Intent.ACTION_CHOOSER)
|
||||
val attachment1 = Attachment(
|
||||
id = "12",
|
||||
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
|
||||
url = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
meta = null
|
||||
)
|
||||
val attachment2 = Attachment(
|
||||
id = "13",
|
||||
url = "https://wiki.gnugen.ch/lib/tpl/gnugen/images/logo_web.png"
|
||||
id = "13",
|
||||
url = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
meta = null
|
||||
)
|
||||
val post = Status(
|
||||
id = "12",
|
||||
account = Account(
|
||||
id = "12",
|
||||
username = "douze",
|
||||
url = "https://pixelfed.de/douze"
|
||||
),
|
||||
media_attachments = listOf(attachment1, attachment2)
|
||||
account = Account(
|
||||
id = "12",
|
||||
username = "douze",
|
||||
url = "$INSTANCE_URI/pixeldroid",
|
||||
),
|
||||
media_attachments = listOf(attachment1, attachment2)
|
||||
)
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
@ -208,7 +196,7 @@ class PostTest {
|
||||
media_attachments= listOf(
|
||||
Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null)
|
||||
remote_url=null, text_url=null, description=null, blurhash=null, meta = null)
|
||||
),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
@ -235,7 +223,7 @@ class PostTest {
|
||||
media_attachments= listOf(
|
||||
Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null)
|
||||
remote_url=null, text_url=null, description=null, blurhash=null, meta = null)
|
||||
),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
@ -243,14 +231,13 @@ class PostTest {
|
||||
in_reply_to_id=null, in_reply_to_account=null, reblog=null, poll=null, card=null, language=null, text=null, favourited=false, reblogged=false, muted=false, bookmarked=false, pinned=false)
|
||||
|
||||
Assert.assertEquals("${status.reblogs_count} Shares",
|
||||
status.getNShares(getInstrumentation().targetContext))
|
||||
status.getNShares(context))
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
Intents.release()
|
||||
clearData()
|
||||
mockServer.stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
102
app/src/androidTest/java/com/h/pixeldroid/ProfileTest.kt
Normal file
102
app/src/androidTest/java/com/h/pixeldroid/ProfileTest.kt
Normal file
@ -0,0 +1,102 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.h.pixeldroid.postCreation.PostCreationActivity
|
||||
import com.h.pixeldroid.profile.ProfileActivity
|
||||
import com.h.pixeldroid.testUtility.*
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import okhttp3.internal.wait
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.Serializable
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ProfileTest {
|
||||
private lateinit var activityScenario: ActivityScenario<ProfileActivity>
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(testiTestoInstance)
|
||||
|
||||
db.userDao().insertUser(testiTesto)
|
||||
db.close()
|
||||
|
||||
val intent = Intent(context, ProfileActivity::class.java)
|
||||
val account = Account(id = "265472486651596800", username = "pixeldroid", acct = "pixeldroid", url = "https://testing2.pixeldroid.org/pixeldroid", display_name = "PixelDroid Developer", avatar = "https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0", avatar_static = "https://testing2.pixeldroid.org/storage/avatars/default.jpg?v=0", locked = false, emojis = arrayListOf(), discoverable = null, created_at = "2021-02-11T13:32:53.000000Z", statuses_count = 1, followers_count = 1, following_count = 1, moved = null, fields = null, bot = false, source = null)
|
||||
intent.putExtra(Account.ACCOUNT_TAG, account)
|
||||
activityScenario = ActivityScenario.launch(intent)
|
||||
onView(withId(R.id.profileRefreshLayout)).perform(swipeDown())
|
||||
Thread.sleep(2000)
|
||||
}
|
||||
@After
|
||||
fun after() {
|
||||
clearData()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun clickFollowButton() {
|
||||
if (onView(ViewMatchers.withText("Unfollow")).isDisplayed()) {
|
||||
//Currently following
|
||||
|
||||
// Unfollow
|
||||
follow("Follow")
|
||||
|
||||
// Follow
|
||||
follow("Unfollow")
|
||||
} else {
|
||||
//Currently not following
|
||||
|
||||
// Follow
|
||||
follow("Unfollow")
|
||||
|
||||
// Unfollow
|
||||
follow("Follow")
|
||||
}
|
||||
}
|
||||
|
||||
private fun follow(follow_or_unfollow: String){
|
||||
onView(withId(R.id.followButton)).perform((ViewActions.click()))
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.followButton)).check(ViewAssertions.matches(ViewMatchers.withText(follow_or_unfollow)))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
fun clickOtherUserFollowers() {
|
||||
// Open followers list
|
||||
onView(withId(R.id.nbFollowersTextView)).perform((ViewActions.click()))
|
||||
|
||||
waitForView(R.id.account_entry_username)
|
||||
|
||||
// Open follower's profile
|
||||
onView(ViewMatchers.withText("testi testo")).perform((ViewActions.click()))
|
||||
|
||||
waitForView(R.id.editButton)
|
||||
|
||||
//Check that our own profile opened
|
||||
onView(withId(R.id.editButton)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
}
|
||||
|
||||
}
|
@ -1,153 +1,284 @@
|
||||
package com.h.pixeldroid.testUtility
|
||||
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.*
|
||||
import androidx.test.espresso.action.*
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.BoundedMatcher
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||
import androidx.test.espresso.util.HumanReadables
|
||||
import androidx.test.espresso.util.TreeIterables
|
||||
import com.h.pixeldroid.R
|
||||
import org.hamcrest.BaseMatcher
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
|
||||
abstract class CustomMatchers {
|
||||
companion object {
|
||||
fun <T> first(matcher: Matcher<T>): Matcher<T>? {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun describeTo(description: org.hamcrest.Description?) {
|
||||
description?.appendText("first matching item")
|
||||
}
|
||||
fun ViewInteraction.isDisplayed(): Boolean {
|
||||
return try {
|
||||
check(matches(ViewMatchers.isDisplayed()))
|
||||
true
|
||||
} catch (e: NoMatchingViewException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun matches(item: Any?): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return true
|
||||
/**
|
||||
* Waits for a view to appear in the hierarchy
|
||||
* Doesn't work if the root changes (since it operates on the root!)
|
||||
* @param viewId The id of the view to wait for.
|
||||
*/
|
||||
fun waitForView(viewId: Int) {
|
||||
Espresso.onView(isRoot()).perform(waitForViewViewAction(viewId))
|
||||
}
|
||||
|
||||
/**
|
||||
* This ViewAction tells espresso to wait till a certain view is found in the view hierarchy.
|
||||
* @param viewId The id of the view to wait for.
|
||||
*/
|
||||
private fun waitForViewViewAction(viewId: Int): ViewAction {
|
||||
// The maximum time which espresso will wait for the view to show up (in milliseconds)
|
||||
val timeOut = 5000
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return isRoot()
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "wait for a specific view with id $viewId; during $timeOut millis."
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, rootView: View) {
|
||||
uiController.loopMainThreadUntilIdle()
|
||||
val startTime = System.currentTimeMillis()
|
||||
val endTime = startTime + timeOut
|
||||
val viewMatcher = withId(viewId)
|
||||
|
||||
do {
|
||||
// Iterate through all views on the screen and see if the view we are looking for is there already
|
||||
for (child in TreeIterables.breadthFirstViewTraversal(rootView)) {
|
||||
// found view with required ID
|
||||
if (viewMatcher.matches(child)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Loops the main thread for a specified period of time.
|
||||
// Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again.
|
||||
uiController.loopMainThreadForAtLeast(100)
|
||||
} while (System.currentTimeMillis() < endTime) // in case of a timeout we throw an exception -> test fails
|
||||
throw PerformException.Builder()
|
||||
.withCause(TimeoutException())
|
||||
.withActionDescription(this.description)
|
||||
.withViewDescription(HumanReadables.describe(rootView))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clickClickableSpanInDescription(textToClick: CharSequence): ViewAction {
|
||||
return object : ViewAction {
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return Matchers.instanceOf(TextView::class.java)
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "clicking on a ClickableSpan"
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val textView = view.findViewById<View>(R.id.description) as TextView
|
||||
val spannableString = SpannableString(textView.text)
|
||||
|
||||
if (spannableString.isEmpty()) {
|
||||
// TextView is empty, nothing to do
|
||||
throw NoMatchingViewException.Builder()
|
||||
.includeViewHierarchy(true)
|
||||
.withRootView(textView)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Get the links inside the TextView and check if we find textToClick
|
||||
val spans = spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
|
||||
if (spans.isNotEmpty()) {
|
||||
var spanCandidate: ClickableSpan
|
||||
for (span: ClickableSpan in spans) {
|
||||
spanCandidate = span
|
||||
val start = spannableString.getSpanStart(spanCandidate)
|
||||
val end = spannableString.getSpanEnd(spanCandidate)
|
||||
val sequence = spannableString.subSequence(start, end)
|
||||
if (textToClick.toString() == sequence.toString()) {
|
||||
span.onClick(textView)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// textToClick not found in TextView
|
||||
throw NoMatchingViewException.Builder()
|
||||
.includeViewHierarchy(true)
|
||||
.withRootView(textView)
|
||||
.build()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun typeSearchViewText(text: String): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getDescription(): String {
|
||||
return "Change view text"
|
||||
}
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return allOf(isDisplayed(), isAssignableFrom(SearchView::class.java))
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController?, view: View?) {
|
||||
(view as SearchView).setQuery(text, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> first(matcher: Matcher<T>): Matcher<T> {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun describeTo(description: org.hamcrest.Description?) {
|
||||
description?.appendText("first matching item")
|
||||
}
|
||||
|
||||
override fun matches(item: Any?): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> second(matcher: Matcher<T>): Matcher<T> {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun describeTo(description: org.hamcrest.Description?) {
|
||||
description?.appendText("second matching item")
|
||||
}
|
||||
|
||||
override fun matches(item: Any?): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return false
|
||||
} else if (!isFirst && matcher.matches(item))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> second(matcher: Matcher<T>): Matcher<T>? {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun describeTo(description: org.hamcrest.Description?) {
|
||||
description?.appendText("second matching item")
|
||||
}
|
||||
|
||||
override fun matches(item: Any?): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return false
|
||||
} else if (!isFirst && matcher.matches(item))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
return itemMatcher.matches(viewHolder.itemView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
* 0: swipes half way up
|
||||
*/
|
||||
fun slowSwipeUp(percent: Boolean) : ViewAction {
|
||||
return ViewActions.actionWithAssertions(
|
||||
GeneralSwipeAction(
|
||||
/**
|
||||
* @param percent can be 1 or 0
|
||||
* 1: swipes all the way up
|
||||
* 0: swipes half way up
|
||||
*/
|
||||
fun slowSwipeUp(percent: Boolean): ViewAction {
|
||||
return ViewActions.actionWithAssertions(
|
||||
GeneralSwipeAction(
|
||||
Swipe.SLOW,
|
||||
GeneralLocation.BOTTOM_CENTER,
|
||||
if (percent) GeneralLocation.TOP_CENTER else GeneralLocation.CENTER,
|
||||
Press.FINGER
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param percent can be 1 or 0
|
||||
* 1: swipes all the way left
|
||||
* 0: swipes half way left
|
||||
*/
|
||||
fun slowSwipeLeft(percent: Boolean) : ViewAction {
|
||||
return ViewActions.actionWithAssertions(
|
||||
GeneralSwipeAction(
|
||||
/**
|
||||
* @param percent can be 1 or 0
|
||||
* 1: swipes all the way left
|
||||
* 0: swipes half way left
|
||||
*/
|
||||
fun slowSwipeLeft(percent: Boolean): ViewAction {
|
||||
return ViewActions.actionWithAssertions(
|
||||
GeneralSwipeAction(
|
||||
Swipe.SLOW,
|
||||
GeneralLocation.CENTER_RIGHT,
|
||||
if (percent) GeneralLocation.CENTER_LEFT else GeneralLocation.CENTER,
|
||||
Press.FINGER
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getText(matcher: Matcher<View?>?): String? {
|
||||
val stringHolder = arrayOf<String?>(null)
|
||||
Espresso.onView(matcher).perform(object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return ViewMatchers.isAssignableFrom(TextView::class.java)
|
||||
}
|
||||
|
||||
fun getText(matcher: Matcher<View?>?): String? {
|
||||
val stringHolder = arrayOf<String?>(null)
|
||||
Espresso.onView(matcher).perform(object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return ViewMatchers.isAssignableFrom(TextView::class.java)
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "getting text from a TextView"
|
||||
}
|
||||
|
||||
override fun perform(
|
||||
uiController: UiController,
|
||||
view: View
|
||||
) {
|
||||
val tv = view as TextView //Save, because of check in getConstraints()
|
||||
stringHolder[0] = tv.text.toString()
|
||||
}
|
||||
})
|
||||
return stringHolder[0]
|
||||
override fun getDescription(): String {
|
||||
return "getting text from a TextView"
|
||||
}
|
||||
|
||||
fun clickChildViewWithId(id: Int) = object : ViewAction {
|
||||
|
||||
override fun getConstraints() = null
|
||||
|
||||
override fun getDescription() = "click child view with id $id"
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<View>(id)
|
||||
v.performClick()
|
||||
}
|
||||
override fun perform(
|
||||
uiController: UiController,
|
||||
view: View,
|
||||
) {
|
||||
val tv = view as TextView //Save, because of check in getConstraints()
|
||||
stringHolder[0] = tv.text.toString()
|
||||
}
|
||||
})
|
||||
return stringHolder[0]
|
||||
}
|
||||
|
||||
fun typeTextInViewWithId(id: Int, text: String) = object : ViewAction {
|
||||
fun clickChildViewWithId(id: Int) = object : ViewAction {
|
||||
|
||||
override fun getConstraints() = null
|
||||
override fun getConstraints() = null
|
||||
|
||||
override fun getDescription() = "click child view with id $id"
|
||||
override fun getDescription() = "click child view with id $id"
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<EditText>(id)
|
||||
v.text.append(text)
|
||||
}
|
||||
}
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<View>(id)
|
||||
v.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
fun typeTextInViewWithId(id: Int, text: String) = object : ViewAction {
|
||||
|
||||
override fun getConstraints() = null
|
||||
|
||||
override fun getDescription() = "click child view with id $id"
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<EditText>(id)
|
||||
v.text.append(text)
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,235 +0,0 @@
|
||||
package com.h.pixeldroid.testUtility
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
|
||||
|
||||
class MockServer {
|
||||
private val server = MockWebServer()
|
||||
|
||||
companion object{
|
||||
private const val headerName = "Content-Type"
|
||||
private const val headerValue = "application/json; charset=utf-8"
|
||||
}
|
||||
|
||||
fun start() {
|
||||
try {
|
||||
server.start(45106)
|
||||
server.dispatcher = getDispatcher()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
}
|
||||
}
|
||||
fun stop(){
|
||||
try {
|
||||
server.shutdown()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDispatcher(): Dispatcher {
|
||||
return object : Dispatcher() {
|
||||
@Throws(InterruptedException::class)
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
when (request.path) {
|
||||
"/api/v1/accounts/verify_credentials" -> return MockResponse()
|
||||
.addHeader(headerName, headerValue)
|
||||
.setResponseCode(200).setBody(JsonValues.accountJson)
|
||||
"/api/v1/instance" -> return MockResponse()
|
||||
.addHeader(headerName, headerValue)
|
||||
.setResponseCode(200).setBody(JsonValues.instanceJson.replace("REPLACEWITHDOMAIN", getUrl().toString()))
|
||||
"/api/v1/media" -> return MockResponse()
|
||||
.addHeader(headerName, headerValue)
|
||||
.setResponseCode(200).setBody(JsonValues.mediaUploadResponseJson)
|
||||
"/api/v1/timelines/home" -> return MockResponse()
|
||||
.addHeader(headerName, headerValue)
|
||||
.setResponseCode(200).setBody(JsonValues.feedJson)
|
||||
"/oauth/token" -> return MockResponse()
|
||||
.addHeader(headerName, headerValue)
|
||||
.setResponseCode(200).setBody(JsonValues.tokenJson)
|
||||
}
|
||||
when {
|
||||
request.path?.contains("/api/v1/apps") == true -> {
|
||||
return MockResponse()
|
||||
.addHeader("Content-Type", "application/json; charset=utf-8")
|
||||
.setResponseCode(200).setBody(JsonValues.applicationJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/notifications") == true -> {
|
||||
return MockResponse()
|
||||
.addHeader("Content-Type", "application/json; charset=utf-8")
|
||||
.setResponseCode(200).setBody(JsonValues.notificationsJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/timelines/home") == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.feedJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/timelines/public") == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.feedJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/accounts/0/statuses") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/statuses".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.accountStatusesJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/statuses/0/context") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/statuses/[0-9]*/context".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.commentStatusesJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/statuses/0/favourite") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/statuses/[0-9]*/favourite".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.likedJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/statuses/0/unfavourite") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/statuses/[0-9]*/unfavourite".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.unlikeJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/statuses") == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.unlikeJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/accounts/0") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.accountJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/statuses/0/reblog") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/statuses/[0-9]*/reblog".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.reblogJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/statuses/0/unreblog") == true -> {
|
||||
return MockResponse().setHttp2ErrorCode(401)
|
||||
}
|
||||
request.path?.matches("/api/v1/statuses/[0-9]*/unreblog".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.unlikeJson)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/follow".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.followRelationshipJson)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/unfollow".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.unfollowRelationshipJson)
|
||||
}
|
||||
request.path?.contains("/api/v1/accounts/relationships") == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.relationshipJson)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/followers\\?limit=[0-9]*".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.followersJson)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/followers\\?since_id=[0-9]*&limit=[0-9]*".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.followersAfterJson)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/following\\?limit=[0-9]*".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.followersJson)
|
||||
}
|
||||
request.path?.matches("/api/v1/accounts/[0-9]*/following\\?since_id=[0-9]*&limit=[0-9]*".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.followersAfterJson)
|
||||
}
|
||||
request.path?.matches("/api/v2/search\\?type=hashtags&q=caturday&limit=[0-9]*&offset=[0-9]*".toRegex()) == true -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.searchEmpty)
|
||||
}
|
||||
request.path?.contains("/api/v2/search?type=hashtags&q=caturday")!!-> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.searchCaturdayHashtags)
|
||||
}
|
||||
request.path?.contains("/api/v2/search?type=statuses&q=caturday")!! -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.searchCaturday)
|
||||
}
|
||||
request.path?.contains("/api/v2/search?type=accounts&q=dansup")!! -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.searchDansupAccounts)
|
||||
}
|
||||
request.path?.matches("""/api/v2/search\?(max_id=[0-9]*&)?type=(accounts|statuses)&q=dansup(&limit=[0-9]*)?""".toRegex())!! -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.searchEmpty)
|
||||
}
|
||||
request.path?.contains("/api/v2/discover/posts")!! -> {
|
||||
return MockResponse().addHeader(
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
).setResponseCode(200).setBody(JsonValues.discover)
|
||||
}
|
||||
else -> return MockResponse().setResponseCode(404)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getUrl(): HttpUrl {
|
||||
return server.url("")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.h.pixeldroid.testUtility
|
||||
|
||||
import com.h.pixeldroid.BuildConfig.*
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
|
||||
val testiTestoInstance = InstanceDatabaseEntity(
|
||||
uri = INSTANCE_URI,
|
||||
title = "PixelDroid CI instance",
|
||||
maxStatusChars = 150,
|
||||
maxPhotoSize = 64000,
|
||||
maxVideoSize = 64000,
|
||||
albumLimit = 4
|
||||
)
|
||||
val testiTesto = UserDatabaseEntity(
|
||||
|
||||
user_id = USER_ID,
|
||||
instance_uri = INSTANCE_URI,
|
||||
username = "testitesto",
|
||||
display_name = "testi testo",
|
||||
avatar_static = "$INSTANCE_URI/storage/avatars/default.jpg?v=0",
|
||||
isActive = true,
|
||||
accessToken = ACCESS_TOKEN,
|
||||
refreshToken = REFRESH_TOKEN,
|
||||
clientId = CLIENT_ID,
|
||||
clientSecret = CLIENT_SECRET
|
||||
)
|
||||
|
@ -19,7 +19,6 @@
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
|
@ -30883,6 +30883,253 @@
|
||||
<pre>Copyright [yyyy] [name of copyright owner]

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

 http://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.</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="library">
|
||||
<!-- https://opensource.org/licenses/Apache-2.0 -->
|
||||
<h1 class="title">dynamicanimation</h1>
|
||||
<p class="notice">Copyright © Google Inc. All rights reserved.</p>
|
||||
<p><a href="http://developer.android.com/tools/extras/support-library.html">http://developer.android.com/tools/extras/support-library.html</a></p>
|
||||
<input type="checkbox"><label></label>
|
||||
<div class="license">
|
||||
<h2>
|
||||
Apache License
|
||||
<br/>
|
||||
Version 2.0, January 2004
|
||||
<br/>
|
||||
http://www.apache.org/licenses/
|
||||
</h2>
|
||||
<p>TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION</p>
|
||||
<h2>1. Definitions.</h2>
|
||||
<p>
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
</p>
|
||||
<p>
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
</p>
|
||||
<p>
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
</p>
|
||||
<p>
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
</p>
|
||||
<p>
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
</p>
|
||||
<p>
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
</p>
|
||||
<p>
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
</p>
|
||||
<p>
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
</p>
|
||||
<p>
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
</p>
|
||||
<p>
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
</p>
|
||||
<div class="block">
|
||||
<h2 class="inline">2. Grant of Copyright License.</h2>
|
||||
<p class="inline">
|
||||
Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2 class="inline">3. Grant of Patent License.</h2>
|
||||
<p class="inline">
|
||||
Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2 class="inline">4. Redistribution.</h2>
|
||||
<p class="inline">
|
||||
You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
</p>
|
||||
</div>
|
||||
<ul class="low-alpha">
|
||||
<li>
|
||||
You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
</li>
|
||||
<li>
|
||||
You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
</li>
|
||||
<li>
|
||||
You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of |
|
||||
the Derivative Works; and
|
||||
</li>
|
||||
<li>
|
||||
If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
</p>
|
||||
<div class="block">
|
||||
<h2 class="inline">5. Submission of Contributions.</h2>
|
||||
<p class="inline">
|
||||
Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2 class="inline">6. Trademarks.</h2>
|
||||
<p class="inline">
|
||||
This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2 class="inline">7. Disclaimer of Warranty.</h2>
|
||||
<p class="inline">
|
||||
Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2 class="inline">8. Limitation of Liability.</h2>
|
||||
<p class="inline">
|
||||
In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
</p>
|
||||
</div>
|
||||
<div class="block">
|
||||
<h2 class="inline">9. Accepting Warranty or Additional Liability.</h2>
|
||||
<p class="inline">
|
||||
While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
</p>
|
||||
</div>
|
||||
<p>END OF TERMS AND CONDITIONS</p>
|
||||
<h1>APPENDIX: How to apply the Apache License to your work.</h1>
|
||||
<p>
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
</p>
|
||||
<pre>Copyright [yyyy] [name of copyright owner]

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

 http://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.</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
@ -10,22 +10,15 @@ import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.databinding.ActivityLoginBinding
|
||||
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.*
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.*
|
||||
import com.h.pixeldroid.utils.db.addUser
|
||||
import com.h.pixeldroid.utils.db.storeInstance
|
||||
import com.h.pixeldroid.utils.hasInternet
|
||||
import com.h.pixeldroid.utils.normalizeDomain
|
||||
import com.h.pixeldroid.utils.openUrl
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import okhttp3.HttpUrl
|
||||
import kotlinx.coroutines.*
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
/**
|
||||
Overview of the flow of the login process: (boxes are requests done in parallel,
|
||||
@ -104,9 +97,13 @@ class LoginActivity : BaseActivity() {
|
||||
|
||||
|
||||
private fun whatsAnInstance() {
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = Uri.parse("https://pixelfed.org/join")
|
||||
startActivity(i)
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.apply {
|
||||
setView(layoutInflater.inflate(R.layout.whats_an_instance_explanation, null))
|
||||
setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
}
|
||||
// Create the AlertDialog
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
@ -121,11 +118,7 @@ class LoginActivity : BaseActivity() {
|
||||
|
||||
private fun registerAppToServer(normalizedDomain: String) {
|
||||
|
||||
try{
|
||||
HttpUrl.Builder().host(normalizedDomain.replace("https://", "")).scheme("https").build()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return failedRegistration(getString(R.string.invalid_domain))
|
||||
}
|
||||
if(!validDomain(normalizedDomain)) return failedRegistration(getString(R.string.invalid_domain))
|
||||
|
||||
hideKeyboard()
|
||||
loadingAnimation(true)
|
||||
@ -134,7 +127,6 @@ class LoginActivity : BaseActivity() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
supervisorScope { }
|
||||
val credentialsDeferred: Deferred<Application?> = async {
|
||||
try {
|
||||
pixelfedAPI.registerApplication(
|
||||
@ -153,7 +145,6 @@ class LoginActivity : BaseActivity() {
|
||||
|
||||
val clientId = credentials?.client_id ?: return@launch failedRegistration()
|
||||
preferences.edit()
|
||||
.putString("domain", normalizedDomain)
|
||||
.putString("clientID", clientId)
|
||||
.putString("clientSecret", credentials.client_secret)
|
||||
.apply()
|
||||
@ -177,18 +168,39 @@ class LoginActivity : BaseActivity() {
|
||||
normalizedDomain: String,
|
||||
clientId: String,
|
||||
nodeInfoSchemaUrl: String
|
||||
) {
|
||||
val nodeInfo = try {
|
||||
) = coroutineScope {
|
||||
|
||||
val nodeInfo: NodeInfo = try {
|
||||
pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl)
|
||||
} catch (exception: IOException) {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||
} catch (exception: HttpException) {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
|
||||
val domain: String = try {
|
||||
if (nodeInfo.hasInstanceEndpointInfo()) {
|
||||
storeInstance(db, nodeInfo)
|
||||
nodeInfo.metadata?.config?.site?.url
|
||||
} else {
|
||||
val instance: Instance = try {
|
||||
pixelfedAPI.instance()
|
||||
} catch (exception: IOException) {
|
||||
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||
} catch (exception: HttpException) {
|
||||
return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
storeInstance(db, nodeInfo = null, instance = instance)
|
||||
instance.uri
|
||||
}
|
||||
} catch (e: IllegalArgumentException){ null }
|
||||
?: return@coroutineScope failedRegistration(getString(R.string.instance_error))
|
||||
|
||||
preferences.edit().putString("domain", normalizeDomain(domain)).apply()
|
||||
|
||||
|
||||
if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) {
|
||||
val builder = AlertDialog.Builder(this@LoginActivity)
|
||||
builder.apply {
|
||||
AlertDialog.Builder(this@LoginActivity).apply {
|
||||
setMessage(R.string.instance_not_pixelfed_warning)
|
||||
setPositiveButton(R.string.instance_not_pixelfed_continue) { _, _ ->
|
||||
promptOAuth(normalizedDomain, clientId)
|
||||
@ -197,13 +209,18 @@ class LoginActivity : BaseActivity() {
|
||||
loadingAnimation(false)
|
||||
wipeSharedSettings()
|
||||
}
|
||||
}
|
||||
// Create the AlertDialog
|
||||
builder.show()
|
||||
}.show()
|
||||
} else if (nodeInfo.metadata?.config?.features?.mobile_apis != true) {
|
||||
AlertDialog.Builder(this@LoginActivity).apply {
|
||||
setMessage(R.string.api_not_enabled_dialog)
|
||||
setNegativeButton(android.R.string.ok) { _, _ ->
|
||||
loadingAnimation(false)
|
||||
wipeSharedSettings()
|
||||
}
|
||||
}.show()
|
||||
} else {
|
||||
promptOAuth(normalizedDomain, clientId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -234,9 +251,6 @@ class LoginActivity : BaseActivity() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val instanceDeferred = async {
|
||||
pixelfedAPI.instance()
|
||||
}
|
||||
val token = pixelfedAPI.obtainToken(
|
||||
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
|
||||
"authorization_code"
|
||||
@ -244,20 +258,12 @@ class LoginActivity : BaseActivity() {
|
||||
if (token.access_token == null) {
|
||||
return@launch failedRegistration(getString(R.string.token_error))
|
||||
}
|
||||
|
||||
val instance = instanceDeferred.await()
|
||||
|
||||
if (instance.uri == null) {
|
||||
return@launch failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
|
||||
storeInstance(db, instance)
|
||||
storeUser(
|
||||
token.access_token,
|
||||
token.refresh_token,
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance.uri
|
||||
domain
|
||||
)
|
||||
wipeSharedSettings()
|
||||
} catch (exception: IOException) {
|
||||
|
@ -61,15 +61,16 @@ class MainActivity : BaseActivity() {
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
TraceDroidEmailSender.sendStackTraces("contact@pixeldroid.org", this)
|
||||
|
||||
//get the currently active user
|
||||
user = db.userDao().getActiveUser()
|
||||
|
||||
//Check if we have logged in and gotten an access token
|
||||
if (user == null) {
|
||||
launchActivity(LoginActivity(), firstTime = true)
|
||||
finish()
|
||||
} else {
|
||||
TraceDroidEmailSender.sendStackTraces("contact@pixeldroid.org", this)
|
||||
|
||||
setupDrawer()
|
||||
|
||||
val tabs: List<() -> Fragment> = listOf(
|
||||
|
@ -1,10 +1,8 @@
|
||||
package com.h.pixeldroid.postCreation
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.app.AlertDialog
|
||||
import android.content.*
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@ -16,26 +14,23 @@ import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.Toast
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.MainActivity
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.postCreation.camera.CameraActivity
|
||||
import com.h.pixeldroid.postCreation.carousel.CarouselItem
|
||||
import com.h.pixeldroid.postCreation.carousel.ImageCarousel
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import com.h.pixeldroid.utils.api.objects.Instance
|
||||
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
@ -47,14 +42,18 @@ import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.ceil
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
private const val TAG = "Post Creation Activity"
|
||||
private const val MORE_PICTURES_REQUEST_CODE = 0xffff
|
||||
|
||||
data class PhotoData(
|
||||
var imageUri: Uri,
|
||||
var size: Long,
|
||||
var uploadId: String? = null,
|
||||
var progress: Int? = null
|
||||
var progress: Int? = null,
|
||||
var imageDescription: String? = null,
|
||||
)
|
||||
|
||||
class PostCreationActivity : BaseActivity() {
|
||||
@ -64,6 +63,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
|
||||
private var positionResult = 0
|
||||
private var user: UserDatabaseEntity? = null
|
||||
private lateinit var instance: InstanceDatabaseEntity
|
||||
|
||||
private val photoData: ArrayList<PhotoData> = ArrayList()
|
||||
|
||||
@ -74,48 +74,40 @@ class PostCreationActivity : BaseActivity() {
|
||||
binding = ActivityPostCreationBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// get image URIs
|
||||
if(intent.clipData != null) {
|
||||
val count = intent.clipData!!.itemCount
|
||||
for (i in 0 until count) {
|
||||
intent.clipData!!.getItemAt(i).uri.let {
|
||||
photoData.add(PhotoData(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user = db.userDao().getActiveUser()
|
||||
|
||||
val instances = db.instanceDao().getAll()
|
||||
instance = user?.run {
|
||||
db.instanceDao().getAll().first { instanceDatabaseEntity ->
|
||||
instanceDatabaseEntity.uri.contains(instance_uri)
|
||||
}
|
||||
} ?: InstanceDatabaseEntity("", "")
|
||||
|
||||
binding.postTextInputLayout.counterMaxLength = if (user != null){
|
||||
val thisInstances =
|
||||
instances.filter { instanceDatabaseEntity ->
|
||||
instanceDatabaseEntity.uri.contains(user!!.instance_uri)
|
||||
}
|
||||
thisInstances.first().max_toot_chars
|
||||
} else {
|
||||
Instance.DEFAULT_MAX_TOOT_CHARS
|
||||
}
|
||||
binding.postTextInputLayout.counterMaxLength = instance.maxStatusChars
|
||||
|
||||
// get image URIs
|
||||
intent.clipData?.let { addPossibleImages(it) }
|
||||
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
|
||||
val carousel: ImageCarousel = binding.carousel
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri) })
|
||||
carousel.layoutCarouselCallback = {
|
||||
//TODO transition instead of at once
|
||||
if(it){
|
||||
// Became a carousel
|
||||
binding.toolbar3.visibility = VISIBLE
|
||||
binding.toolbarPostCreation.visibility = VISIBLE
|
||||
} else {
|
||||
// Became a grid
|
||||
binding.toolbar3.visibility = INVISIBLE
|
||||
binding.toolbarPostCreation.visibility = INVISIBLE
|
||||
}
|
||||
}
|
||||
carousel.maxEntries = instance.albumLimit
|
||||
carousel.addPhotoButtonCallback = {
|
||||
addPhoto(applicationContext)
|
||||
}
|
||||
carousel.updateDescriptionCallback = { position: Int, description: String ->
|
||||
photoData[position].imageDescription = description
|
||||
}
|
||||
|
||||
// get the description and send the post
|
||||
binding.postCreationSendButton.setOnClickListener {
|
||||
@ -152,7 +144,64 @@ class PostCreationActivity : BaseActivity() {
|
||||
binding.removePhotoButton.setOnClickListener {
|
||||
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
||||
photoData.removeAt(currentPosition)
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||
binding.addPhotoButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add as many images as possible to [photoData], from the [clipData], and if
|
||||
* ([photoData].size + [clipData].itemCount) > [albumLimit] then it will only add as many images
|
||||
* as are legal (if any) and a dialog will be shown to the user alerting them of this fact.
|
||||
*/
|
||||
private fun addPossibleImages(clipData: ClipData){
|
||||
var count = clipData.itemCount
|
||||
if(count + photoData.size > instance.albumLimit){
|
||||
AlertDialog.Builder(this).apply {
|
||||
setMessage(getString(R.string.total_exceeds_album_limit).format(instance.albumLimit))
|
||||
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
count = count.coerceAtMost(instance.albumLimit - photoData.size)
|
||||
}
|
||||
if (count + photoData.size >= instance.albumLimit) {
|
||||
// Disable buttons to add more images
|
||||
binding.addPhotoButton.isEnabled = false
|
||||
}
|
||||
for (i in 0 until count) {
|
||||
clipData.getItemAt(i).uri.let {
|
||||
val size: Long =
|
||||
if (it.toString().startsWith("content")) {
|
||||
contentResolver.query(it, null, null, null, null)
|
||||
?.use { cursor ->
|
||||
/* Get the column indexes of the data in the Cursor,
|
||||
* move to the first row in the Cursor, get the data,
|
||||
* and display it.
|
||||
*/
|
||||
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||
cursor.moveToFirst()
|
||||
cursor.getLong(sizeIndex)
|
||||
} ?: 0
|
||||
} else {
|
||||
it.toFile().length()
|
||||
}
|
||||
val sizeInkBytes = ceil(size.toDouble() / 1000).toLong()
|
||||
if(sizeInkBytes > instance.maxPhotoSize || sizeInkBytes > instance.maxVideoSize){
|
||||
val maxSize = when {
|
||||
instance.maxPhotoSize != instance.maxVideoSize -> {
|
||||
val type = contentResolver.getType(it)
|
||||
if(type?.startsWith("video/") == true){
|
||||
instance.maxVideoSize
|
||||
} else instance.maxPhotoSize
|
||||
}
|
||||
else -> instance.maxPhotoSize
|
||||
}
|
||||
AlertDialog.Builder(this).apply {
|
||||
setMessage(getString(R.string.size_exceeds_instance_limit).format(photoData.size + 1, sizeInkBytes, maxSize))
|
||||
setNegativeButton(android.R.string.ok) { _, _ -> }
|
||||
}.show()
|
||||
}
|
||||
photoData.add(PhotoData(imageUri = it, size = size))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,21 +226,21 @@ class PostCreationActivity : BaseActivity() {
|
||||
|
||||
if(path.startsWith("file")) {
|
||||
MediaScannerConnection.scanFile(
|
||||
this,
|
||||
arrayOf(path.toUri().toFile().absolutePath),
|
||||
null
|
||||
this,
|
||||
arrayOf(path.toUri().toFile().absolutePath),
|
||||
null
|
||||
) { path, uri ->
|
||||
if (uri == null) {
|
||||
Log.e(
|
||||
"NEW IMAGE SCAN FAILED",
|
||||
"Tried to scan $path, but it failed"
|
||||
"NEW IMAGE SCAN FAILED",
|
||||
"Tried to scan $path, but it failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Snackbar.make(
|
||||
button, getString(R.string.save_image_success),
|
||||
Snackbar.LENGTH_LONG
|
||||
button, getString(R.string.save_image_success),
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
@ -204,8 +253,8 @@ class PostCreationActivity : BaseActivity() {
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
||||
contentValues.put(
|
||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
Environment.DIRECTORY_PICTURES
|
||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
Environment.DIRECTORY_PICTURES
|
||||
)
|
||||
val imageUri: Uri =
|
||||
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
|
||||
@ -228,7 +277,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
val content = editText?.length() ?: 0
|
||||
if (content > counterMaxLength) {
|
||||
// error, too many characters
|
||||
error = getString(R.string.description_max_characters).format(counterMaxLength)
|
||||
error = resources.getQuantityString(R.plurals.description_max_characters, counterMaxLength, counterMaxLength)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -252,23 +301,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
val imageUri = data.imageUri
|
||||
val imageInputStream = contentResolver.openInputStream(imageUri)!!
|
||||
|
||||
val size =
|
||||
if (imageUri.toString().startsWith("content")) {
|
||||
contentResolver.query(imageUri, null, null, null, null)
|
||||
?.use { cursor ->
|
||||
/* Get the column indexes of the data in the Cursor,
|
||||
* move to the first row in the Cursor, get the data,
|
||||
* and display it.
|
||||
*/
|
||||
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||
cursor.moveToFirst()
|
||||
cursor.getLong(sizeIndex)
|
||||
} ?: 0
|
||||
} else {
|
||||
imageUri.toFile().length()
|
||||
}
|
||||
|
||||
val imagePart = ProgressRequestBody(imageInputStream, size)
|
||||
val imagePart = ProgressRequestBody(imageInputStream, data.size)
|
||||
val requestBody = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
|
||||
@ -283,32 +316,43 @@ class PostCreationActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
var postSub: Disposable? = null
|
||||
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", requestBody.parts[0])
|
||||
|
||||
val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
|
||||
|
||||
|
||||
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", description, requestBody.parts[0])
|
||||
|
||||
postSub = inter
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ attachment: Attachment ->
|
||||
data.progress = 0
|
||||
data.uploadId = attachment.id!!
|
||||
},
|
||||
{ e ->
|
||||
binding.uploadError.visibility = View.VISIBLE
|
||||
e.printStackTrace()
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
},
|
||||
{
|
||||
data.progress = 100
|
||||
if(photoData.all{it.progress == 100}){
|
||||
binding.uploadProgressBar.visibility = View.GONE
|
||||
binding.uploadCompletedTextview.visibility = View.VISIBLE
|
||||
post()
|
||||
{ attachment: Attachment ->
|
||||
data.progress = 0
|
||||
data.uploadId = attachment.id!!
|
||||
},
|
||||
{ e: Throwable ->
|
||||
binding.uploadError.visibility = View.VISIBLE
|
||||
if(e is HttpException){
|
||||
binding.uploadErrorTextExplanation.text =
|
||||
getString(R.string.upload_error).format(e.code())
|
||||
binding.uploadErrorTextExplanation.visibility= VISIBLE
|
||||
} else {
|
||||
binding.uploadErrorTextExplanation.visibility= View.GONE
|
||||
}
|
||||
e.printStackTrace()
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
},
|
||||
{
|
||||
data.progress = 100
|
||||
if (photoData.all { it.progress == 100 && it.uploadId != null }) {
|
||||
binding.uploadProgressBar.visibility = View.GONE
|
||||
binding.uploadCompletedTextview.visibility = View.VISIBLE
|
||||
post()
|
||||
}
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
}
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -319,23 +363,23 @@ class PostCreationActivity : BaseActivity() {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
pixelfedAPI.postStatus(
|
||||
authorization = "Bearer $accessToken",
|
||||
statusText = description,
|
||||
media_ids = photoData.mapNotNull { it.uploadId }.toList()
|
||||
authorization = "Bearer $accessToken",
|
||||
statusText = description,
|
||||
media_ids = photoData.mapNotNull { it.uploadId }.toList()
|
||||
)
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.upload_post_success),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
} catch (exception: IOException) {
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.upload_post_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, exception.toString())
|
||||
enableButton(true)
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.upload_post_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, exception.response().toString() + exception.message().toString())
|
||||
enableButton(true)
|
||||
}
|
||||
@ -369,7 +413,7 @@ class PostCreationActivity : BaseActivity() {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
photoData[positionResult].imageUri = data.getStringExtra("result")!!.toUri()
|
||||
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||
|
||||
photoData[positionResult].progress = null
|
||||
photoData[positionResult].uploadId = null
|
||||
@ -377,15 +421,13 @@ class PostCreationActivity : BaseActivity() {
|
||||
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else if (requestCode == MORE_PICTURES_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK && data?.clipData != null) {
|
||||
|
||||
val count = data.clipData!!.itemCount
|
||||
for (i in 0 until count) {
|
||||
val imageUri: Uri = data.clipData!!.getItemAt(i).uri
|
||||
photoData.add(PhotoData(imageUri))
|
||||
if (resultCode == Activity.RESULT_OK && data?.clipData != null) {
|
||||
data.clipData?.let {
|
||||
addPossibleImages(it)
|
||||
}
|
||||
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri, it.imageDescription) })
|
||||
} else if(resultCode != Activity.RESULT_CANCELED){
|
||||
Toast.makeText(applicationContext, "Error while adding images", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
@ -13,18 +13,17 @@ import com.h.pixeldroid.R
|
||||
|
||||
|
||||
class CarouselAdapter(
|
||||
@LayoutRes private val itemLayout: Int,
|
||||
@IdRes private val imageViewId: Int,
|
||||
var listener: OnItemClickListener? = null,
|
||||
private val imageScaleType: ImageView.ScaleType,
|
||||
private val imagePlaceholder: Drawable?,
|
||||
private val carousel: Boolean
|
||||
@LayoutRes private val itemLayout: Int,
|
||||
@IdRes private val imageViewId: Int,
|
||||
var listener: OnItemClickListener? = null,
|
||||
private val imageScaleType: ImageView.ScaleType,
|
||||
private val imagePlaceholder: Drawable?,
|
||||
private val carousel: Boolean,
|
||||
var maxEntries: Int?,
|
||||
) : RecyclerView.Adapter<CarouselAdapter.MyViewHolder>() {
|
||||
|
||||
private val dataList: MutableList<CarouselItem> = mutableListOf()
|
||||
|
||||
|
||||
|
||||
class MyViewHolder(itemView: View, imageViewId: Int) : RecyclerView.ViewHolder(itemView) {
|
||||
var img: ImageView = itemView.findViewById(imageViewId)
|
||||
}
|
||||
@ -51,6 +50,7 @@ class CarouselAdapter(
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if(carousel) dataList.size
|
||||
else if (maxEntries != null && dataList.size >= maxEntries!!) maxEntries!!
|
||||
else dataList.size + 1
|
||||
}
|
||||
|
||||
@ -95,6 +95,11 @@ class CarouselAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateDescription(position: Int, description: String) {
|
||||
dataList[position] = dataList[position].copy(caption = description)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
fun addAll(dataList: List<CarouselItem>) {
|
||||
this.dataList.clear()
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.h.pixeldroid.postCreation.carousel
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
data class CarouselItem constructor(
|
||||
val imageUrl: String? = null,
|
||||
val caption: String? = null
|
||||
val imageUrl: Uri,
|
||||
val caption: String? = null
|
||||
) {
|
||||
constructor(imageUrl: String? = null) : this(imageUrl, null)
|
||||
constructor(imageUrl: Uri) : this(imageUrl, null)
|
||||
}
|
@ -8,10 +8,7 @@ import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.*
|
||||
import androidx.annotation.Dimension
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.LayoutRes
|
||||
@ -19,6 +16,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.*
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ImageCarouselBinding
|
||||
import me.relex.circleindicator.CircleIndicator2
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.Nullable
|
||||
@ -31,6 +29,8 @@ class ImageCarousel(
|
||||
|
||||
private var adapter: CarouselAdapter? = null
|
||||
|
||||
private lateinit var binding: ImageCarouselBinding
|
||||
|
||||
private val scaleTypeArray = arrayOf(
|
||||
ImageView.ScaleType.MATRIX,
|
||||
ImageView.ScaleType.FIT_XY,
|
||||
@ -42,11 +42,9 @@ class ImageCarousel(
|
||||
ImageView.ScaleType.CENTER_INSIDE
|
||||
)
|
||||
|
||||
private lateinit var carouselView: View
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var tvCaption: TextView
|
||||
private lateinit var previousButtonContainer: FrameLayout
|
||||
private lateinit var nextButtonContainer: FrameLayout
|
||||
private lateinit var editTextMediaDescription: EditText
|
||||
private var snapHelper: SnapHelper = PagerSnapHelper()
|
||||
|
||||
var indicator: CircleIndicator2? = null
|
||||
@ -151,9 +149,9 @@ class ImageCarousel(
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
previousButtonContainer.visibility =
|
||||
binding.btnPrevious.visibility =
|
||||
if (showNavigationButtons) View.VISIBLE else View.GONE
|
||||
nextButtonContainer.visibility =
|
||||
binding.btnNext.visibility =
|
||||
if (showNavigationButtons) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
@ -194,69 +192,19 @@ class ImageCarousel(
|
||||
initAdapter()
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
var previousButtonLayout: Int = R.layout.previous_button_layout
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
btnPrevious = null
|
||||
|
||||
previousButtonContainer.removeAllViews()
|
||||
LayoutInflater.from(context).apply {
|
||||
inflate(previousButtonLayout, previousButtonContainer, true)
|
||||
}
|
||||
}
|
||||
|
||||
@IdRes
|
||||
var previousButtonId: Int = R.id.btn_next
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
btnPrevious = carouselView.findViewById(previousButtonId)
|
||||
|
||||
btnPrevious?.setOnClickListener {
|
||||
previous()
|
||||
}
|
||||
}
|
||||
|
||||
@Dimension(unit = Dimension.PX)
|
||||
var previousButtonMargin: Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
val previousButtonParams = previousButtonContainer.layoutParams as LayoutParams
|
||||
val previousButtonParams = binding.btnPrevious.layoutParams as LayoutParams
|
||||
previousButtonParams.setMargins(
|
||||
previousButtonMargin,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
)
|
||||
previousButtonContainer.layoutParams = previousButtonParams
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
var nextButtonLayout: Int = R.layout.next_button_layout
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
btnNext = null
|
||||
|
||||
nextButtonContainer.removeAllViews()
|
||||
LayoutInflater.from(context).apply {
|
||||
inflate(nextButtonLayout, nextButtonContainer, true)
|
||||
}
|
||||
}
|
||||
|
||||
@IdRes
|
||||
var nextButtonId: Int = R.id.btn_previous
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
btnNext = carouselView.findViewById(nextButtonId)
|
||||
|
||||
btnNext?.setOnClickListener {
|
||||
next()
|
||||
}
|
||||
binding.btnPrevious.layoutParams = previousButtonParams
|
||||
}
|
||||
|
||||
@Dimension(unit = Dimension.PX)
|
||||
@ -264,22 +212,22 @@ class ImageCarousel(
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
val nextButtonParams = nextButtonContainer.layoutParams as LayoutParams
|
||||
val nextButtonParams = binding.btnNext.layoutParams as LayoutParams
|
||||
nextButtonParams.setMargins(
|
||||
0,
|
||||
0,
|
||||
nextButtonMargin,
|
||||
0
|
||||
)
|
||||
nextButtonContainer.layoutParams = nextButtonParams
|
||||
binding.btnNext.layoutParams = nextButtonParams
|
||||
}
|
||||
|
||||
var showLayoutSwitchButton: Boolean = true
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
btnGrid = findViewById<ImageButton>(R.id.switchToGridButton)
|
||||
btnCarousel = findViewById<ImageButton>(R.id.switchToCarouselButton)
|
||||
btnGrid = binding.switchToGridButton
|
||||
btnCarousel = binding.switchToCarouselButton
|
||||
|
||||
btnGrid?.setOnClickListener {
|
||||
layoutCarousel = false
|
||||
@ -304,6 +252,9 @@ class ImageCarousel(
|
||||
|
||||
var layoutCarouselCallback: ((Boolean) -> Unit)? = null
|
||||
|
||||
var updateDescriptionCallback: ((position: Int, description: String) -> Unit)? = null
|
||||
|
||||
|
||||
var layoutCarousel: Boolean = true
|
||||
set(value) {
|
||||
field = value
|
||||
@ -313,10 +264,16 @@ class ImageCarousel(
|
||||
|
||||
btnNext?.visibility = VISIBLE
|
||||
btnPrevious?.visibility = VISIBLE
|
||||
|
||||
binding.editMediaDescriptionLayout.visibility = if(editingMediaDescription) VISIBLE else INVISIBLE
|
||||
tvCaption.visibility = if(editingMediaDescription) INVISIBLE else VISIBLE
|
||||
} else {
|
||||
recyclerView.layoutManager = GridLayoutManager(context, 3)
|
||||
btnNext?.visibility = GONE
|
||||
btnPrevious?.visibility = GONE
|
||||
|
||||
binding.editMediaDescriptionLayout.visibility = INVISIBLE
|
||||
tvCaption.visibility = INVISIBLE
|
||||
}
|
||||
showIndicator = value
|
||||
|
||||
@ -330,6 +287,43 @@ class ImageCarousel(
|
||||
|
||||
var addPhotoButtonCallback: (() -> Unit)? = null
|
||||
|
||||
var editingMediaDescription: Boolean = false
|
||||
set(value){
|
||||
|
||||
if(layoutCarousel){
|
||||
field = value
|
||||
if(value) editTextMediaDescription.setText(currentDescription)
|
||||
else {
|
||||
val description = editTextMediaDescription.text.toString()
|
||||
currentDescription = description
|
||||
adapter?.updateDescription(currentPosition, description)
|
||||
updateDescriptionCallback?.invoke(currentPosition, description)
|
||||
}
|
||||
binding.editMediaDescriptionLayout.visibility = if(value) VISIBLE else INVISIBLE
|
||||
tvCaption.visibility = if(value) INVISIBLE else VISIBLE
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var currentDescription: String? = null
|
||||
set(value) {
|
||||
if(!value.isNullOrEmpty()) {
|
||||
field = value
|
||||
tvCaption.text = value
|
||||
} else {
|
||||
field = null
|
||||
tvCaption.text = context.getText(R.string.no_media_description)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var maxEntries: Int? = null
|
||||
set(value){
|
||||
field = value
|
||||
adapter?.maxEntries = value
|
||||
}
|
||||
|
||||
|
||||
|
||||
init {
|
||||
@ -341,12 +335,11 @@ class ImageCarousel(
|
||||
|
||||
|
||||
private fun initViews() {
|
||||
carouselView = LayoutInflater.from(context).inflate(R.layout.image_carousel, this)
|
||||
binding = ImageCarouselBinding.inflate(LayoutInflater.from(context),this, true)
|
||||
|
||||
recyclerView = carouselView.findViewById(R.id.recyclerView)
|
||||
tvCaption = carouselView.findViewById(R.id.tv_caption)
|
||||
previousButtonContainer = carouselView.findViewById(R.id.previous_button_container)
|
||||
nextButtonContainer = carouselView.findViewById(R.id.next_button_container)
|
||||
recyclerView = binding.recyclerView
|
||||
tvCaption = binding.tvCaption
|
||||
editTextMediaDescription = binding.editTextMediaDescription
|
||||
|
||||
recyclerView.setHasFixedSize(true)
|
||||
|
||||
@ -403,16 +396,8 @@ class ImageCarousel(
|
||||
R.id.img
|
||||
)
|
||||
|
||||
previousButtonLayout = R.layout.previous_button_layout
|
||||
|
||||
previousButtonId = R.id.btn_previous
|
||||
|
||||
previousButtonMargin = 4.dpToPx(context)
|
||||
|
||||
nextButtonLayout = R.layout.next_button_layout
|
||||
|
||||
nextButtonId = R.id.btn_next
|
||||
|
||||
nextButtonMargin = 4.dpToPx(context)
|
||||
|
||||
showNavigationButtons = getBoolean(
|
||||
@ -440,12 +425,13 @@ class ImageCarousel(
|
||||
|
||||
private fun initAdapter() {
|
||||
adapter = CarouselAdapter(
|
||||
itemLayout = itemLayout,
|
||||
imageViewId = imageViewId,
|
||||
listener = onItemClickListener,
|
||||
imageScaleType = imageScaleType,
|
||||
imagePlaceholder = imagePlaceholder,
|
||||
carousel = layoutCarousel
|
||||
itemLayout = itemLayout,
|
||||
imageViewId = imageViewId,
|
||||
listener = onItemClickListener,
|
||||
imageScaleType = imageScaleType,
|
||||
imagePlaceholder = imagePlaceholder,
|
||||
carousel = layoutCarousel,
|
||||
maxEntries = maxEntries
|
||||
)
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
@ -474,7 +460,13 @@ class ImageCarousel(
|
||||
val dataItem = adapter?.getItem(position)
|
||||
|
||||
dataItem?.apply {
|
||||
tvCaption.text = this.caption
|
||||
caption.apply {
|
||||
if(layoutCarousel){
|
||||
binding.editMediaDescriptionLayout.visibility = INVISIBLE
|
||||
tvCaption.visibility = VISIBLE
|
||||
}
|
||||
currentDescription = this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -499,12 +491,27 @@ class ImageCarousel(
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
tvCaption.setOnClickListener {
|
||||
editingMediaDescription = true
|
||||
}
|
||||
|
||||
binding.btnNext.setOnClickListener {
|
||||
next()
|
||||
}
|
||||
|
||||
binding.btnPrevious.setOnClickListener {
|
||||
previous()
|
||||
}
|
||||
binding.imageDescriptionButton.setOnClickListener {
|
||||
editingMediaDescription = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initIndicator() {
|
||||
// If no custom indicator added, then default indicator will be shown.
|
||||
if (indicator == null) {
|
||||
indicator = carouselView.findViewById(R.id.indicator)
|
||||
indicator = binding.indicator
|
||||
isBuiltInIndicator = true
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ private val REQUIRED_PERMISSIONS = arrayOf(
|
||||
|
||||
class PhotoEditActivity : BaseActivity() {
|
||||
|
||||
private var saving: Boolean = false
|
||||
var saving: Boolean = false
|
||||
private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
|
||||
private val BRIGHTNESS_START = 0
|
||||
private val SATURATION_START = 1.0f
|
||||
@ -319,6 +319,7 @@ class PhotoEditActivity : BaseActivity() {
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if(grantResults.size > 1
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
|
113
app/src/main/java/com/h/pixeldroid/posts/NestedScrollableHost.kt
Normal file
113
app/src/main/java/com/h/pixeldroid/posts/NestedScrollableHost.kt
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2019 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
package com.h.pixeldroid.posts
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import android.widget.FrameLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.sign
|
||||
|
||||
/**
|
||||
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
|
||||
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
|
||||
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
|
||||
*
|
||||
* This solution has limitations when using multiple levels of nested scrollable elements
|
||||
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
|
||||
*/
|
||||
class NestedScrollableHost : ConstraintLayout {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
private var touchSlop = 0
|
||||
private var initialX = 0f
|
||||
private var initialY = 0f
|
||||
private val parentViewPager: ViewPager2?
|
||||
get() {
|
||||
var v: View? = parent as? View
|
||||
while (v != null && v !is ViewPager2) {
|
||||
v = v.parent as? View
|
||||
}
|
||||
return v as? ViewPager2
|
||||
}
|
||||
|
||||
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
|
||||
|
||||
init {
|
||||
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
}
|
||||
|
||||
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
|
||||
val direction = -delta.sign.toInt()
|
||||
return when (orientation) {
|
||||
0 -> child?.canScrollHorizontally(direction) ?: false
|
||||
1 -> child?.canScrollVertically(direction) ?: false
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
|
||||
handleInterceptTouchEvent(e)
|
||||
return super.onInterceptTouchEvent(e)
|
||||
}
|
||||
|
||||
private fun handleInterceptTouchEvent(e: MotionEvent) {
|
||||
val orientation = parentViewPager?.orientation ?: return
|
||||
|
||||
// Early return if child can't scroll in same direction as parent
|
||||
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.action == MotionEvent.ACTION_DOWN) {
|
||||
initialX = e.x
|
||||
initialY = e.y
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
} else if (e.action == MotionEvent.ACTION_MOVE) {
|
||||
val dx = e.x - initialX
|
||||
val dy = e.y - initialY
|
||||
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
|
||||
|
||||
// assuming ViewPager2 touch-slop is 2x touch-slop of child
|
||||
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
|
||||
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
|
||||
|
||||
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
||||
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
||||
// Gesture is perpendicular, allow all parents to intercept
|
||||
parent.requestDisallowInterceptTouchEvent(false)
|
||||
} else {
|
||||
// Gesture is parallel, query child if movement in that direction is possible
|
||||
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
|
||||
// Child can scroll, disallow all parents to intercept
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
} else {
|
||||
// Child cannot scroll, allow all parents to intercept
|
||||
parent.requestDisallowInterceptTouchEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,36 @@
|
||||
package com.h.pixeldroid.posts
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityPostBinding
|
||||
import com.h.pixeldroid.utils.api.objects.DiscoverPost
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.DISCOVER_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.DOMAIN_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.databinding.CommentBinding
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Mention
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
|
||||
import com.h.pixeldroid.utils.displayDimensionsInPx
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
class PostActivity : BaseActivity() {
|
||||
private lateinit var postFragment : PostFragment
|
||||
lateinit var domain : String
|
||||
private lateinit var accessToken : String
|
||||
|
||||
private lateinit var binding: ActivityPostBinding
|
||||
|
||||
private lateinit var status: Status
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityPostBinding.inflate(layoutInflater)
|
||||
@ -29,23 +38,40 @@ class PostActivity : BaseActivity() {
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val status = intent.getSerializableExtra(POST_TAG) as Status?
|
||||
val discoverPost: DiscoverPost? = intent.getSerializableExtra(DISCOVER_TAG) as DiscoverPost?
|
||||
status = intent.getSerializableExtra(POST_TAG) as Status
|
||||
val viewComments: Boolean = (intent.getSerializableExtra(VIEW_COMMENTS_TAG) ?: false) as Boolean
|
||||
val postComment: Boolean = (intent.getSerializableExtra(POST_COMMENT_TAG) ?: false) as Boolean
|
||||
|
||||
val user = db.userDao().getActiveUser()
|
||||
|
||||
domain = user?.instance_uri.orEmpty()
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
|
||||
postFragment = PostFragment()
|
||||
val arguments = Bundle()
|
||||
arguments.putString(DOMAIN_TAG, domain)
|
||||
|
||||
if (discoverPost != null) {
|
||||
binding.postProgressBar.visibility = View.VISIBLE
|
||||
getDiscoverPost(arguments, discoverPost)
|
||||
} else {
|
||||
initializeFragment(arguments, status)
|
||||
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName())
|
||||
|
||||
val holder = StatusViewHolder(binding.postFragmentSingle)
|
||||
|
||||
holder.bind(status, apiHolder.api!!, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
|
||||
|
||||
val credential = "Bearer $accessToken"
|
||||
activateCommenter(credential)
|
||||
|
||||
if(viewComments || postComment){
|
||||
//Scroll already down as much as possible (since comments are not loaded yet)
|
||||
binding.scrollview.requestChildFocus(binding.editComment, binding.editComment)
|
||||
|
||||
//Open keyboard if we want to post a comment
|
||||
if(postComment && binding.editComment.requestFocus()) {
|
||||
window.setSoftInputMode(SOFT_INPUT_STATE_VISIBLE)
|
||||
binding.editComment.requestFocus()
|
||||
}
|
||||
|
||||
// also retrieve comments if we're not posting the comment
|
||||
if(!postComment) retrieveComments(apiHolder.api!!, credential)
|
||||
}
|
||||
binding.postFragmentSingle.viewComments.setOnClickListener {
|
||||
retrieveComments(apiHolder.api!!, credential)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,32 +80,109 @@ class PostActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getDiscoverPost(
|
||||
arguments: Bundle,
|
||||
discoverPost: DiscoverPost
|
||||
) {
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
val id = discoverPost.url?.substringAfterLast('/') ?: ""
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
val status = api.getStatus("Bearer $accessToken", id)
|
||||
binding.postProgressBar.visibility = View.GONE
|
||||
initializeFragment(arguments, status)
|
||||
} catch (exception: IOException) {
|
||||
//TODO show error message
|
||||
Log.e("PostActivity:", exception.toString())
|
||||
} catch (exception: HttpException) {
|
||||
private fun activateCommenter(credential: String) {
|
||||
//Activate commenter
|
||||
binding.submitComment.setOnClickListener {
|
||||
val textIn = binding.editComment.text
|
||||
//Open text input
|
||||
if(textIn.isNullOrEmpty()) {
|
||||
Toast.makeText(
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.empty_comment),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
//Post the comment
|
||||
lifecycleScope.launchWhenCreated {
|
||||
apiHolder.api?.let { it1 -> postComment(it1, credential) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeFragment(arguments: Bundle, status: Status?){
|
||||
supportActionBar?.title = getString(R.string.post_title).format(status!!.account?.getDisplayName())
|
||||
arguments.putSerializable(POST_TAG, status)
|
||||
postFragment.arguments = arguments
|
||||
supportFragmentManager.isStateSaved
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.postFragmentSingle, postFragment).commit()
|
||||
binding.postFragmentSingle.visibility = View.VISIBLE
|
||||
private fun addComment(context: Context, commentContainer: LinearLayout,
|
||||
commentUsername: String, commentContent: String, mentions: List<Mention>,
|
||||
credential: String) {
|
||||
|
||||
|
||||
val itemBinding = CommentBinding.inflate(
|
||||
LayoutInflater.from(context), commentContainer, true
|
||||
)
|
||||
|
||||
itemBinding.user.text = commentUsername
|
||||
itemBinding.commentText.text = parseHTMLText(
|
||||
commentContent,
|
||||
mentions,
|
||||
apiHolder.api!!,
|
||||
context,
|
||||
credential,
|
||||
lifecycleScope
|
||||
)
|
||||
}
|
||||
|
||||
private fun retrieveComments(api: PixelfedAPI, credential: String) {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
status.id.let {
|
||||
try {
|
||||
val statuses = api.statusComments(it, credential).descendants
|
||||
|
||||
binding.commentContainer.removeAllViews()
|
||||
|
||||
//Create the new views for each comment
|
||||
for (status in statuses) {
|
||||
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!,
|
||||
status.content!!, status.mentions.orEmpty(), credential
|
||||
)
|
||||
}
|
||||
binding.commentContainer.visibility = View.VISIBLE
|
||||
|
||||
//Focus the comments
|
||||
binding.scrollview.requestChildFocus(binding.commentContainer, binding.commentContainer)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT FETCH ERROR", exception.toString())
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("COMMENT ERROR", "${exception.code()} with body ${exception.response()?.errorBody()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun postComment(
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
val textIn = binding.editComment.text
|
||||
val nonNullText = textIn.toString()
|
||||
status.id.let {
|
||||
try {
|
||||
val response = api.postStatus(credential, nonNullText, it)
|
||||
binding.commentIn.visibility = View.GONE
|
||||
|
||||
//Add the comment to the comment section
|
||||
addComment(
|
||||
binding.root.context, binding.commentContainer, response.account!!.username!!,
|
||||
response.content!!, response.mentions.orEmpty(), credential
|
||||
)
|
||||
|
||||
Toast.makeText(
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.comment_posted).format(textIn),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
binding.root.context, binding.root.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
binding.root.context, binding.root.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Log.e("ERROR_CODE", exception.code().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
package com.h.pixeldroid.posts
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.DOMAIN_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.BaseFragment
|
||||
import com.h.pixeldroid.utils.bindingLifecycleAware
|
||||
|
||||
|
||||
class PostFragment : BaseFragment() {
|
||||
|
||||
private lateinit var statusDomain: String
|
||||
private var currentStatus: Status? = null
|
||||
|
||||
var binding: PostFragmentBinding by bindingLifecycleAware()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
currentStatus = arguments?.getSerializable(POST_TAG) as Status?
|
||||
statusDomain = arguments?.getString(DOMAIN_TAG)!!
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = PostFragmentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
|
||||
val holder = StatusViewHolder(binding)
|
||||
|
||||
holder.bind(currentStatus, api, db, lifecycleScope)
|
||||
}
|
||||
|
||||
}
|
@ -3,9 +3,7 @@ package com.h.pixeldroid.posts
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.Log
|
||||
@ -19,15 +17,17 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.AlbumImageViewBinding
|
||||
import com.h.pixeldroid.databinding.CommentBinding
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.utils.BlurHashDecoder
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
@ -36,6 +36,7 @@ import com.karumi.dexter.listener.single.BasePermissionListener
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
/**
|
||||
@ -45,25 +46,36 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
|
||||
private var status: Status? = null
|
||||
|
||||
fun bind(status: Status?, pixelfedAPI: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope) {
|
||||
fun bind(status: Status?, pixelfedAPI: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, isActivity: Boolean = false) {
|
||||
|
||||
this.itemView.visibility = View.VISIBLE
|
||||
this.status = status
|
||||
|
||||
val metrics = itemView.context.resources.displayMetrics
|
||||
//Limit the height of the different images
|
||||
binding.postPicture.maxHeight = metrics.heightPixels * 3/4
|
||||
val maxImageRatio: Float = status?.media_attachments?.map {
|
||||
if (it.meta?.original?.width == null || it.meta.original.height == null) {
|
||||
1f
|
||||
} else {
|
||||
it.meta.original.width.toFloat() / it.meta.original.height.toFloat()
|
||||
}
|
||||
}?.maxOrNull() ?: 1f
|
||||
|
||||
val (displayWidth, displayHeight) = displayDimensionsInPx
|
||||
if (displayWidth / maxImageRatio > displayHeight * 3/4f) {
|
||||
binding.postPager.layoutParams.width = ((displayHeight * 3 / 4f) * maxImageRatio).roundToInt()
|
||||
binding.postPager.layoutParams.height = (displayHeight * 3 / 4f).toInt()
|
||||
} else {
|
||||
binding.postPager.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.postPager.layoutParams.height = (displayWidth / maxImageRatio).toInt()
|
||||
}
|
||||
|
||||
//Setup the post layout
|
||||
val picRequest = Glide.with(itemView)
|
||||
.asDrawable().fitCenter()
|
||||
.placeholder(ColorDrawable(Color.GRAY))
|
||||
val picRequest = Glide.with(itemView).asDrawable().fitCenter()
|
||||
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
||||
setupPost(picRequest, user.instance_uri, false)
|
||||
setupPost(picRequest, user.instance_uri, isActivity)
|
||||
|
||||
activateButtons(pixelfedAPI, db, lifecycleScope)
|
||||
activateButtons(pixelfedAPI, db, lifecycleScope, isActivity)
|
||||
|
||||
}
|
||||
|
||||
@ -76,7 +88,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
binding.username.apply {
|
||||
text = status?.account?.getDisplayName() ?: ""
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
setOnClickListener { status?.account?.openProfile(rootView.context) }
|
||||
setOnClickListener { status?.account?.openProfile(binding.root.context) }
|
||||
}
|
||||
|
||||
binding.usernameDesc.apply {
|
||||
@ -85,12 +97,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
}
|
||||
|
||||
binding.nlikes.apply {
|
||||
text = status?.getNLikes(rootView.context)
|
||||
text = status?.getNLikes(binding.root.context)
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
binding.nshares.apply {
|
||||
text = status?.getNShares(rootView.context)
|
||||
text = status?.getNShares(binding.root.context)
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
@ -116,15 +128,9 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
if(!status?.media_attachments.isNullOrEmpty()) {
|
||||
setupPostPics(binding, request)
|
||||
} else {
|
||||
binding.postPicture.visibility = View.GONE
|
||||
binding.postPager.visibility = View.GONE
|
||||
binding.postTabs.visibility = View.GONE
|
||||
binding.postIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
|
||||
//Set comment initial visibility
|
||||
binding.commentIn.visibility = View.GONE
|
||||
binding.commentContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun setupPostPics(
|
||||
@ -133,50 +139,44 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
) {
|
||||
|
||||
// Standard layout
|
||||
binding.postPicture.visibility = View.VISIBLE
|
||||
binding.postPager.visibility = View.GONE
|
||||
binding.postTabs.visibility = View.GONE
|
||||
binding.postPager.visibility = View.VISIBLE
|
||||
|
||||
//Attach the given tabs to the view pager
|
||||
binding.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList(), status?.sensitive)
|
||||
|
||||
if(status?.media_attachments?.size == 1) {
|
||||
request.load(status?.getPostUrl()).into(binding.postPicture)
|
||||
val imgDescription = status?.media_attachments?.get(0)?.description.orEmpty().ifEmpty { binding.root.context.getString(
|
||||
R.string.no_description) }
|
||||
binding.postPicture.contentDescription = imgDescription
|
||||
|
||||
binding.postPicture.setOnLongClickListener {
|
||||
Snackbar.make(it, imgDescription, Snackbar.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
|
||||
} else if(status?.media_attachments?.size!! > 1) {
|
||||
setupTabsLayout(binding, request)
|
||||
if(status?.media_attachments?.size ?: 0 > 1) {
|
||||
binding.postIndicator.setViewPager(binding.postPager)
|
||||
binding.postIndicator.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.postIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (status?.sensitive!!) {
|
||||
status?.setupSensitiveLayout(binding)
|
||||
if (status?.sensitive == true) {
|
||||
setupSensitiveLayout()
|
||||
} else {
|
||||
// GONE is the default, but have to set it again because of how RecyclerViews work
|
||||
binding.sensitiveWarning.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabsLayout(
|
||||
binding: PostFragmentBinding,
|
||||
request: RequestBuilder<Drawable>,
|
||||
) {
|
||||
//Only show the viewPager and tabs
|
||||
binding.postPicture.visibility = View.GONE
|
||||
binding.postPager.visibility = View.VISIBLE
|
||||
binding.postTabs.visibility = View.VISIBLE
|
||||
|
||||
//Attach the given tabs to the view pager
|
||||
binding.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList())
|
||||
private fun setupSensitiveLayout() {
|
||||
|
||||
TabLayoutMediator(binding.postTabs, binding.postPager) { tab, _ ->
|
||||
tab.icon = ContextCompat.getDrawable(binding.root.context, R.drawable.ic_dot_blue_12dp)
|
||||
}.attach()
|
||||
// Set dark layout and warning message
|
||||
binding.sensitiveWarning.visibility = View.VISIBLE
|
||||
//binding.postPicture.colorFilter = ColorMatrixColorFilter(censorMatrix)
|
||||
|
||||
fun uncensorPicture(binding: PostFragmentBinding) {
|
||||
binding.sensitiveWarning.visibility = View.GONE
|
||||
(binding.postPager.adapter as AlbumViewPagerAdapter).uncensor()
|
||||
}
|
||||
|
||||
binding.sensitiveWarning.setOnClickListener {
|
||||
uncensorPicture(binding)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDescription(
|
||||
rootView: View,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
@ -189,7 +189,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
status?.content.orEmpty(),
|
||||
status?.mentions,
|
||||
api,
|
||||
rootView.context,
|
||||
binding.root.context,
|
||||
credential,
|
||||
lifecycleScope
|
||||
)
|
||||
@ -197,13 +197,18 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun activateButtons(api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
||||
//region buttons
|
||||
private fun activateButtons(
|
||||
api: PixelfedAPI,
|
||||
db: AppDatabase,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
isActivity: Boolean
|
||||
){
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
||||
val credential = "Bearer ${user.accessToken}"
|
||||
//Set the special HTML text
|
||||
setDescription(binding.root, api, credential, lifecycleScope)
|
||||
setDescription(api, credential, lifecycleScope)
|
||||
|
||||
//Activate onclickListeners
|
||||
activateLiker(
|
||||
@ -214,9 +219,23 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
api, credential, status?.reblogged ?: false,
|
||||
lifecycleScope
|
||||
)
|
||||
activateCommenter(api, credential, lifecycleScope)
|
||||
|
||||
showComments(api, credential, lifecycleScope)
|
||||
if(isActivity){
|
||||
binding.commenter.visibility = View.INVISIBLE
|
||||
}
|
||||
else {
|
||||
binding.commenter.setOnClickListener {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Open status in activity
|
||||
val intent = Intent(it.context, PostActivity::class.java)
|
||||
intent.putExtra(POST_TAG, status)
|
||||
intent.putExtra(POST_COMMENT_TAG, true)
|
||||
it.context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showComments(lifecycleScope, isActivity)
|
||||
|
||||
activateMoreButton(api, db, lifecycleScope)
|
||||
}
|
||||
@ -438,7 +457,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
|
||||
//Activate double tap liking
|
||||
var clicked = false
|
||||
binding.postPicture.setOnClickListener {
|
||||
binding.postPager.setOnClickListener {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Check that the post isn't hidden
|
||||
if(binding.sensitiveWarning.visibility == View.GONE) {
|
||||
@ -458,7 +477,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
clicked = true
|
||||
|
||||
//Reset clicked to false after 500ms
|
||||
binding.postPicture.handler.postDelayed(fun() { clicked = false }, 500)
|
||||
binding.postPager.handler.postDelayed(fun() { clicked = false }, 500)
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,156 +530,37 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
private fun showComments(
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
isActivity: Boolean
|
||||
) {
|
||||
//Show all comments of a post
|
||||
//Show number of comments on the post
|
||||
if (status?.replies_count == 0) {
|
||||
binding.viewComments.text = binding.root.context.getString(R.string.NoCommentsToShow)
|
||||
} else {
|
||||
binding.viewComments.apply {
|
||||
text = binding.root.context.getString(R.string.number_comments)
|
||||
.format(status?.replies_count)
|
||||
setOnClickListener {
|
||||
visibility = View.GONE
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Retrieve the comments
|
||||
retrieveComments(api, credential)
|
||||
text = resources.getQuantityString(R.plurals.number_comments,
|
||||
status?.replies_count ?: 0,
|
||||
status?.replies_count ?: 0
|
||||
)
|
||||
if(!isActivity) {
|
||||
setOnClickListener {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Open status in activity
|
||||
val intent = Intent(context, PostActivity::class.java)
|
||||
intent.putExtra(POST_TAG, status)
|
||||
intent.putExtra(VIEW_COMMENTS_TAG, true)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun activateCommenter(
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
//Toggle comment button
|
||||
toggleCommentInput()
|
||||
|
||||
//Activate commenterpostPicture
|
||||
binding.submitComment.setOnClickListener {
|
||||
val textIn = binding.editComment.text
|
||||
//Open text input
|
||||
if(textIn.isNullOrEmpty()) {
|
||||
Toast.makeText(
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.empty_comment),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
//Post the comment
|
||||
lifecycleScope.launchWhenCreated {
|
||||
postComment(api, credential)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleCommentInput() {
|
||||
//Toggle comment button
|
||||
binding.commenter.setOnClickListener {
|
||||
when(binding.commentIn.visibility) {
|
||||
View.VISIBLE -> {
|
||||
binding.commentIn.visibility = View.GONE
|
||||
ImageConverter.setImageFromDrawable(
|
||||
binding.root,
|
||||
binding.commenter,
|
||||
R.drawable.ic_comment_empty
|
||||
)
|
||||
}
|
||||
View.GONE -> {
|
||||
binding.commentIn.visibility = View.VISIBLE
|
||||
ImageConverter.setImageFromDrawable(
|
||||
binding.root,
|
||||
binding.commenter,
|
||||
R.drawable.ic_comment_blue
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addComment(context: android.content.Context, commentContainer: LinearLayout, commentUsername: String, commentContent: String) {
|
||||
|
||||
|
||||
val itemBinding = CommentBinding.inflate(
|
||||
LayoutInflater.from(context), commentContainer, false
|
||||
)
|
||||
|
||||
itemBinding.user.text = commentUsername
|
||||
itemBinding.commentText.text = commentContent
|
||||
}
|
||||
|
||||
private suspend fun retrieveComments(
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
status?.id?.let {
|
||||
try {
|
||||
val statuses = api.statusComments(it, credential).descendants
|
||||
|
||||
binding.commentContainer.removeAllViews()
|
||||
|
||||
//Create the new views for each comment
|
||||
for (status in statuses) {
|
||||
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!,
|
||||
status.content!!
|
||||
)
|
||||
}
|
||||
binding.commentContainer.visibility = View.VISIBLE
|
||||
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT FETCH ERROR", exception.toString())
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("COMMENT ERROR", "${exception.code()} with body ${exception.response()?.errorBody()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun postComment(
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
val textIn = binding.editComment.text
|
||||
val nonNullText = textIn.toString()
|
||||
status?.id?.let {
|
||||
try {
|
||||
val response = api.postStatus(credential, nonNullText, it)
|
||||
binding.commentIn.visibility = View.GONE
|
||||
|
||||
//Add the comment to the comment section
|
||||
addComment(
|
||||
binding.root.context, binding.commentContainer, response.account!!.username!!,
|
||||
response.content!!
|
||||
)
|
||||
|
||||
Toast.makeText(
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.comment_posted).format(textIn),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
binding.root.context, binding.root.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
binding.root.context, binding.root.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Log.e("ERROR_CODE", exception.code().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
@ -673,7 +573,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>) :
|
||||
private class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>, private var sensitive: Boolean?) :
|
||||
RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
@ -685,19 +585,42 @@ class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>) :
|
||||
|
||||
override fun getItemCount() = media_attachments.size
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
Glide.with(holder.binding.root)
|
||||
.asDrawable().fitCenter().placeholder(ColorDrawable(Color.GRAY))
|
||||
.load(media_attachments[position].url).into(holder.image)
|
||||
media_attachments[position].apply {
|
||||
val blurhashBitMap = blurhash?.let {
|
||||
BlurHashDecoder.blurHashBitmap(
|
||||
holder.binding.root.resources,
|
||||
it,
|
||||
meta?.original?.width,
|
||||
meta?.original?.height
|
||||
)
|
||||
}
|
||||
if (sensitive == false) {
|
||||
Glide.with(holder.binding.root)
|
||||
.asDrawable().fitCenter()
|
||||
.placeholder(blurhashBitMap)
|
||||
.load(url).into(holder.image)
|
||||
} else {
|
||||
Glide.with(holder.binding.root)
|
||||
.asDrawable().fitCenter()
|
||||
.load(blurhashBitMap).into(holder.image)
|
||||
}
|
||||
|
||||
val description = media_attachments[position].description
|
||||
.orEmpty().ifEmpty{ holder.binding.root.context.getString(R.string.no_description)}
|
||||
val description = description
|
||||
.orEmpty()
|
||||
.ifEmpty { holder.binding.root.context.getString(R.string.no_description) }
|
||||
|
||||
holder.image.setOnLongClickListener {
|
||||
Snackbar.make(it, description, Snackbar.LENGTH_SHORT).show()
|
||||
true
|
||||
holder.image.setOnLongClickListener {
|
||||
Snackbar.make(it, description, Snackbar.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
|
||||
holder.image.contentDescription = description
|
||||
}
|
||||
}
|
||||
|
||||
holder.image.contentDescription = description
|
||||
fun uncensor(){
|
||||
sensitive = false
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: AlbumImageViewBinding) : RecyclerView.ViewHolder(binding.root){
|
||||
|
@ -19,6 +19,7 @@ import com.h.pixeldroid.posts.feeds.cachedFeeds.CachedFeedFragment
|
||||
import com.h.pixeldroid.posts.feeds.cachedFeeds.ViewModelFactory
|
||||
import com.h.pixeldroid.utils.api.objects.FeedContentDatabase
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.displayDimensionsInPx
|
||||
|
||||
|
||||
/**
|
||||
@ -35,7 +36,7 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
adapter = PostsAdapter()
|
||||
adapter = PostsAdapter(requireContext().displayDimensionsInPx())
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (requireArguments().get("home") as Boolean){
|
||||
@ -67,7 +68,7 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
|
||||
return view
|
||||
}
|
||||
|
||||
inner class PostsAdapter : PagingDataAdapter<T, RecyclerView.ViewHolder>(
|
||||
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<T, RecyclerView.ViewHolder>(
|
||||
object : DiffUtil.ItemCallback<T>() {
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
@ -89,7 +90,7 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position) as Status
|
||||
uiModel.let {
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope)
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ class AccountViewHolder(binding: AccountListEntryBinding) : RecyclerView.ViewHol
|
||||
.circleCrop().placeholder(R.drawable.ic_default_user)
|
||||
.into(avatar)
|
||||
|
||||
username.text = account?.username
|
||||
username.text = account?.display_name
|
||||
@SuppressLint("SetTextI18n")
|
||||
acct.text = "@${account?.acct}"
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
package com.h.pixeldroid.posts.feeds.uncachedFeeds.accountLists
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
|
||||
class FollowersPagingSource(
|
||||
private val api: PixelfedAPI,
|
||||
private val accessToken: String,
|
||||
private val accountId: String,
|
||||
private val following: Boolean
|
||||
) : PagingSource<Int, Account>() {
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Account> {
|
||||
) : PagingSource<String, Account>() {
|
||||
override suspend fun load(params: LoadParams<String>): LoadResult<String, Account> {
|
||||
val position = params.key
|
||||
return try {
|
||||
val response =
|
||||
@ -23,14 +25,14 @@ class FollowersPagingSource(
|
||||
api.followers(account_id = accountId,
|
||||
authorization = "Bearer $accessToken",
|
||||
limit = params.loadSize,
|
||||
page = position?.toString(),
|
||||
max_id = position?.toString())
|
||||
page = position,
|
||||
max_id = position)
|
||||
} else {
|
||||
api.following(account_id = accountId,
|
||||
authorization = "Bearer $accessToken",
|
||||
limit = params.loadSize,
|
||||
page = position?.toString(),
|
||||
max_id = position?.toString())
|
||||
page = position,
|
||||
max_id = position)
|
||||
}
|
||||
|
||||
val accounts = if(response.isSuccessful){
|
||||
@ -39,19 +41,19 @@ class FollowersPagingSource(
|
||||
throw HttpException(response)
|
||||
}
|
||||
|
||||
val nextPosition = if(response.headers()["Link"] != null){
|
||||
val nextPosition: String = if(response.headers()["Link"] != null){
|
||||
//Header is of the form:
|
||||
// Link: <https://mastodon.social/api/v1/accounts/1/followers?limit=2&max_id=7628164>; rel="next", <https://mastodon.social/api/v1/accounts/1/followers?limit=2&since_id=7628165>; rel="prev"
|
||||
// So we want the first max_id value. In case there are arguments after
|
||||
// the max_id in the URL, we make sure to stop at the first '?'
|
||||
response.headers()["Link"]
|
||||
.orEmpty()
|
||||
.substringAfter("max_id=")
|
||||
.substringBefore('?')
|
||||
.substringBefore('>')
|
||||
.toIntOrNull() ?: 0
|
||||
.substringAfter("max_id=", "")
|
||||
.substringBefore('?', "")
|
||||
.substringBefore('>', "")
|
||||
} else {
|
||||
params.key?.plus(1) ?: 2
|
||||
// No Link header, so we just increment the position value
|
||||
(position?.toBigIntegerOrNull() ?: 1.toBigInteger()).inc().toString()
|
||||
}
|
||||
|
||||
LoadResult.Page(
|
||||
@ -65,4 +67,9 @@ class FollowersPagingSource(
|
||||
LoadResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<String, Account>): String? =
|
||||
state.anchorPosition?.run {
|
||||
state.closestItemToPosition(this)?.id
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.h.pixeldroid.posts.feeds.uncachedFeeds.search
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.FeedContent
|
||||
import com.h.pixeldroid.utils.api.objects.Results
|
||||
@ -44,4 +45,9 @@ class SearchPagingSource<T: FeedContent>(
|
||||
LoadResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, T>): Int? =
|
||||
state.anchorPosition?.run {
|
||||
state.closestItemToPosition(this)?.id?.toIntOrNull()
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import com.h.pixeldroid.posts.StatusViewHolder
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.*
|
||||
import com.h.pixeldroid.utils.api.objects.Results
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.displayDimensionsInPx
|
||||
|
||||
/**
|
||||
* Fragment to show a list of [Status]es, as a result of a search.
|
||||
@ -25,7 +26,7 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
adapter = PostsAdapter()
|
||||
adapter = PostsAdapter(requireContext().displayDimensionsInPx())
|
||||
|
||||
query = arguments?.getSerializable("searchFeed") as String
|
||||
|
||||
@ -57,7 +58,7 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
|
||||
return view
|
||||
}
|
||||
|
||||
inner class PostsAdapter : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
|
||||
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
|
||||
object : DiffUtil.ItemCallback<Status>() {
|
||||
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
@ -79,7 +80,7 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position) as Status
|
||||
uiModel.let {
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope)
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,14 +247,23 @@ class ProfileActivity : BaseActivity() {
|
||||
supportActionBar?.subtitle = "@${account.acct}"
|
||||
}
|
||||
|
||||
binding.nbPostsTextView.text = applicationContext.getString(R.string.nb_posts)
|
||||
.format(account.statuses_count.toString())
|
||||
binding.nbPostsTextView.text = resources.getQuantityString(
|
||||
R.plurals.nb_posts,
|
||||
account.statuses_count ?: 0,
|
||||
account.statuses_count ?: 0
|
||||
)
|
||||
|
||||
binding.nbFollowersTextView.text = applicationContext.getString(R.string.nb_followers)
|
||||
.format(account.followers_count.toString())
|
||||
binding.nbFollowersTextView.text = resources.getQuantityString(
|
||||
R.plurals.nb_followers,
|
||||
account.followers_count ?: 0,
|
||||
account.followers_count ?: 0
|
||||
)
|
||||
|
||||
binding.nbFollowingTextView.text = applicationContext.getString(R.string.nb_following)
|
||||
.format(account.following_count.toString())
|
||||
binding.nbFollowingTextView.text = resources.getQuantityString(
|
||||
R.plurals.nb_following,
|
||||
account.following_count ?: 0,
|
||||
account.following_count ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
private fun onClickEditButton() {
|
||||
|
@ -4,25 +4,17 @@ import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.FragmentSearchBinding
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.profile.ProfilePostViewHolder
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.DiscoverPost
|
||||
import com.h.pixeldroid.utils.api.objects.DiscoverPosts
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.posts.PostActivity
|
||||
import com.h.pixeldroid.utils.BaseFragment
|
||||
@ -34,10 +26,7 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.color
|
||||
import com.mikepenz.iconics.utils.paddingDp
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
@ -122,12 +111,12 @@ class SearchDiscoverFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* [RecyclerView.Adapter] that can display a list of [DiscoverPost]s
|
||||
* [RecyclerView.Adapter] that can display a list of [Status]s' thumbnails for the discover view
|
||||
*/
|
||||
class DiscoverRecyclerViewAdapter: RecyclerView.Adapter<ProfilePostViewHolder>() {
|
||||
private val posts: ArrayList<DiscoverPost> = ArrayList()
|
||||
private val posts: ArrayList<Status> = ArrayList()
|
||||
|
||||
fun addPosts(newPosts : List<DiscoverPost>) {
|
||||
fun addPosts(newPosts : List<Status>) {
|
||||
posts.clear()
|
||||
posts.addAll(newPosts)
|
||||
notifyDataSetChanged()
|
||||
@ -141,15 +130,15 @@ class SearchDiscoverFragment : BaseFragment() {
|
||||
|
||||
override fun onBindViewHolder(holder: ProfilePostViewHolder, position: Int) {
|
||||
val post = posts[position]
|
||||
if(post.type?.contains("album") == true) {
|
||||
if(post.media_attachments?.size ?: 0 > 1) {
|
||||
holder.albumIcon.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.albumIcon.visibility = View.GONE
|
||||
}
|
||||
ImageConverter.setSquareImageFromURL(holder.postView, post.thumb, holder.postPreview)
|
||||
ImageConverter.setSquareImageFromURL(holder.postView, post.media_attachments?.firstOrNull()?.preview_url, holder.postPreview, post.media_attachments?.firstOrNull()?.blurhash)
|
||||
holder.postPreview.setOnClickListener {
|
||||
val intent = Intent(holder.postView.context, PostActivity::class.java)
|
||||
intent.putExtra(Status.DISCOVER_TAG, post)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
holder.postView.context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
145
app/src/main/java/com/h/pixeldroid/utils/BlurHashDecoder.kt
Normal file
145
app/src/main/java/com/h/pixeldroid/utils/BlurHashDecoder.kt
Normal file
@ -0,0 +1,145 @@
|
||||
package com.h.pixeldroid.utils
|
||||
|
||||
/**
|
||||
* Blurhash implementation from blurhash project:
|
||||
* https://github.com/woltapp/blurhash
|
||||
* Minor modifications by charlag, for the Tusky project
|
||||
* https://github.com/tuskyapp/Tusky/
|
||||
*/
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.withSign
|
||||
|
||||
object BlurHashDecoder {
|
||||
|
||||
fun blurHashBitmap(resources: Resources, blurHash: String, width: Int?, height: Int?): BitmapDrawable {
|
||||
val ratioOr0 = (width?.toFloat() ?: 1f) / (height?.toFloat() ?: 1f)
|
||||
|
||||
val ratio = if (ratioOr0 == 0f) 1f else ratioOr0
|
||||
return BitmapDrawable(resources,
|
||||
decode(blurHash,
|
||||
(32f * ratio).toInt().coerceAtLeast(32),
|
||||
(32f / ratio).toInt().coerceAtLeast(32))
|
||||
)
|
||||
}
|
||||
|
||||
fun decode(blurHash: String?, width: Int?, height: Int?, punch: Float = 1f): Bitmap? {
|
||||
if (blurHash == null || width == null || height == null || blurHash.length < 6) {
|
||||
return null
|
||||
}
|
||||
require(width > 0) { "Width must be greater than zero" }
|
||||
require(height > 0) { "height must be greater than zero" }
|
||||
|
||||
val numCompEnc = decode83(blurHash, 0, 1)
|
||||
val numCompX = (numCompEnc % 9) + 1
|
||||
val numCompY = (numCompEnc / 9) + 1
|
||||
if (blurHash.length != 4 + 2 * numCompX * numCompY) {
|
||||
return null
|
||||
}
|
||||
val maxAcEnc = decode83(blurHash, 1, 2)
|
||||
val maxAc = (maxAcEnc + 1) / 166f
|
||||
val colors = Array(numCompX * numCompY) { i ->
|
||||
if (i == 0) {
|
||||
val colorEnc = decode83(blurHash, 2, 6)
|
||||
decodeDc(colorEnc)
|
||||
} else {
|
||||
val from = 4 + i * 2
|
||||
val colorEnc = decode83(blurHash, from, from + 2)
|
||||
decodeAc(colorEnc, maxAc * punch)
|
||||
}
|
||||
}
|
||||
return composeBitmap(width, height, numCompX, numCompY, colors)
|
||||
}
|
||||
|
||||
private fun decode83(str: String, from: Int = 0, to: Int = str.length): Int {
|
||||
var result = 0
|
||||
for (i in from until to) {
|
||||
val index = charMap[str[i]] ?: -1
|
||||
if (index != -1) {
|
||||
result = result * 83 + index
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun decodeDc(colorEnc: Int): FloatArray {
|
||||
val r = colorEnc shr 16
|
||||
val g = (colorEnc shr 8) and 255
|
||||
val b = colorEnc and 255
|
||||
return floatArrayOf(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b))
|
||||
}
|
||||
|
||||
private fun srgbToLinear(colorEnc: Int): Float {
|
||||
val v = colorEnc / 255f
|
||||
return if (v <= 0.04045f) {
|
||||
(v / 12.92f)
|
||||
} else {
|
||||
((v + 0.055f) / 1.055f).pow(2.4f)
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeAc(value: Int, maxAc: Float): FloatArray {
|
||||
val r = value / (19 * 19)
|
||||
val g = (value / 19) % 19
|
||||
val b = value % 19
|
||||
return floatArrayOf(
|
||||
signedPow2((r - 9) / 9.0f) * maxAc,
|
||||
signedPow2((g - 9) / 9.0f) * maxAc,
|
||||
signedPow2((b - 9) / 9.0f) * maxAc
|
||||
)
|
||||
}
|
||||
|
||||
private fun signedPow2(value: Float) = value.pow(2f).withSign(value)
|
||||
|
||||
private fun composeBitmap(
|
||||
width: Int, height: Int,
|
||||
numCompX: Int, numCompY: Int,
|
||||
colors: Array<FloatArray>
|
||||
): Bitmap {
|
||||
val imageArray = IntArray(width * height)
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
var r = 0f
|
||||
var g = 0f
|
||||
var b = 0f
|
||||
for (j in 0 until numCompY) {
|
||||
for (i in 0 until numCompX) {
|
||||
val basis = (cos(PI * x * i / width) * cos(PI * y * j / height)).toFloat()
|
||||
val color = colors[j * numCompX + i]
|
||||
r += color[0] * basis
|
||||
g += color[1] * basis
|
||||
b += color[2] * basis
|
||||
}
|
||||
}
|
||||
imageArray[x + width * y] = Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b))
|
||||
}
|
||||
}
|
||||
return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
|
||||
private fun linearToSrgb(value: Float): Int {
|
||||
val v = value.coerceIn(0f, 1f)
|
||||
return if (v <= 0.0031308f) {
|
||||
(v * 12.92f * 255f + 0.5f).toInt()
|
||||
} else {
|
||||
((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private val charMap = listOf(
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
||||
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',',
|
||||
'-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~'
|
||||
)
|
||||
.mapIndexed { i, c -> c to i }
|
||||
.toMap()
|
||||
}
|
@ -69,8 +69,10 @@ class ImageConverter {
|
||||
* @param url, the url of the image that will be loaded
|
||||
* @param image, the imageView into which we will load the image
|
||||
*/
|
||||
fun setSquareImageFromURL(view : View, url : String?, image : ImageView) {
|
||||
Glide.with(view).load(url).apply(RequestOptions().centerCrop()).into(image)
|
||||
fun setSquareImageFromURL(view : View, url : String?, image : ImageView, blurhash: String? = null) {
|
||||
Glide.with(view).load(url).placeholder(
|
||||
blurhash?.let { BlurHashDecoder.blurHashBitmap(view.resources, it, 32,32) }
|
||||
).apply(RequestOptions().centerCrop()).into(image)
|
||||
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,15 @@ import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.h.pixeldroid.R
|
||||
import okhttp3.HttpUrl
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
@ -22,6 +25,35 @@ fun hasInternet(context: Context): Boolean {
|
||||
return cm.activeNetwork != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if domain is valid or not
|
||||
*/
|
||||
fun validDomain(domain: String?): Boolean {
|
||||
domain?.apply {
|
||||
try {
|
||||
HttpUrl.Builder().host(replace("https://", "")).scheme("https").build()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
} ?: return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
fun Context.displayDimensionsInPx(): Pair<Int, Int> {
|
||||
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
Pair(windowManager.currentWindowMetrics.bounds.width(), windowManager.currentWindowMetrics.bounds.height())
|
||||
} else {
|
||||
val metrics = DisplayMetrics()
|
||||
@Suppress("DEPRECATION")
|
||||
windowManager.defaultDisplay.getMetrics(metrics)
|
||||
Pair(metrics.widthPixels, metrics.heightPixels)
|
||||
}
|
||||
}
|
||||
|
||||
fun normalizeDomain(domain: String): String {
|
||||
return "https://" + domain
|
||||
.replace("http://", "")
|
||||
|
@ -256,6 +256,7 @@ interface PixelfedAPI {
|
||||
fun mediaUpload(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String,
|
||||
@Part description: MultipartBody.Part? = null,
|
||||
@Part file: MultipartBody.Part
|
||||
): Observable<Attachment>
|
||||
|
||||
|
@ -11,11 +11,31 @@ data class Attachment(
|
||||
//Optional attributes
|
||||
val remote_url: String? = null, //URL
|
||||
val text_url: String? = null, //URL
|
||||
//TODO meta
|
||||
|
||||
val meta: Meta?,
|
||||
|
||||
val description: String? = null,
|
||||
val blurhash: String? = null
|
||||
) : Serializable {
|
||||
enum class AttachmentType {
|
||||
unknown, image, gifv, video, audio
|
||||
}
|
||||
|
||||
data class Meta (
|
||||
val focus: Focus?,
|
||||
val original: Image?
|
||||
) : Serializable
|
||||
|
||||
{
|
||||
data class Focus(
|
||||
val x: Double?,
|
||||
val y: Double?
|
||||
) : Serializable
|
||||
data class Image(
|
||||
val width: Int?,
|
||||
val height: Int?,
|
||||
val size: String?,
|
||||
val aspect: Double?
|
||||
) : Serializable
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
package com.h.pixeldroid.utils.api.objects
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/*
|
||||
NOT DOCUMENTED, USE WITH CAUTION
|
||||
*/
|
||||
|
||||
data class DiscoverPost(
|
||||
val type: String?, //This is probably an enum, with these values: https://github.com/pixelfed/pixelfed/blob/700c7805cecc364b68b9cfe20df00608e0f6c465/app/Status.php#L31
|
||||
val url: String?, //URL to post
|
||||
val thumb: String? //URL to thumbnail
|
||||
) : Serializable
|
@ -4,5 +4,5 @@ import java.io.Serializable
|
||||
|
||||
data class DiscoverPosts(
|
||||
//Required attributes
|
||||
val posts: List<DiscoverPost>
|
||||
val posts: List<Status>
|
||||
) : Serializable
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.h.pixeldroid.utils.api.objects
|
||||
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_TOOT_CHARS
|
||||
|
||||
data class Instance (
|
||||
val description: String?,
|
||||
val email: String?,
|
||||
@ -9,8 +11,4 @@ data class Instance (
|
||||
val title: String?,
|
||||
val uri: String?,
|
||||
val version: String?
|
||||
) {
|
||||
companion object {
|
||||
const val DEFAULT_MAX_TOOT_CHARS = 500
|
||||
}
|
||||
}
|
||||
)
|
@ -1,5 +1,7 @@
|
||||
package com.h.pixeldroid.utils.api.objects
|
||||
|
||||
import com.h.pixeldroid.utils.validDomain
|
||||
|
||||
/*
|
||||
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
|
||||
@ -11,8 +13,21 @@ data class NodeInfo (
|
||||
val software: Software?,
|
||||
val protocols: List<String>?,
|
||||
val openRegistrations: Boolean?,
|
||||
val metadata: PixelfedMetadata?
|
||||
val metadata: PixelfedMetadata?,
|
||||
){
|
||||
/**
|
||||
* Check if this NodeInfo has the fields we need or if we also need to look into the
|
||||
* /api/v1/instance endpoint
|
||||
* This only checks for values that might be in the /api/v1/instance endpoint.
|
||||
*/
|
||||
fun hasInstanceEndpointInfo(): Boolean {
|
||||
return validDomain(metadata?.config?.site?.url)
|
||||
&& !metadata?.config?.site?.name.isNullOrBlank()
|
||||
&& metadata?.config?.uploader?.max_caption_length?.toIntOrNull() != null
|
||||
}
|
||||
|
||||
|
||||
|
||||
data class Software(
|
||||
val name: String?,
|
||||
val version: String?
|
||||
@ -31,7 +46,8 @@ data class NodeInfo (
|
||||
val open_registration: Boolean?,
|
||||
val uploader: Uploader?,
|
||||
val activitypub: ActivityPub?,
|
||||
val features: Features?
|
||||
val features: Features?,
|
||||
val site: Site?
|
||||
){
|
||||
data class Uploader(
|
||||
val max_photo_size: String?,
|
||||
@ -55,6 +71,13 @@ data class NodeInfo (
|
||||
val stories: Boolean?,
|
||||
val video: Boolean?
|
||||
)
|
||||
|
||||
data class Site(
|
||||
val name: String?,
|
||||
val domain: String?,
|
||||
val url: String?,
|
||||
val description: String?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,8 @@ open class Status(
|
||||
{
|
||||
companion object {
|
||||
const val POST_TAG = "postTag"
|
||||
const val DOMAIN_TAG = "domainTag"
|
||||
const val DISCOVER_TAG = "discoverTag"
|
||||
const val VIEW_COMMENTS_TAG = "view_comments_tag"
|
||||
const val POST_COMMENT_TAG = "post_comment_tag"
|
||||
}
|
||||
|
||||
fun getPostUrl() : String? = media_attachments?.firstOrNull()?.url
|
||||
@ -74,11 +74,19 @@ open class Status(
|
||||
|
||||
|
||||
fun getNLikes(context: Context) : CharSequence {
|
||||
return context.getString(R.string.likes).format(favourites_count.toString())
|
||||
return context.resources.getQuantityString(
|
||||
R.plurals.likes,
|
||||
favourites_count ?: 0,
|
||||
favourites_count ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
fun getNShares(context: Context) : CharSequence {
|
||||
return context.getString(R.string.shares).format(reblogs_count.toString())
|
||||
return context.resources.getQuantityString(
|
||||
R.plurals.shares,
|
||||
reblogs_count ?: 0,
|
||||
reblogs_count ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
fun getStatusDomain(domain: String) : String {
|
||||
@ -88,29 +96,6 @@ open class Status(
|
||||
|
||||
}
|
||||
|
||||
fun setupSensitiveLayout(binding: PostFragmentBinding) {
|
||||
|
||||
// Set dark layout and warning message
|
||||
binding.sensitiveWarning.visibility = VISIBLE
|
||||
val array = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 1f, 0f)
|
||||
val censorMatrix = ColorMatrix(array)
|
||||
binding.postPicture.colorFilter = ColorMatrixColorFilter(censorMatrix)
|
||||
|
||||
fun uncensorPicture(binding: PostFragmentBinding) {
|
||||
binding.sensitiveWarning.visibility = GONE
|
||||
binding.postPicture.clearColorFilter()
|
||||
}
|
||||
|
||||
|
||||
binding.sensitiveWarning.setOnClickListener {
|
||||
uncensorPicture(binding)
|
||||
}
|
||||
|
||||
binding.postPicture.setOnClickListener {
|
||||
uncensorPicture(binding)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadImage(context: Context, url: String, view: View, share: Boolean = false) {
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
|
@ -20,7 +20,7 @@ import com.h.pixeldroid.utils.api.objects.Notification
|
||||
PublicFeedStatusDatabaseEntity::class,
|
||||
Notification::class
|
||||
],
|
||||
version = 2
|
||||
version = 3
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
@ -4,23 +4,20 @@ import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Instance
|
||||
import com.h.pixeldroid.utils.api.objects.NodeInfo
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_ALBUM_LIMIT
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_PHOTO_SIZE
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_TOOT_CHARS
|
||||
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity.Companion.DEFAULT_MAX_VIDEO_SIZE
|
||||
import com.h.pixeldroid.utils.normalizeDomain
|
||||
|
||||
private fun normalizeOrNot(uri: String): String{
|
||||
return if(uri.startsWith("http://localhost")){
|
||||
uri
|
||||
} else {
|
||||
normalizeDomain(uri)
|
||||
}
|
||||
}
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = account.id!!,
|
||||
//make sure not to normalize to https when localhost, to allow testing
|
||||
instance_uri = normalizeOrNot(instance_uri),
|
||||
instance_uri = normalizeDomain(instance_uri),
|
||||
username = account.username!!,
|
||||
display_name = account.getDisplayName(),
|
||||
avatar_static = account.avatar_static.orEmpty(),
|
||||
@ -33,14 +30,24 @@ fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser:
|
||||
)
|
||||
}
|
||||
|
||||
fun storeInstance(db: AppDatabase, instance: Instance) {
|
||||
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.orEmpty()),
|
||||
title = instance.title.orEmpty(),
|
||||
max_toot_chars = maxTootChars,
|
||||
thumbnail = instance.thumbnail.orEmpty()
|
||||
)
|
||||
fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = null) {
|
||||
val dbInstance: InstanceDatabaseEntity = nodeInfo?.run {
|
||||
InstanceDatabaseEntity(
|
||||
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
||||
title = metadata.config.site.name!!,
|
||||
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
||||
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_PHOTO_SIZE,
|
||||
//Pixelfed doesn't distinguish between max photo and video size
|
||||
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_VIDEO_SIZE,
|
||||
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT
|
||||
)
|
||||
} ?: instance?.run {
|
||||
InstanceDatabaseEntity(
|
||||
uri = normalizeDomain(uri.orEmpty()),
|
||||
title = title.orEmpty(),
|
||||
maxStatusChars = max_toot_chars?.toInt() ?: DEFAULT_MAX_TOOT_CHARS,
|
||||
)
|
||||
} ?: throw IllegalArgumentException("Cannot store instance where both are null")
|
||||
|
||||
db.instanceDao().insertInstance(dbInstance)
|
||||
}
|
@ -6,8 +6,23 @@ import com.h.pixeldroid.utils.api.objects.Instance
|
||||
|
||||
@Entity(tableName = "instances")
|
||||
data class InstanceDatabaseEntity (
|
||||
@PrimaryKey var uri: String,
|
||||
var title: String = "",
|
||||
var max_toot_chars: Int = Instance.DEFAULT_MAX_TOOT_CHARS,
|
||||
var thumbnail: String = ""
|
||||
)
|
||||
@PrimaryKey var uri: String,
|
||||
var title: String,
|
||||
var maxStatusChars: Int = DEFAULT_MAX_TOOT_CHARS,
|
||||
// Per-file file-size limit in KB. Defaults to 15000 (15MB). Default limit for Mastodon is 8MB
|
||||
var maxPhotoSize: Int = DEFAULT_MAX_PHOTO_SIZE,
|
||||
// Mastodon has different file limits for videos, default of 40MB
|
||||
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
||||
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
||||
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
||||
) {
|
||||
companion object{
|
||||
// Default max number of chars for Mastodon: used when their is no other value supplied by
|
||||
// either NodeInfo or the instance endpoint
|
||||
const val DEFAULT_MAX_TOOT_CHARS = 500
|
||||
|
||||
const val DEFAULT_MAX_PHOTO_SIZE = 8000
|
||||
const val DEFAULT_MAX_VIDEO_SIZE = 40000
|
||||
const val DEFAULT_ALBUM_LIMIT = 4
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 95 KiB |
Binary file not shown.
Before Width: | Height: | Size: 92 KiB |
@ -1,11 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- The background color, preferably the same as your normal theme -->
|
||||
<item android:drawable="@android:color/white"/>
|
||||
<!-- Your product logo - 144dp color version of your app icon -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/index"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
<!-- The background color -->
|
||||
<item android:drawable="@android:color/black"/>
|
||||
<!-- The drawable (mascot) -->
|
||||
<item android:drawable="@drawable/ic_fred_phone"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- The background color, preferably the same as your normal theme -->
|
||||
<item android:drawable="@android:color/black"/>
|
||||
<!-- Your product logo - 144dp color version of your app icon -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/index_night"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
</layer-list>
|
Binary file not shown.
Before Width: | Height: | Size: 104 KiB |
Binary file not shown.
Before Width: | Height: | Size: 111 KiB |
Binary file not shown.
Before Width: | Height: | Size: 117 KiB |
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="30dp"
|
||||
android:height="30dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"
|
||||
android:fillColor="#757575"/>
|
||||
</vector>
|
7
app/src/main/res/drawable/add_photo_button.xml
Normal file
7
app/src/main/res/drawable/add_photo_button.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="false"
|
||||
android:drawable="@drawable/add_photo_alternate_gray_30dp" /> <!-- disabled -->
|
||||
<item android:drawable="@drawable/add_photo_alternate_white_30dp" /> <!-- default -->
|
||||
</selector>
|
||||
|
12
app/src/main/res/drawable/check_circle_24.xml
Normal file
12
app/src/main/res/drawable/check_circle_24.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="36dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path android:fillColor="@color/white" android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"/>
|
||||
|
||||
<path
|
||||
android:fillColor="@color/colorButtonBg"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
|
||||
</vector>
|
12
app/src/main/res/drawable/rounded_corner.xml
Normal file
12
app/src/main/res/drawable/rounded_corner.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#BE505050" />
|
||||
|
||||
<padding
|
||||
android:left="4dp"
|
||||
android:right="4dp"
|
||||
android:bottom="4dp"
|
||||
android:top="4dp" />
|
||||
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
9
app/src/main/res/drawable/selector_commenter.xml
Normal file
9
app/src/main/res/drawable/selector_commenter.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:drawable="@drawable/ic_comment_empty"
|
||||
android:state_pressed="false" />
|
||||
<item
|
||||
android:drawable="@drawable/ic_comment_blue"
|
||||
android:state_pressed="true"/>
|
||||
</selector>
|
@ -1,11 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- The background color, preferably the same as your normal theme -->
|
||||
<!-- The background color -->
|
||||
<item android:drawable="@android:color/white"/>
|
||||
<!-- Your product logo - 144dp color version of your app icon -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/index"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
<!-- The drawable (mascot) -->
|
||||
<item android:drawable="@drawable/ic_fred_phone"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- The background color, preferably the same as your normal theme -->
|
||||
<item android:drawable="@android:color/black"/>
|
||||
<!-- Your product logo - 144dp color version of your app icon -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/index_night"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
</layer-list>
|
@ -1,37 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".posts.PostActivity">
|
||||
tools:context=".posts.PostActivity"
|
||||
android:id="@+id/scrollview">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/postProgressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/postFragmentSingle"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:context=".posts.PostFragment"
|
||||
tools:visibility="visible"/>
|
||||
</ScrollView>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<include layout="@layout/post_fragment"
|
||||
android:id="@+id/postFragmentSingle"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commentIn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintTop_toBottomOf="@+id/postFragmentSingle"
|
||||
tools:layout_editor_absoluteX="10dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editComment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/comment"
|
||||
android:inputType="text"
|
||||
android:importantForAutofill="no" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/submitComment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/submit_comment"
|
||||
android:src="@drawable/ic_send_blue" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/commentIn">
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
@ -8,14 +8,15 @@
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/upload_error"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:elevation="2dp"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="2dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.mikepenz.iconics.view.IconicsTextView
|
||||
android:id="@+id/upload_error_text_view"
|
||||
@ -30,15 +31,29 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.mikepenz.iconics.view.IconicsTextView
|
||||
android:id="@+id/upload_error_text_explanation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#90000000"
|
||||
tools:text="Error code returned by server: 413"
|
||||
android:textColor="@color/colorPrimaryError"
|
||||
android:textSize="20sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upload_error_text_view"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/retry_upload_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/retry"
|
||||
app:layout_constraintEnd_toEndOf="@id/upload_error_text_view"
|
||||
app:layout_constraintHorizontal_bias="0.498"
|
||||
app:layout_constraintStart_toStartOf="@id/upload_error_text_view"
|
||||
app:layout_constraintTop_toBottomOf="@id/upload_error_text_view" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/upload_error_text_explanation" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -47,6 +62,7 @@
|
||||
android:id="@+id/carousel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:showCaption="true"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/uploadProgressBar"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
@ -134,7 +150,7 @@
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/toolbar3"
|
||||
android:id="@+id/toolbarPostCreation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#40000000"
|
||||
@ -191,7 +207,7 @@
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/add_photo"
|
||||
android:tooltipText='@string/add_photo'
|
||||
android:src="@drawable/add_photo_alternate_white_30dp"
|
||||
android:src="@drawable/add_photo_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -12,6 +12,6 @@
|
||||
android:layout_height="50dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/add_photo_alternate_white_30dp"
|
||||
android:background="@drawable/add_photo_button"
|
||||
android:contentDescription="@string/add_photo" />
|
||||
</com.h.pixeldroid.postCreation.SquareLayout>
|
||||
|
@ -8,8 +8,9 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageImageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foregroundTint="#FFFFFF">
|
||||
android:foregroundTint="#000000">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
@ -25,6 +25,7 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="marquee"
|
||||
android:text="@string/no_media_description"
|
||||
android:gravity="center"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:singleLine="true"
|
||||
@ -37,6 +38,49 @@
|
||||
app:layout_goneMarginBottom="8dp"
|
||||
tools:text="@tools:sample/lorem[5]" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/editMediaDescriptionLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:background="#4D000000"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/indicator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_goneMarginBottom="8dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextMediaDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:gravity="start|top"
|
||||
android:hint="@string/no_media_description"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textMultiLine"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageDescriptionButton"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/imageDescriptionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/save_image_description"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/editTextMediaDescription"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/check_circle_24" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
<me.relex.circleindicator.CircleIndicator2
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="wrap_content"
|
||||
@ -45,21 +89,48 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/previous_button_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_previous"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:backgroundTint="#22000000"
|
||||
app:cornerRadius="48dp"
|
||||
app:icon="@drawable/ic_chevron_left_black_24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="48dp"
|
||||
app:iconTint="@color/white"
|
||||
app:rippleColor="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/recyclerView" />
|
||||
app:layout_constraintTop_toTopOf="@+id/recyclerView"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/next_button_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_next"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:backgroundTint="#22000000"
|
||||
app:cornerRadius="48dp"
|
||||
app:icon="@drawable/ic_chevron_right_black_24dp"
|
||||
app:iconGravity="textEnd"
|
||||
app:iconSize="48dp"
|
||||
app:iconTint="@color/white"
|
||||
app:rippleColor="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/recyclerView" />
|
||||
app:layout_constraintTop_toTopOf="@+id/recyclerView"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/switchToGridButton"
|
||||
@ -81,13 +152,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/switch_to_carousel"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:src="@drawable/view_carousel_black_24dp"
|
||||
android:tint="@color/white"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/indicator"
|
||||
app:layout_constraintTop_toTopOf="@+id/indicator" />
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/indicator"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/btn_next"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:backgroundTint="#22000000"
|
||||
app:cornerRadius="48dp"
|
||||
app:icon="@drawable/ic_chevron_right_black_24dp"
|
||||
app:iconGravity="textEnd"
|
||||
app:iconSize="48dp"
|
||||
app:iconTint="@color/white"
|
||||
app:rippleColor="@color/white" />
|
@ -6,8 +6,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".posts.PostFragment">
|
||||
xmlns:sparkbutton="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -56,59 +55,59 @@
|
||||
android:layout_marginTop="10dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/profilePic">
|
||||
|
||||
<com.h.pixeldroid.posts.NestedScrollableHost
|
||||
android:id="@+id/postPagerHost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/postPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/postTabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/postPager"
|
||||
app:tabMode="auto" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/postPicture"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_height="200dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@color/browser_actions_bg_grey"
|
||||
tools:ignore="ContentDescription" />
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</com.h.pixeldroid.posts.NestedScrollableHost>
|
||||
|
||||
<me.relex.circleindicator.CircleIndicator3
|
||||
android:id="@+id/postIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/postPagerHost"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/post_fragment_image_popup_menu_anchor"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/postPicture"
|
||||
app:layout_constraintEnd_toEndOf="@+id/postPicture"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/postPagerHost"
|
||||
app:layout_constraintEnd_toEndOf="@+id/postPagerHost"
|
||||
app:layout_constraintHorizontal_bias="0.1"
|
||||
app:layout_constraintStart_toStartOf="@+id/postPicture"
|
||||
app:layout_constraintTop_toTopOf="@+id/postPicture"
|
||||
app:layout_constraintStart_toStartOf="@+id/postPagerHost"
|
||||
app:layout_constraintTop_toTopOf="@+id/postPagerHost"
|
||||
app:layout_constraintVertical_bias="0.1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sensitiveWarning"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:gravity="center|center_horizontal|center_vertical"
|
||||
android:text="@string/cw_nsfw_hidden_media_n_click_to_show"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="@color/ic_launcher_background"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/postPicture"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/postPagerHost"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/postTabs"
|
||||
app:layout_constraintTop_toTopOf="@+id/postPagerHost"
|
||||
tools:src="@color/browser_actions_bg_grey" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -119,7 +118,7 @@
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_comment_empty"
|
||||
android:src="@drawable/selector_commenter"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/liker"
|
||||
app:layout_constraintEnd_toStartOf="@id/reblogger"
|
||||
app:layout_constraintStart_toEndOf="@id/liker"
|
||||
@ -227,49 +226,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/commentIn"
|
||||
tools:text="3 comments" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commentIn"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintTop_toBottomOf="@+id/postDate"
|
||||
tools:layout_editor_absoluteX="10dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editComment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/comment"
|
||||
android:inputType="text"
|
||||
android:importantForAutofill="no" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/submitComment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/submit_comment"
|
||||
android:src="@drawable/ic_send_blue" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/viewComments">
|
||||
|
||||
</LinearLayout>
|
||||
tools:text="3 comments" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/btn_previous"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:padding="0dp"
|
||||
app:backgroundTint="#22000000"
|
||||
app:cornerRadius="48dp"
|
||||
app:icon="@drawable/ic_chevron_left_black_24dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconSize="48dp"
|
||||
app:iconTint="@color/white"
|
||||
app:rippleColor="@color/white" />
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/whats_an_instance_explanation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:autoLink="web"
|
||||
android:padding="16dp"
|
||||
android:text="@string/whats_an_instance_explanation" />
|
@ -47,19 +47,13 @@
|
||||
<string name="light_theme">فاتح</string>
|
||||
<string name="save_image_success">تم حفظ الصورة بنجاح</string>
|
||||
<string name="default_system">افتراضي (يتبع النظام)</string>
|
||||
<string name="description_max_characters">يجب أن يحتوي الوصف على %1$s حرفًا على الأكثر.</string>
|
||||
<string name="upload_post_success">تم تحميل المنشور بنجاح</string>
|
||||
<string name="upload_post_failed">فشل في تحميل المنشور</string>
|
||||
<string name="upload_picture_failed">خطأ في تحميل الصورة!</string>
|
||||
<string name="loading_toast">حدث خلل اثناء التحميل</string>
|
||||
<string name="normal_filter">عادي</string>
|
||||
<string name="upload_post_error">فشل في تحميل المنشور</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nيتابعون</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nمتابع</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nمشاركة</string>
|
||||
|
||||
<string name="comment">تعليق</string>
|
||||
<string name="comment_posted">التعليق: تم نشر%1$s!</string>
|
||||
<string name="comment_error">خطأ في التعليق!</string>
|
||||
@ -68,8 +62,6 @@
|
||||
<string name="write_permission_download_pic">تحتاج إلى منح إذن الكتابة لتنزيل الصور!</string>
|
||||
<string name="empty_comment">لا يجب ان يكون التعليق فارغًا!</string>
|
||||
<string name="posted_on">نُشِر في %1$s</string>
|
||||
<string name="shares">%1$s مشاركات</string>
|
||||
<string name="likes">%1$s إعجاب</string>
|
||||
<string name="no_description">مِن دون وصف</string>
|
||||
<string name="feed_failed">تعذّر جلب التدفق</string>
|
||||
<string name="retry">حاول مجدّدًا</string>
|
||||
|
@ -41,7 +41,6 @@
|
||||
<string name="add_account_description">Afegeix un altre compte</string>
|
||||
<string name="add_account_name">Afegeix un compte</string>
|
||||
<string name="instance_error">No s\'ha pogut obtenir informació sobre la instància</string>
|
||||
<string name="shares">%1$s Accions</string>
|
||||
<string name="hashtags">HASHTAGS</string>
|
||||
<string name="accounts">COMPTES</string>
|
||||
<string name="posts">PUBLICACIONS</string>
|
||||
@ -61,12 +60,6 @@
|
||||
<string name="follow_error">No s\'ha pogut seguir</string>
|
||||
<string name="follow_button_failed">No s\'ha pogut mostrar el botó de seguir</string>
|
||||
<string name="follow_status_failed">No s\'ha pogut obtenir l\'estat de seguiment</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nSeguits/des</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nSeguidors/es</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nPublicacions</string>
|
||||
<string name="comment">Comentari</string>
|
||||
<string name="comment_posted">Comentari: %1$s publicat!</string>
|
||||
<string name="comment_error">Error de comentari!</string>
|
||||
@ -75,7 +68,6 @@
|
||||
<string name="write_permission_download_pic">Has de concedir permís d’escriptura per baixar imatges!</string>
|
||||
<string name="empty_comment">El comentari no ha de estar buit!</string>
|
||||
<string name="posted_on">Publica\'t el %1$s</string>
|
||||
<string name="likes">%1$s M\'agrades</string>
|
||||
<string name="no_description">Sense descripció</string>
|
||||
<string name="feed_failed">No s\'ha pogut obtenir el fil</string>
|
||||
<string name="loading_toast">Alguna cosa no ha funcionat correctament mentre es carregava</string>
|
||||
@ -86,7 +78,6 @@
|
||||
<string name="request_format_error">Error d\'enviament: format de sol·licitud dolent</string>
|
||||
<string name="picture_format_error">Error d\'enviament: format d\'imatge erroni.</string>
|
||||
<string name="upload_picture_failed">Error d\'enviament d\'imatges!</string>
|
||||
<string name="description_max_characters">La descripció ha de contenir %1$s caràcters com a mínim.</string>
|
||||
<string name="save_image_success">La imatge s\'ha desat correctament</string>
|
||||
<string name="save_image_failed">No es pot desar la imatge</string>
|
||||
<string name="permission_denied">Permís denegat</string>
|
||||
|
@ -43,7 +43,6 @@
|
||||
<string name="default_system">Standard (Systemeinstellung)</string>
|
||||
<string name="permission_denied">Berechtigung verweigert</string>
|
||||
<string name="save_image_failed">Bild kann nicht gespeichert werden</string>
|
||||
<string name="description_max_characters">Die Beschreibung darf höchstens %1$s Zeichen enthalten.</string>
|
||||
<string name="normal_filter">Normal</string>
|
||||
<string name="save_image_success">Bild erfolgreich gespeichert</string>
|
||||
<string name="picture_format_error">Upload-Fehler: falsches Bildformat.</string>
|
||||
@ -55,8 +54,6 @@
|
||||
<string name="comment">Kommentieren</string>
|
||||
<string name="share_image">Bild teilen</string>
|
||||
<string name="empty_comment">Der Kommentar darf nicht leer sein!</string>
|
||||
<string name="shares">%1$s Geteilt</string>
|
||||
<string name="likes">%1$s Likes</string>
|
||||
<string name="no_description">Keine Beschreibung</string>
|
||||
<string name="loading_toast">Beim Laden ist etwas schiefgegangen</string>
|
||||
<string name="search">Suchen</string>
|
||||
@ -66,12 +63,6 @@
|
||||
<string name="upload_post_error">Fehler beim Senden des Beitrags</string>
|
||||
<string name="comment_error">Kommentarfehler!</string>
|
||||
<string name="comment_posted">Kommentar: %1$s geschrieben!</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nBeiträge</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nFollower</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nFolgt</string>
|
||||
<string name="follow_button_failed">Konnte Knopf zum Folgen nicht anzeigen</string>
|
||||
<string name="follow_error">Konnte nicht folgen</string>
|
||||
<string name="no_username">Kein Benutzername</string>
|
||||
@ -142,11 +133,32 @@
|
||||
<string name="post_is_album">Dieser Beitrag ist ein Album</string>
|
||||
<string name="submit_comment">Kommentar senden</string>
|
||||
<string name="add_comment">Erstelle einen Kommentar</string>
|
||||
<string name="number_comments">%1$s Kommentare</string>
|
||||
<string name="crop_button">Schaltfläche zum ausschneiden oder rotieren des Bildes</string>
|
||||
<string name="image_preview">Vorschau des bearbeiteten Bildes</string>
|
||||
<string name="filter_thumbnail">Vorschaubild des Filters</string>
|
||||
<string name="click_image_edit">Klicke auf das Bild um es zu ändern</string>
|
||||
<string name="post_image">Eines der Bilder im Beitrag</string>
|
||||
<string name="verify_credentials">Benutzerdaten konnten nicht geladen werden</string>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">%d Beitrag</item>
|
||||
<item quantity="other">%d Beiträge</item>
|
||||
</plurals>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d Kommentar</item>
|
||||
<item quantity="other">%d Kommentar</item>
|
||||
</plurals>
|
||||
<string name="no_media_description">Ergänze hier eine Medienbeschreibung hier…</string>
|
||||
<string name="save_image_description">Bildbeschreibung speichern</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">Die Beschreibung muss mindestens %d Zeichen enthalten.</item>
|
||||
<item quantity="other">Die Beschreibung muss mindestens %d Zeichen enthalten.</item>
|
||||
</plurals>
|
||||
<string name="whats_an_instance_explanation">Das Textfeld, mit der Frage nach der Domain deiner Instanz, hat dich vielleicht verwirrt.
|
||||
\n
|
||||
\n
|
||||
\nPixelfed ist eine Föderierte Platform und Teil des \"Fediverse\". Das bedeutet, dass es mit anderen Plattformen kommunizieren kann, wie Mastodon (https://joinmastodon.org).
|
||||
\n
|
||||
\nEs bedeutend auch, dass du wählen musst welchen Server, oder \"Pixelfed-Instanz\", du nutzen möchtest.
|
||||
\nWenn du keine kennst kannst u hier nachsehen: https://pixelfed.org/join
|
||||
\n
|
||||
\nMehr Informationen zu Pixelfed findest du hier: https://pixelfed.org</string>
|
||||
</resources>
|
@ -64,12 +64,6 @@
|
||||
<string name="follow_error">No pudo seguir</string>
|
||||
<string name="follow_button_failed">No se pudo mostrar el botón de seguimiento</string>
|
||||
<string name="follow_status_failed">No se pudo obtener el estado de seguidores</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nSeguidos</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nSeguidores</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nPublicaciones</string>
|
||||
<string name="comment">Comentar</string>
|
||||
<string name="comment_posted">¡Comentario: %1$s publicado!</string>
|
||||
<string name="comment_error">¡Error al comentar!</string>
|
||||
@ -78,8 +72,6 @@
|
||||
<string name="write_permission_download_pic">¡Tienes que dar permiso de escritura para descargar fotos!</string>
|
||||
<string name="empty_comment">¡Los comentarios no deben estar vacíos!</string>
|
||||
<string name="posted_on">Publicado en %1$s</string>
|
||||
<string name="shares">%1$s Comparticiones</string>
|
||||
<string name="likes">%1$s Me gustas</string>
|
||||
<string name="no_description">Sin descripción</string>
|
||||
<string name="feed_failed">No se pudieron obtener publicaciones</string>
|
||||
<string name="loading_toast">Algo salió mal al cargar</string>
|
||||
@ -90,7 +82,6 @@
|
||||
<string name="request_format_error">Error de carga: formato de petición erróneo</string>
|
||||
<string name="picture_format_error">Error de carga: formato de imagen incorrecto.</string>
|
||||
<string name="upload_picture_failed">¡Error al subir la foto!</string>
|
||||
<string name="description_max_characters">La descripción debe contener %1$s caracteres como máximo.</string>
|
||||
<string name="save_image_success">Imagen guardada correctamente</string>
|
||||
<string name="save_image_failed">No se puede guardar la imagen</string>
|
||||
<string name="permission_denied">Permiso denegado</string>
|
||||
|
@ -49,10 +49,6 @@
|
||||
\nJarraitzaile</string>
|
||||
<string name="default_nposts">-
|
||||
\nArgitalpen</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nJarraitzen</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nJarraitzaile</string>
|
||||
<string name="share_image">Irudia partekatu</string>
|
||||
<string name="upload_picture_failed">Ezin izan da irudia kargatu!</string>
|
||||
<string name="save_image_failed">Ezinezkoa irudia gordetzea</string>
|
||||
@ -60,16 +56,13 @@
|
||||
<string name="light_theme">Argia</string>
|
||||
<string name="default_system">Lehenetsia (Sistemaren lehentsia)</string>
|
||||
<string name="no_description">Deskribapenik ez</string>
|
||||
<string name="description_max_characters">Deskribapenak %1$s karaktere izan behar ditu gehienez.</string>
|
||||
<string name="save_image_success">Irudia behar bezala gorde da</string>
|
||||
<string name="feed_failed">Ezin izan da jarioa lortu</string>
|
||||
<string name="loading_toast">Zerbait gaizki joan da kargatzean</string>
|
||||
<string name="shares">%1$s Partekatze</string>
|
||||
<string name="write_permission_download_pic">Idazteko baimena eman behar duzu irudiak deskargatzeko!</string>
|
||||
<string name="empty_comment">Iruzkina ez da hutsik egon behar!</string>
|
||||
<string name="likes">%1$s Atsegite</string>
|
||||
<string name="normal_filter">Normala</string>
|
||||
<string name="upload_post_error">Argitalpenak huts egin du</string>
|
||||
<string name="upload_post_error">Argitalpena igotzean errorea</string>
|
||||
<string name="comment_posted">Iruzkina: %1$s argitaratua!</string>
|
||||
<string name="upload_post_success">Argitalpena ongi kargatu da</string>
|
||||
<string name="media_upload_failed">{gmd_cloud_off} Multimedia-kargak huts egin du, saiatu berriro edo egiaztatu sareko egoera</string>
|
||||
@ -80,7 +73,7 @@
|
||||
<string name="permission_denied">Baimena ukatuta</string>
|
||||
<string name="retry">Saiatu berriz</string>
|
||||
<string name="posting_image_accessibility_hint">Argitaratzen ari den irudia</string>
|
||||
<string name="edit_profile">Profila editatu</string>
|
||||
<string name="edit_profile">Editatu profila</string>
|
||||
<string name="no_username">Erabiltzaile-izenik ez</string>
|
||||
<string name="access_token_invalid">Sartzeko tokena baliogabea</string>
|
||||
<string name="unfollow_error">Ezin izan da jarraitzeari utzi</string>
|
||||
@ -88,13 +81,98 @@
|
||||
<string name="follow_error">Ezin izan da jarraitu</string>
|
||||
<string name="follow_button_failed">Ezin izan da jarraipen-botoia erakutsi</string>
|
||||
<string name="follow_status_failed">Ezin izan da segimendu-egoera lortu</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nArgitalpen</string>
|
||||
<string name="comment">Iruzkin</string>
|
||||
<string name="write_permission_share_pic">Idazteko baimena eman behar duzu argazkiak partekatzeko!</string>
|
||||
<string name="posted_on">%1$s(e)n argitaratua</string>
|
||||
<string name="hashtags">TRAOLAK</string>
|
||||
<string name="accounts">KONTUAK</string>
|
||||
<string name="media_upload_completed">{gmd_cloud_done} Multimedia karga burutu da</string>
|
||||
<string name="upload_post_failed">Argitalpenak huts egin du</string>
|
||||
<string name="media_upload_completed">{gmd_cloud_done} Multimedia karga amaitu da</string>
|
||||
<string name="upload_post_failed">Argitalpena igotzeak huts egin du</string>
|
||||
<string name="no_cancel_edit">Ez, utzi editatzeari</string>
|
||||
<string name="save_before_returning">Zure edizioak gorde\?</string>
|
||||
<string name="mascot_description">Panda gorri bat, Pixelfeden maskota, mugikor bat erabiltzen erakusten duen irudia</string>
|
||||
<string name="issues_contribute">Jakinarazi arazoak edo aplikazioan lagundu:</string>
|
||||
<string name="help_translate">Lagundu PixelDroid zure hizkuntzara itzultzen:</string>
|
||||
<string name="language">Hizkuntza</string>
|
||||
<string name="delete_dialog">Argitalpen hau ezabatu\?</string>
|
||||
<string name="delete">Ezabatu</string>
|
||||
<string name="panda_pull_to_refresh_to_try_again">Panda hau ez dago pozik. Tira freskatzeko berriz saiatzeko.</string>
|
||||
<string name="something_went_wrong">Arazoren bat izan da…</string>
|
||||
<string name="discover">AURKITU</string>
|
||||
<string name="open_drawer_menu">Ireki menu lerrakorra</string>
|
||||
<string name="profile_picture">Profileko irudia</string>
|
||||
<string name="toolbar_title_edit">Editatu</string>
|
||||
<string name="report_error">Ezin izan da salaketa bidali</string>
|
||||
<string name="reported">Salatua {gmd_check_circle}</string>
|
||||
<string name="report_target">Salatu @%1$s erabiltzailearen argitalpena</string>
|
||||
<string name="optional_report_comment">Moderatzaile/administratzaileentzako hautazko mezua</string>
|
||||
<string name="share_link">Partekatu esteka</string>
|
||||
<string name="report">Salatu</string>
|
||||
<string name="status_more_options">Aukera gehiago</string>
|
||||
<string name="search_empty_error">Bilaketak ezin du hutsik egon</string>
|
||||
<string name="follows_title">%1$s erabiltzaileak jarraituak</string>
|
||||
<string name="followers_title">%1$s erabiltzailearen jarraitzaileak</string>
|
||||
<string name="post_title">%1$s erabiltzailearen argitalpena</string>
|
||||
<string name="about">Honi buruz</string>
|
||||
<string name="license_info">PixelDroid software libre eta kode irekikoa da, GNU General Public License (3 bertsioa edo berriagoa) lizentzia duena</string>
|
||||
<string name="project_website">Proiektuaren webgunea: https://pixeldroid.org</string>
|
||||
<string name="dependencies_licenses">Menpekotasunak eta lizentziak</string>
|
||||
<string name="about_pixeldroid">PixelDroidi buruz</string>
|
||||
<string name="nothing_to_see_here">Hemen ez dago ezer!</string>
|
||||
<string name="unfollow">Utzi jarraitzeari</string>
|
||||
<plurals name="nb_following">
|
||||
<item quantity="one">%d jarraitzen</item>
|
||||
<item quantity="other">%d jarraitzen</item>
|
||||
</plurals>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">Jarraitzaile %d</item>
|
||||
<item quantity="other">%d jarraitzaile</item>
|
||||
</plurals>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">Argitalpen %d</item>
|
||||
<item quantity="other">%d argitalpen</item>
|
||||
</plurals>
|
||||
<string name="post_is_album">Argitalpen hau album bat da</string>
|
||||
<string name="submit_comment">Bidali iruzkina</string>
|
||||
<string name="add_comment">Gehitu iruzkin bat</string>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">Iruzkin %d</item>
|
||||
<item quantity="other">%d iruzkin</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">Partekatze %d</item>
|
||||
<item quantity="other">%d partekatze</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">Gogoko %d</item>
|
||||
<item quantity="other">%d gogoko</item>
|
||||
</plurals>
|
||||
<string name="crop_button">Irudia moztu edo biratzeko botoia</string>
|
||||
<string name="image_preview">Editatutako irudiaren aurrebista</string>
|
||||
<string name="crop_result_error">Ezin izan da irudia berreskuratu moztu ondoren</string>
|
||||
<string name="busy_dialog_ok_button">Ados, itxaron.</string>
|
||||
<string name="busy_dialog_text">Oraindik irudia prozesatzen ari da, itxaron amaitu arte!</string>
|
||||
<string name="filter_thumbnail">Iragazkiaren koadro txikia</string>
|
||||
<string name="no_media_description">Gehitu multimediaren deskribapena hemen…</string>
|
||||
<string name="save_image_description">Gorde irudiaren deskribapena</string>
|
||||
<string name="switch_to_carousel">Aldatu karruselera</string>
|
||||
<string name="switch_to_grid">Aldatu sareta-ikuspegira</string>
|
||||
<string name="post_image">Argitalpeneko irudietako bat</string>
|
||||
<string name="add_photo">Gehitu argazki bat</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">Deskribapenak karaktere %d izan behar du gehienez.</item>
|
||||
<item quantity="other">Deskribapenak %d karaktere izan behar ditu gehienez.</item>
|
||||
</plurals>
|
||||
<string name="whats_an_instance_explanation">Behar bada zure \'instantziaren\' domeinua eskatzen dizun testu-eremua nahasgarria iruditu zaizu.
|
||||
\n
|
||||
\nPixelfed plataforma federatu bat da, eta \'fedibertsoaren\' parte da. Horrek esan nahi du hizkuntza bera erabiltzen duten beste plataforma batzuekin hitz egin dezakeela, adibidez Mastodonekin (ikusi https://joinmastodon.org).
|
||||
\n
|
||||
\nHorregatik Pixelfeden ze zerbitzari, edo \'instantzia\', erabili nahi duzun aukeratu behar duzu. Ez baduzu bat ere ezagutzen, hemen begira dezakezu: https://pixelfed.org/join
|
||||
\n
|
||||
\nPixelfedi buruzko informazio gehiago nahi baduzu: https://pixelfed.org</string>
|
||||
<string name="poll_notification">%1$s erabiltzailearen inkesta amaitu da</string>
|
||||
<string name="instance_not_pixelfed_cancel">Utzi saioa hastea</string>
|
||||
<string name="instance_not_pixelfed_continue">Ados, jarraitu hala ere</string>
|
||||
<string name="instance_not_pixelfed_warning">Honek ez dirudi Pixelfed instantzia bat, aplikazioa ustekabeko moduetan huts egin dezake.</string>
|
||||
<string name="verify_credentials">Ezin izan da erabiltzailearen informazioa eskuratu</string>
|
||||
</resources>
|
@ -43,7 +43,6 @@
|
||||
<string name="instance_error">نتوانستیم اطلاعات نمونه را دریافت کنیم</string>
|
||||
<string name="permission_denied">ممنوعیت دسترسی</string>
|
||||
<string name="save_image_failed">نتوانستیم تصویر را ذخیره کنیم</string>
|
||||
<string name="description_max_characters">توضیحات حداکثر میتوانند تا %1$s حرف داشته باشند.</string>
|
||||
<string name="default_system">پیشگزیده (پیروی از سامانه)</string>
|
||||
<string name="light_theme">روشن</string>
|
||||
<string name="dark_theme">تاریک</string>
|
||||
@ -58,10 +57,8 @@
|
||||
<string name="loading_toast">مشکلی حین بارکردن رخ داد</string>
|
||||
<string name="feed_failed">نتوانستیم خوراک را دریافت کنیم</string>
|
||||
<string name="no_description">بدون توضیحات</string>
|
||||
<string name="likes">%1$s پسند</string>
|
||||
<string name="accounts">حسابها</string>
|
||||
<string name="hashtags">هشتگها</string>
|
||||
<string name="shares">%1$s همرسانی</string>
|
||||
<string name="posted_on">منتشر شده در %1$s</string>
|
||||
<string name="empty_comment">نظر نمیتواند خالی باشد!</string>
|
||||
<string name="write_permission_download_pic">برای بارگیری تصاویر بایستی مجوز نوشتن را بدهید!</string>
|
||||
@ -70,12 +67,6 @@
|
||||
<string name="comment_error">خطا در درج نظر!</string>
|
||||
<string name="comment_posted">نظر: %1$s منتشر کرد!</string>
|
||||
<string name="comment">نظر</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nمطلب</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nپیگیرنده</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nپیگیری</string>
|
||||
<string name="follow_status_failed">نتوانستیم وضعیت پیگیری را دریافت کنیم</string>
|
||||
<string name="follow_button_failed">نتوانستیم دکمه پیگیری را نمایش دهیم</string>
|
||||
<string name="follow_error">نتوانستیم پیگیری کنیم</string>
|
||||
@ -137,11 +128,55 @@
|
||||
<string name="post_is_album">این فرسته، یک آلبوم است</string>
|
||||
<string name="submit_comment">فرستادن نظر</string>
|
||||
<string name="add_comment">نظری اضافه کنید</string>
|
||||
<string name="number_comments">%1$s نظر</string>
|
||||
<string name="crop_button">دکمه برش یا چرخش تصویر</string>
|
||||
<string name="image_preview">پیشنمایش تصویری که در حال ویرایش است</string>
|
||||
<string name="filter_thumbnail">تصویر بندانگشتی پالایه</string>
|
||||
<string name="click_image_edit">برای ویرایش تصویر، روی آن کلیک کنید</string>
|
||||
<string name="post_image">یکی از تصاویر در فرسته</string>
|
||||
<string name="verify_credentials">ناتوانی در دریافت اطلاعات کاربر</string>
|
||||
<string name="mascot_description">تصویر، یک پاندای قرمز که شگونهٔ پیکسلفد است را گوشی به دست نشان میدهد</string>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">%d فرسته</item>
|
||||
<item quantity="other">%d فرسته</item>
|
||||
</plurals>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d نظر</item>
|
||||
<item quantity="other">%d نظر</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d همرسانی</item>
|
||||
<item quantity="other">%d همرسانی</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d پسند</item>
|
||||
<item quantity="other">%d پسند</item>
|
||||
</plurals>
|
||||
<string name="switch_to_carousel">تغییر وضعیت به نمای گردونه</string>
|
||||
<string name="no_cancel_edit">خیر، ویرایش لغو شود</string>
|
||||
<string name="save_before_returning">ویرایشها ذخیره شوند؟</string>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">%d پیگیر</item>
|
||||
<item quantity="other">%d پیگیر</item>
|
||||
</plurals>
|
||||
<plurals name="nb_following">
|
||||
<item quantity="one">%d پیگرفته</item>
|
||||
<item quantity="other">%d پیگرفته</item>
|
||||
</plurals>
|
||||
<string name="no_media_description">این جا توضیحات رسانه را وارد کنید…</string>
|
||||
<string name="save_image_description">ذخیره توضیحات تصویر</string>
|
||||
<string name="switch_to_grid">تغییر وضعیت به نمای شبکهای</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">توضیحات باید در بیشترین حالت حاوی %d حرف باشد.</item>
|
||||
<item quantity="other">توضیحات باید در بیشترین حالت حاوی %d حرف باشد.</item>
|
||||
</plurals>
|
||||
<string name="whats_an_instance_explanation">ممکن است از دیدن بخشی که از شما میخواهد که دامنه مربوط به «نمونه» را وارد کنید گیج شده باشید.
|
||||
\n
|
||||
\nپیکسلفد یک بستر فدرالیزه و عضوی از«فدیورس» (دنیای شبکههای فدرالیزه) است به این معنا که میتواند با سایر بسترها مانند ماستودون (https://joinmastodon.org را ببینید) که زبان مشترکی دارند صحبت کرده و ارتباط برقرار کند.
|
||||
\n
|
||||
\nمعنای دیگر این موضوع آن است که شما باید انتخاب کنید که از کدام کارساز یا «نمونه» از پیکسلفد استفاده میکنید. اگر چیزی در این باره نمیدانید میتوانید به این نشانی مراجعه کنید: https://pixelfed.org/join
|
||||
\n
|
||||
\nبرای کسب اطلاعات بیشتر درباره پیکسلفد میتوانید این نشانی را ببینید: https://pixelfed.org</string>
|
||||
<string name="upload_error">کد خطای دریافت شده از کارساز: %1$s</string>
|
||||
<string name="size_exceeds_instance_limit">اندازه تصویر شماره %1$s در آلبوم از بیشینه اندازه مجاز در نمونه (%2$s کیلوبایت در مقابل آستانه مجاز %3$s کیلوبایت) تجاوز رده است.</string>
|
||||
<string name="total_exceeds_album_limit">تعداد تصاویر انتخاب شده شما بیش از بیشینه مجاز روی کارساز است (%1$s). تصاویر بیش از آن تعداد، نادیده گرفته شدهاند.</string>
|
||||
<string name="api_not_enabled_dialog">روی این نمونه، API فعال نیست. با مدیر نمونه تماس گرفته و از او بخواهید آن را فعال کند.</string>
|
||||
</resources>
|
@ -12,13 +12,13 @@
|
||||
<string name="post">envoyer</string>
|
||||
<string name="whats_an_instance">Qu\'est-ce qu\'une instance \?</string>
|
||||
<string name="logout">Se déconnecter</string>
|
||||
<string name="save_to_gallery">Sauvegarder dans la galerie…</string>
|
||||
<string name="save_to_gallery">Enregistrer dans la galerie…</string>
|
||||
<string name="image_download_downloading">En cours de téléchargement…</string>
|
||||
<string name="image_download_success">Image téléchargée avec succès</string>
|
||||
<string name="share_picture">Partager l\'image…</string>
|
||||
<string name="invalid_domain">Domaine invalide</string>
|
||||
<string name="image_download_failed">Le téléchargement a échoué, veuillez réessayer</string>
|
||||
<string name="browser_launch_failed">Impossible de lancer un navigateur, en avez-vous un \?</string>
|
||||
<string name="browser_launch_failed">Impossible de lancer un navigateur web, en avez-vous un \?</string>
|
||||
<string name="mention_notification">%1$s vous a mentionné</string>
|
||||
<string name="app_name">PixelDroid</string>
|
||||
<string name="token_error">Erreur lors de l\'obtention du token</string>
|
||||
@ -26,17 +26,17 @@
|
||||
<string name="theme_title">Thème de l’application</string>
|
||||
<string name="theme_header">Thème</string>
|
||||
<string name="lbl_brightness">Luminosité</string>
|
||||
<string name="lbl_contrast">CONTRASTE</string>
|
||||
<string name="lbl_contrast">Contraste</string>
|
||||
<string name="lbl_saturation">Saturation</string>
|
||||
<string name="tab_filters">Filtres</string>
|
||||
<string name="edit">MODIFIER</string>
|
||||
<string name="edit">Modifier</string>
|
||||
<string name="capture_button_alt">Prendre une photo</string>
|
||||
<string name="switch_camera_button_alt">Changer de caméra</string>
|
||||
<string name="gallery_button_alt">Galerie</string>
|
||||
<string name="NoCommentsToShow">Pas de commentaire pour cette publication…</string>
|
||||
<string name="NoCommentsToShow">Aucun commentaire pour cette publication…</string>
|
||||
<string name="domain_of_your_instance">Domaine de votre instance</string>
|
||||
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Media caché
|
||||
\n(Cliquer pour le montrer)</string>
|
||||
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Média caché
|
||||
\n(Cliquer pour l\'afficher)</string>
|
||||
<string name="login_connection_required_once">Vous devez être connecté à internet pour pouvoir ajouter un compte et utiliser PixelDroid :(</string>
|
||||
<string name="add_account_description">Ajouter un autre compte Pixelfed</string>
|
||||
<string name="add_account_name">Ajouter un compte Pixelfed</string>
|
||||
@ -44,9 +44,8 @@
|
||||
<string name="request_format_error">Erreur de téléchargement : format de requête incorrect</string>
|
||||
<string name="picture_format_error">Erreur de téléversement : format d\'image incorrect.</string>
|
||||
<string name="upload_picture_failed">Erreur de téléversement d\'image !</string>
|
||||
<string name="description_max_characters">La description doit contenir au plus %1$s caractères.</string>
|
||||
<string name="save_image_success">Image enregistrée avec succès</string>
|
||||
<string name="save_image_failed">Impossible de sauvegarder l\'image</string>
|
||||
<string name="save_image_failed">Impossible d\'enregistrer l\'image</string>
|
||||
<string name="permission_denied">Permission refusée</string>
|
||||
<string name="dark_theme">Sombre</string>
|
||||
<string name="light_theme">Clair</string>
|
||||
@ -63,19 +62,11 @@
|
||||
<string name="loading_toast">Une erreur s\'est produite lors du chargement</string>
|
||||
<string name="feed_failed">Impossible d\'obtenir le flux</string>
|
||||
<string name="no_description">Sans description</string>
|
||||
<string name="likes">%1$s J\'aimes</string>
|
||||
<string name="shares">%1$s Partages</string>
|
||||
<string name="write_permission_download_pic">Vous devez accorder une autorisation d\'écriture pour télécharger des photos !</string>
|
||||
<string name="share_image">Partager image</string>
|
||||
<string name="comment_error">Erreur de commentaire !</string>
|
||||
<string name="comment_posted">Commentaire : %1$s publié !</string>
|
||||
<string name="comment">Commenter</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nPublications</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nAbonné·e·s</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nAbonnements</string>
|
||||
<string name="follow_status_failed">Impossible d\'obtenir l\'état de suivi</string>
|
||||
<string name="follow_button_failed">Impossible d\'afficher le bouton de suivi</string>
|
||||
<string name="follow_error">Impossible de suivre</string>
|
||||
@ -89,7 +80,7 @@
|
||||
<string name="no_username">Pas de nom d\'utilisateur</string>
|
||||
<string name="follow">S\'abonner</string>
|
||||
<string name="edit_profile">Modifier le profil</string>
|
||||
<string name="search">Chercher</string>
|
||||
<string name="search">Rechercher</string>
|
||||
<string name="posts">PUBLICATIONS</string>
|
||||
<string name="accounts">COMPTES</string>
|
||||
<string name="hashtags">HASHTAGS</string>
|
||||
@ -99,29 +90,28 @@
|
||||
<string name="media_upload_failed">{gmd_cloud_off} Échec du téléversement du média, réessayez ou vérifiez l\'état du réseau</string>
|
||||
<string name="crop_result_error">Impossible de récupérer l\'image après le recadrage</string>
|
||||
<string name="busy_dialog_ok_button">D\'accord, attendez pour ça.</string>
|
||||
<string name="busy_dialog_text">Toujours en train de traiter l\'image, attendez que cela se termine en premier !</string>
|
||||
<string name="busy_dialog_text">Toujours en train de traiter l\'image, attendez tout d\'abord que cela se termine !</string>
|
||||
<string name="nothing_to_see_here">Rien à voir ici !</string>
|
||||
<string name="issues_contribute">Rapporter des problèmes ou contribuer à l\'application :</string>
|
||||
<string name="issues_contribute">Signaler des problèmes ou contribuer à l\'application :</string>
|
||||
<string name="something_went_wrong">Il y a eu un problème…</string>
|
||||
<string name="reported">Rapporté {gmd_check_circle}</string>
|
||||
<string name="report_target">Rapporter la publication de @%1$s</string>
|
||||
<string name="report">Rapporter</string>
|
||||
<string name="reported">Signalé {gmd_check_circle}</string>
|
||||
<string name="report_target">Signaler la publication de @%1$s</string>
|
||||
<string name="report">Signaler</string>
|
||||
<string name="image_preview">Aperçu de l\'image en cours de modification</string>
|
||||
<string name="filter_thumbnail">Vignette d\'un filtre</string>
|
||||
<string name="click_image_edit">Appuyez sur l\'image pour la modifier</string>
|
||||
<string name="mascot_description">Image montrant un panda rouge (la mascotte de Pixelfed) qui utilise un téléphone</string>
|
||||
<string name="help_translate">Aidez pour traduire PixelDroid dans votre langue :</string>
|
||||
<string name="mascot_description">Image montrant un panda rouge (la mascotte de Pixelfed) utilisant un téléphone</string>
|
||||
<string name="help_translate">Aidez à traduire PixelDroid dans votre langue :</string>
|
||||
<string name="language">Langue</string>
|
||||
<string name="delete_dialog">Supprimer cette publication \?</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="discover">DÉCOUVRIR</string>
|
||||
<string name="profile_picture">Photo de profil</string>
|
||||
<string name="toolbar_title_edit">Modifier</string>
|
||||
<string name="report_error">Échec lors de l\'envoi du rapport</string>
|
||||
<string name="optional_report_comment">Message facultatif pour les mods/admins</string>
|
||||
<string name="report_error">Échec lors de l\'envoi du signalement</string>
|
||||
<string name="optional_report_comment">Message facultatif pour les modérateurs/admins</string>
|
||||
<string name="share_link">Partager le lien</string>
|
||||
<string name="status_more_options">Plus d\'options</string>
|
||||
<string name="search_empty_error">La requête de recherche ne peut pas être vide</string>
|
||||
<string name="search_empty_error">La recherche ne peut pas être vide</string>
|
||||
<string name="post_title">Publication de %1$s</string>
|
||||
<string name="about">À propos</string>
|
||||
<string name="license_info">PixelDroid est un logiciel libre, sous licence GNU General Public License (version 3 ou ultérieure)</string>
|
||||
@ -132,7 +122,6 @@
|
||||
<string name="post_is_album">Cette publication est un album</string>
|
||||
<string name="submit_comment">Envoyer le commentaire</string>
|
||||
<string name="add_comment">Ajouter un commentaire</string>
|
||||
<string name="number_comments">%1$s commentaires</string>
|
||||
<string name="crop_button">Bouton pour effectuer une rotation ou recadrement de l\'image</string>
|
||||
<string name="post_image">Une des images de la publication</string>
|
||||
<string name="add_photo">Ajouter une photo</string>
|
||||
@ -146,7 +135,48 @@
|
||||
<string name="save_before_returning">Enregistrer vos modifications \?</string>
|
||||
<string name="panda_pull_to_refresh_to_try_again">Ce panda est malheureux. Glissez pour actualiser et réessayer.</string>
|
||||
<string name="open_drawer_menu">Ouvrir le menu latéral</string>
|
||||
<string name="follows_title">%1$s abonnements</string>
|
||||
<string name="followers_title">%1$s abonnés</string>
|
||||
<string name="follows_title">Abonnements de %1$s</string>
|
||||
<string name="followers_title">Abonné·e·s de %1$s</string>
|
||||
<string name="verify_credentials">Impossible d\'obtenir les informations sur l\'utilisateur</string>
|
||||
<string name="size_exceeds_instance_limit">La taille de l\'image n°%1$s dans l\'album dépasse la taille maximale autorisée (%2$s ko mais la limite est %3$s ko). Il se peut que vous ne puissiez pas la télécharger.</string>
|
||||
<string name="upload_error">Le serveur a retourné le code d\'erreur : %1$s</string>
|
||||
<string name="total_exceeds_album_limit">Vous avez choisi plus d\'images que le maximum autorisé par votre serveur (%1$s). Les images au delà de la limite ont été ignorées.</string>
|
||||
<string name="no_media_description">Ajouter une description du média ici…</string>
|
||||
<string name="save_image_description">Enregistrer la description de l\'image</string>
|
||||
<string name="api_not_enabled_dialog">L\'API n\'est pas activée sur cette instance. Veuillez contacter votre administrateur pour lui demander de l\'activer.</string>
|
||||
<string name="whats_an_instance_explanation">Vous risquez d\'être déroutés par le champ demandant le domaine de votre \"instance\".
|
||||
\n
|
||||
\nPixelfed est une plateforme fédérée, et fait partie de \"fediverse\", ce qui signifie qu\'elle peut communiquer avec d\'autres plateformes qui parlent la même langue, comme Mastodon (voir https://joinmastodon.org).
|
||||
\n
|
||||
\nCela signifie également que vous devez choisir quel serveur ou \"instance\" de Pixelfed utiliser. Si vous n\'en connaissez aucun, vous pouvez regarder ici : https://pixelfed.org/join
|
||||
\n
|
||||
\nPour plus d\'informations sur Pixelfed, vous pouvez consulter le site suivant : https://pixelfed.org</string>
|
||||
<plurals name="nb_following">
|
||||
<item quantity="one">%d Abonnement</item>
|
||||
<item quantity="other">%d Abonnements</item>
|
||||
</plurals>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">%d Abonné·e</item>
|
||||
<item quantity="other">%d Abonné·e·s</item>
|
||||
</plurals>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">%d Publication</item>
|
||||
<item quantity="other">%d Publications</item>
|
||||
</plurals>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d commentaire</item>
|
||||
<item quantity="other">%d commentaires</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d Partage</item>
|
||||
<item quantity="other">%d Partages</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d J\'aime</item>
|
||||
<item quantity="other">%d J\'aime</item>
|
||||
</plurals>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">La description doit contenir au moins %d lettre.</item>
|
||||
<item quantity="other">La description doit contenir au moins %d lettres.</item>
|
||||
</plurals>
|
||||
</resources>
|
@ -61,12 +61,6 @@
|
||||
<string name="follow_error">Non se puido seguir</string>
|
||||
<string name="follow_button_failed">Non se pode mostrar o botón para seguir</string>
|
||||
<string name="follow_status_failed">Non se obtivo o estado de relacións</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nSeguindo</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nSeguidoras</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nPublicacións</string>
|
||||
<string name="comment">Comentar</string>
|
||||
<string name="comment_posted">Comentario: %1$s publicado!</string>
|
||||
<string name="comment_error">Fallo ao comentar!</string>
|
||||
@ -75,19 +69,16 @@
|
||||
<string name="write_permission_download_pic">Tes que conceder permiso de escritura para descargar fotos!</string>
|
||||
<string name="empty_comment">O comentario non debe quedar baleiro!</string>
|
||||
<string name="posted_on">Publicado o %1$s</string>
|
||||
<string name="shares">Compartido %1$s</string>
|
||||
<string name="likes">%1$s Gústame</string>
|
||||
<string name="no_description">Sen descrición</string>
|
||||
<string name="feed_failed">Non se obtivo a fonte</string>
|
||||
<string name="loading_toast">Algo fallou ao realizar a subida</string>
|
||||
<string name="normal_filter">Normal</string>
|
||||
<string name="upload_post_error">Fallou a publicación: not 200</string>
|
||||
<string name="upload_post_error">Erro ao subir a publicación</string>
|
||||
<string name="upload_post_success">Publicación correcta</string>
|
||||
<string name="upload_post_failed">Fallou a subida da publicación</string>
|
||||
<string name="request_format_error">Erro na subida: formato da solicitude non válido</string>
|
||||
<string name="picture_format_error">Erro na subida: formato de imaxe non válido.</string>
|
||||
<string name="upload_picture_failed">Erro ao subir a foto!</string>
|
||||
<string name="description_max_characters">A descrición non pode superar os %1$s caracteres.</string>
|
||||
<string name="save_image_failed">Non se gardou a imaxe</string>
|
||||
<string name="permission_denied">Permiso denegado</string>
|
||||
<string name="dark_theme">Escuro</string>
|
||||
@ -134,4 +125,58 @@
|
||||
<string name="language">Idioma</string>
|
||||
<string name="delete_dialog">Eliminar esta publicación\?</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
<string name="verify_credentials">Non se obtivo a información da usuaria</string>
|
||||
<string name="mascot_description">Imaxe amosando un panda vermello, mascota de Pixelfed, usando un móbil</string>
|
||||
<plurals name="nb_following">
|
||||
<item quantity="one">%d seguimento</item>
|
||||
<item quantity="other">%d seguimentos</item>
|
||||
</plurals>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">%d Seguidora</item>
|
||||
<item quantity="other">%d Seguidoras</item>
|
||||
</plurals>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">%d Publicación</item>
|
||||
<item quantity="other">%d Publicacións</item>
|
||||
</plurals>
|
||||
<string name="post_is_album">Esta publicación é un álbume</string>
|
||||
<string name="submit_comment">Enviar comentario</string>
|
||||
<string name="add_comment">Engadir un comentario</string>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d comentario</item>
|
||||
<item quantity="other">%d comentarios</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d Compartición</item>
|
||||
<item quantity="other">%d Comparticións</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d Gústame</item>
|
||||
<item quantity="other">%d Gústame</item>
|
||||
</plurals>
|
||||
<string name="no_cancel_edit">Non, cancela a edición</string>
|
||||
<string name="save_before_returning">Gardar a edición\?</string>
|
||||
<string name="crop_button">Botón para recortar ou rotar a imaxe</string>
|
||||
<string name="image_preview">Vista previa da imaxe a ser editada</string>
|
||||
<string name="filter_thumbnail">Miniatura do filtro</string>
|
||||
<string name="upload_error">Código de erro devolto polo servidor: %1$s</string>
|
||||
<string name="size_exceeds_instance_limit">O tamaño do número de imaxes %1$s no álbume supera o máximo permitido pola instancia (%2$s kB pero o límite é %3$s kB). É posible que non poidas subilas.</string>
|
||||
<string name="total_exceeds_album_limit">Elexiches máis imaxes das que permite o servidor (%1$s). As imaxes que superan o límite serán ignoradas.</string>
|
||||
<string name="no_media_description">Engade aquí unha descrición…</string>
|
||||
<string name="save_image_description">Gardar descrición da imaxe</string>
|
||||
<string name="switch_to_carousel">Cambiar ao carrusel</string>
|
||||
<string name="switch_to_grid">Mudar á vista en grella</string>
|
||||
<string name="post_image">Unha das imaxes da publicación</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">A descrición ten que ter %d caracter como moito.</item>
|
||||
<item quantity="other">A descrición ten que ter %d caracteres como moito.</item>
|
||||
</plurals>
|
||||
<string name="api_not_enabled_dialog">O API non está activado nesta instancia. Contacta coa administración e pídelle que o activen.</string>
|
||||
<string name="whats_an_instance_explanation">Pode que o campo de texto pedíndoche o dominio da túa \'instancia\' sexa confuso.
|
||||
\n
|
||||
\nPixelfed é unha plataforma federada, parte do \'fediverso\', e isto significa que pode comunicarse con outras plataformas que utilizan a mesma linguaxe, como Mastodon (see https://joinmastodon.org).
|
||||
\n
|
||||
\nTamén significa que tes que escoller un servidor, ou \'instancia\' de Pixelfed para utilizar. Se non coñeces ningunha, mira aquí: https://pixelfed.org/join
|
||||
\n
|
||||
\nPara saber máis acerca de Pixelfed, podes mirar aquí: https://pixelfed.org</string>
|
||||
</resources>
|
@ -48,7 +48,6 @@
|
||||
<string name="picture_format_error">Errore di caricamento: formato immagine errato.</string>
|
||||
<string name="add_account_name">Aggiungi Account</string>
|
||||
<string name="add_account_description">Aggiungi un altro account Pixelfed</string>
|
||||
<string name="description_max_characters">La descrizione deve contenere al massimo %1$s caratteri.</string>
|
||||
<string name="loading_toast">Qualcosa è andato storto durante il caricamento</string>
|
||||
<string name="write_permission_download_pic">È necessario concedere i permessi di scrittura per scaricare le immagini!</string>
|
||||
<string name="request_format_error">Errore di caricamento: formato di richiesta errato</string>
|
||||
@ -58,8 +57,6 @@
|
||||
<string name="normal_filter">Normale</string>
|
||||
<string name="feed_failed">Impossibile ottenere il feed</string>
|
||||
<string name="no_description">Nessuna descrizione</string>
|
||||
<string name="likes">%1$s Mi piace</string>
|
||||
<string name="shares">%1$s Condivisioni</string>
|
||||
<string name="posted_on">Postato su %1$s</string>
|
||||
<string name="empty_comment">Il commento non deve essere vuoto!</string>
|
||||
<string name="write_permission_share_pic">È necessario concedere i permessi di scrittura per condividere le immagini!</string>
|
||||
@ -67,10 +64,6 @@
|
||||
<string name="comment_error">Errore nel commento!</string>
|
||||
<string name="comment_posted">Commento: %1$s postato!</string>
|
||||
<string name="comment">Commento</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nSeguaci</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nSeguiti</string>
|
||||
<string name="follow_error">Impossibile seguire</string>
|
||||
<string name="unfollow_error">Impossibile smettere di seguire</string>
|
||||
<string name="access_token_invalid">Il token di accesso non è valido</string>
|
||||
@ -88,8 +81,6 @@
|
||||
<string name="accounts">ACCOUNTS</string>
|
||||
<string name="hashtags">HASHTAGS</string>
|
||||
<string name="posting_image_accessibility_hint">Immagine in corso di pubblicazione</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nPosts</string>
|
||||
<string name="action_not_allowed">Questa azione non è consentita</string>
|
||||
<string name="upload_post_error">Errore di caricamento del post</string>
|
||||
<string name="media_upload_completed">{gmd_cloud_done} Caricamento contenuti completato</string>
|
||||
@ -138,11 +129,50 @@
|
||||
<string name="post_is_album">Questo post è un album</string>
|
||||
<string name="submit_comment">Invia commento</string>
|
||||
<string name="add_comment">Aggiungi un commento</string>
|
||||
<string name="number_comments">%1$s commenti</string>
|
||||
<string name="crop_button">Pulsante per ritagliare o ruotare l\'immagine</string>
|
||||
<string name="image_preview">Anteprima dell\'immagine in fase di modifica</string>
|
||||
<string name="filter_thumbnail">Miniatura del filtro</string>
|
||||
<string name="click_image_edit">Clicca sull\'immagine per modificarla</string>
|
||||
<string name="post_image">Una delle immagini nel post</string>
|
||||
<string name="verify_credentials">Impossibile ottenere le informazioni sull\'utente</string>
|
||||
<string name="switch_to_grid">Passa alla visualizzazione a griglia</string>
|
||||
<string name="no_cancel_edit">No, cancella la modifica</string>
|
||||
<string name="save_before_returning">Salavre le tue modifiche\?</string>
|
||||
<string name="switch_to_carousel">Passa alla panoramica</string>
|
||||
<string name="whats_an_instance_explanation">Potresti essere confuso dal campo di testo che chiede il dominio della tua \'instanza\'.
|
||||
\n
|
||||
\nPixelfed è una piattaforma federata, e parte del \'fediverso\', il che significa che può comunicare con altre piattaforme che parlano la stessa lingua, come Mastodon (vedi https://joinmastodon.org).
|
||||
\n
|
||||
\nSignifica anche che è necessario scegliere quale server, o \'istanza\' di Pixelfed da utilizzare. Se non ne conosci uno, puoi guardare qui: https://pixelfed.org/join
|
||||
\n
|
||||
\nPer maggiori informazioni su Pixelfed, puoi controllare qui: https://pixelfed.org</string>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">%d Seguace</item>
|
||||
<item quantity="other">%d Seguaci</item>
|
||||
</plurals>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">%d Post</item>
|
||||
<item quantity="other">%d Post</item>
|
||||
</plurals>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d commento</item>
|
||||
<item quantity="other">%d commenti</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d Condivisione</item>
|
||||
<item quantity="other">%d Condivisioni</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d Mi piace</item>
|
||||
<item quantity="other">%d Mi piace</item>
|
||||
</plurals>
|
||||
<string name="upload_error">Codice di errore restituito dal server: %1$s</string>
|
||||
<string name="size_exceeds_instance_limit">La dimensione del numero dell\'immagine %1$s nell\'album supera la dimensione massima consentita dall\'istanza (%2$s kB ma il limite è %3$s kB). Potresti non essere in grado di caricarla.</string>
|
||||
<string name="total_exceeds_album_limit">Hai scelto più immagini del massimo che il server consente (%1$s). Le immagini oltre il limite sono state ignorate.</string>
|
||||
<string name="no_media_description">Aggiungi una descrizione dell\'immagine qui…</string>
|
||||
<string name="save_image_description">Salva la descrizione dell\'immagine</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">La descrizione deve contenere al massimo %d carattere</item>
|
||||
<item quantity="other">La descrizione deve contenere al massimo %d caratteri</item>
|
||||
</plurals>
|
||||
<string name="api_not_enabled_dialog">L\'API non è attivata su questa istanza. Contatta l\'amministratore per chiedergli di attivarlo.</string>
|
||||
</resources>
|
@ -46,9 +46,6 @@
|
||||
<string name="write_permission_share_pic">画像を共有するためには書き込みの権限を更新する必要があります</string>
|
||||
<string name="write_permission_download_pic">画像をダウンロードするには書き込みの権限を更新する必要があります</string>
|
||||
<string name="empty_comment">コメントは空欄にできません</string>
|
||||
<string name="shares">%1$s再共有</string>
|
||||
<string name="likes">%1$sお気に入り</string>
|
||||
<string name="description_max_characters">説明には最低 %1$s文字以上含める必要があります。</string>
|
||||
<string name="comment_posted">コメント: %1$s が投稿されました</string>
|
||||
<string name="retry">再試行</string>
|
||||
<string name="posting_image_accessibility_hint">投稿された画像</string>
|
||||
@ -72,12 +69,6 @@
|
||||
<string name="follow_error">フォローできませんでした</string>
|
||||
<string name="follow_button_failed">フォローボタンを表示できませんでした</string>
|
||||
<string name="follow_status_failed">フォローステータスを取得できませんでした</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nフォロー</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nフォロワー</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\n投稿</string>
|
||||
<string name="comment">コメント</string>
|
||||
<string name="comment_error">コメントエラー</string>
|
||||
<string name="share_image">画像を共有</string>
|
||||
|
@ -1,5 +0,0 @@
|
||||
<resources>
|
||||
<style name="AppTheme.Launcher">
|
||||
<item name="android:windowBackground">@drawable/theme_night</item>
|
||||
</style>
|
||||
</resources>
|
@ -41,7 +41,6 @@
|
||||
<string name="add_account_description">Andere Pixelfed account toevoegen</string>
|
||||
<string name="add_account_name">Account toevoegen</string>
|
||||
<string name="upload_picture_failed">Fout bij het uploaden!</string>
|
||||
<string name="description_max_characters">De beschrijving mag maximaal %1$s karakters bevatten.</string>
|
||||
<string name="dark_theme">Donker</string>
|
||||
<string name="light_theme">Licht</string>
|
||||
<string name="search">Zoeken</string>
|
||||
@ -53,10 +52,6 @@
|
||||
<string name="default_nfollowers">-
|
||||
\nVolgers</string>
|
||||
<string name="action_not_allowed">Deze actie is niet toegestaan</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nVolgend</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nVolgers</string>
|
||||
<string name="empty_comment">Commentaar mag niet leeg zijn!</string>
|
||||
<string name="no_description">Geen beschrijving</string>
|
||||
<string name="loading_toast">Er ging iets mis tijdens het laden</string>
|
||||
@ -75,9 +70,7 @@
|
||||
<string name="permission_denied">Toestemming geweigerd</string>
|
||||
<string name="default_system">Standaard (systeem instelling)</string>
|
||||
<string name="instance_error">Kon instance-informatie niet ophalen</string>
|
||||
<string name="likes">%1$s Vind-ik-leuks</string>
|
||||
<string name="request_format_error">Fout bij het uploaden: slecht verzoekformaat</string>
|
||||
<string name="shares">%1$s keer gedeeld</string>
|
||||
<string name="posted_on">Gepost op %1$s</string>
|
||||
<string name="comment">Commentaar maken</string>
|
||||
<string name="comment_posted">Commentaar: %1$s gepost!</string>
|
||||
@ -86,8 +79,6 @@
|
||||
<string name="write_permission_share_pic">Je moet schrijven toestaan om afbeeldingen te delen!</string>
|
||||
<string name="write_permission_download_pic">Je moet schrijven toestaan om afbeeldingen te downloaden!</string>
|
||||
<string name="follow_status_failed">Kon volgstatus niet ophalen</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nPosts</string>
|
||||
<string name="busy_dialog_ok_button">Ok, wacht.</string>
|
||||
<string name="crop_result_error">Kon de afbeelding niet ophalen na bijsnijden</string>
|
||||
<string name="busy_dialog_text">Bezig met het verwerken van de afbeelding, wacht even tot dat het klaar is!</string>
|
||||
@ -129,11 +120,10 @@
|
||||
<string name="post_is_album">Deze post is een album</string>
|
||||
<string name="submit_comment">Commentaar versturen</string>
|
||||
<string name="add_comment">Commentaar toevoegen</string>
|
||||
<string name="number_comments">%1$s commentaren</string>
|
||||
<string name="click_image_edit">Klik op de afbeelding om het te bewerken</string>
|
||||
<string name="add_photo">Foto toevoegen</string>
|
||||
<string name="instance_not_pixelfed_cancel">Aanmelding annuleren</string>
|
||||
<string name="instance_not_pixelfed_continue">OK, toch verdergaan</string>
|
||||
<string name="instance_not_pixelfed_warning">Dit is geen Pixelfed instantie, dus de app zou zich op onverwachte wijze kunnen gedragen.</string>
|
||||
<string name="report">Melden</string>
|
||||
<string name="poll_notification">De peiling van %1$s is beëindigd</string>
|
||||
</resources>
|
@ -45,8 +45,6 @@
|
||||
<string name="image_download_downloading">Pobieranie…</string>
|
||||
<string name="image_download_success">Pobieranie zakończone powodzeniem</string>
|
||||
<string name="no_description">Bez opisu</string>
|
||||
<string name="likes">Polubienia: %1$s</string>
|
||||
<string name="shares">Udostępnienia: %1$s</string>
|
||||
<string name="posted_on">Opublikowano %1$s</string>
|
||||
<string name="empty_comment">Komentarz nie może być pusty!</string>
|
||||
<string name="write_permission_share_pic">Musisz nadać uprawnienia do zapisu, aby móc udostępniać obrazki!</string>
|
||||
@ -64,7 +62,6 @@
|
||||
<string name="whats_an_instance">Co to jest instancja\?</string>
|
||||
<string name="login_connection_required_once">Musisz być online, aby dodać konto i korzystać z PixelDroida :(</string>
|
||||
<string name="permission_denied">Brak dostępu</string>
|
||||
<string name="description_max_characters">Opis może zawierać najwyżej %1$s znaków.</string>
|
||||
<string name="picture_format_error">Błąd wgrywania na serwer: niewłaściwy format pliku.</string>
|
||||
<string name="upload_post_error">Nie udało się opublikować posta</string>
|
||||
<string name="busy_dialog_text">Przetwarzam obrazek, zaczekaj aż skończę!</string>
|
||||
|
@ -41,4 +41,60 @@
|
||||
<string name="menu_settings">Configurações</string>
|
||||
<string name="menu_account">Meu Perfil</string>
|
||||
<string name="app_name">PixelDroid</string>
|
||||
<string name="no_cancel_edit">Não, cancelar edição</string>
|
||||
<string name="help_translate">Ajude a traduzir o PixelDroid para a sua língua:</string>
|
||||
<string name="delete">Apagar</string>
|
||||
<string name="discover">DESCOBRIR</string>
|
||||
<string name="toolbar_title_edit">Editar</string>
|
||||
<string name="share_link">Partilhar Ligação</string>
|
||||
<string name="report">Reportar</string>
|
||||
<string name="status_more_options">Mais opções</string>
|
||||
<string name="about">Sobre</string>
|
||||
<string name="project_website">Website do projeto: https://pixeldroid.org</string>
|
||||
<string name="about_pixeldroid">Sobre PixelDroid</string>
|
||||
<string name="retry">Tentar novamente</string>
|
||||
<string name="search">Pesquisar</string>
|
||||
<string name="edit_profile">Editar perfil</string>
|
||||
<string name="follow">Seguir</string>
|
||||
<string name="default_nfollowers">-
|
||||
\nSeguidores</string>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">%d Seguidor</item>
|
||||
<item quantity="other">%d Seguidores</item>
|
||||
</plurals>
|
||||
<string name="submit_comment">Submeter comentário</string>
|
||||
<string name="add_comment">Adicionar comentário</string>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d comentário</item>
|
||||
<item quantity="other">%d comentários</item>
|
||||
</plurals>
|
||||
<string name="share_image">Partilhar Imagem</string>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d Partilha</item>
|
||||
<item quantity="other">%d Partilhas</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d Gosto</item>
|
||||
<item quantity="other">%d Gostos</item>
|
||||
</plurals>
|
||||
<string name="no_description">Sem descrição</string>
|
||||
<string name="feed_failed">Não foi possível obter feed</string>
|
||||
<string name="crop_button">Botão para cortar ou rodar imagem</string>
|
||||
<string name="image_preview">Pré-visualização da imagem editada</string>
|
||||
<string name="busy_dialog_text">A processar imagem, espere que termine primeiro!</string>
|
||||
<string name="normal_filter">Normal</string>
|
||||
<string name="save_image_description">Guardar descrição da imagem</string>
|
||||
<string name="post_image">Uma das imagens na publicação</string>
|
||||
<string name="add_photo">Adicionar foto</string>
|
||||
<string name="upload_post_error">Erro ao publicar</string>
|
||||
<string name="upload_post_success">Publicado com sucesso</string>
|
||||
<string name="upload_post_failed">Falha ao publicar</string>
|
||||
<string name="picture_format_error">Erro de publicação: formato errado de imagem.</string>
|
||||
<string name="upload_picture_failed">Erro ao publicar imagem!</string>
|
||||
<string name="save_image_success">Imagem guardada com sucesso</string>
|
||||
<string name="save_image_failed">Não foi possível guardar a imagem</string>
|
||||
<string name="permission_denied">Permissão recusada</string>
|
||||
<string name="dark_theme">Escuro</string>
|
||||
<string name="light_theme">Claro</string>
|
||||
<string name="verify_credentials">Não foi possível obter informação do utilizador</string>
|
||||
</resources>
|
@ -3,7 +3,7 @@
|
||||
<string name="auth_failed">Не удалось авторизоваться</string>
|
||||
<string name="token_error">Ошибка получения токена</string>
|
||||
<string name="title_activity_settings2">Настройки</string>
|
||||
<string name="theme_title">Тема приложения</string>
|
||||
<string name="theme_title">Тема Приложения</string>
|
||||
<string name="theme_header">Тема</string>
|
||||
<string name="followed_notification">%1$s подписался(-лась) на вас</string>
|
||||
<string name="mention_notification">%1$s упомянул(а) вас</string>
|
||||
@ -17,8 +17,8 @@
|
||||
<string name="tab_filters">ФИЛЬТРЫ</string>
|
||||
<string name="edit">РЕДАКТИРОВАТЬ</string>
|
||||
<string name="save_to_gallery">Сохранить в Галерею…</string>
|
||||
<string name="image_download_downloading">Сохранение…</string>
|
||||
<string name="image_download_success">Изображение успешно сохранено</string>
|
||||
<string name="image_download_downloading">Загрузка…</string>
|
||||
<string name="image_download_success">Изображение успешно загружено</string>
|
||||
<string name="switch_camera_button_alt">Переключить камеру</string>
|
||||
<string name="gallery_button_alt">Галерея</string>
|
||||
<string name="NoCommentsToShow">Нет комментариев…</string>
|
||||
@ -35,56 +35,47 @@
|
||||
<string name="share_picture">Поделиться изображением…</string>
|
||||
<string name="login_connection_required_once">Вам необходимо быть в сети что бы добавить аккаунт и использовать PixelDroid :(</string>
|
||||
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Скрытое медиа
|
||||
\n(кликните что бы показать)</string>
|
||||
\n(кликните что бы показать 18+)</string>
|
||||
<string name="registration_failed">Не удалось зарегистрировать приложение на этом инстансе</string>
|
||||
<string name="capture_button_alt">Сделать снимок</string>
|
||||
<string name="add_account_description">Добавить другой аккаунт Pixelfed</string>
|
||||
<string name="add_account_name">Добавить аккаунт</string>
|
||||
<string name="instance_error">Не удалось получить информацию об инстансе</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nПодписки</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nПодписчики</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nПосты</string>
|
||||
<string name="comment">Комментарий</string>
|
||||
<string name="comment_posted">Комментарий: %1$s опубликован!</string>
|
||||
<string name="comment_error">Ошибка при добавлении комментария!</string>
|
||||
<string name="share_image">Поделиться изображением</string>
|
||||
<string name="write_permission_download_pic">Вы должны дать разрешение на запись файлов чтобы сохранять изображения!</string>
|
||||
<string name="write_permission_share_pic">Вы должны дать разрешение на запись файлов чтобы делиться изображениями!</string>
|
||||
<string name="comment_error">Ошибка комментария!</string>
|
||||
<string name="share_image">Поделиться Изображением</string>
|
||||
<string name="write_permission_download_pic">Вы должны дать разрешение на запись для загрузки изображений!</string>
|
||||
<string name="write_permission_share_pic">Вы должны дать разрешение на запись чтобы делиться изображениями!</string>
|
||||
<string name="empty_comment">Комментарий не может быть пустым!</string>
|
||||
<string name="posted_on">Опубликовано в %1$s</string>
|
||||
<string name="shares">%1$s репостов</string>
|
||||
<string name="likes">%1$s лайков</string>
|
||||
<string name="no_description">Нет описания</string>
|
||||
<string name="feed_failed">Не удалось загрузить ленту</string>
|
||||
<string name="loading_toast">При загрузке что-то пошло не так</string>
|
||||
<string name="normal_filter">Обычный</string>
|
||||
<string name="upload_post_error">Не удалось загрузить пост</string>
|
||||
<string name="upload_post_error">Ошибка загрузки поста</string>
|
||||
<string name="upload_post_success">Пост успешно загружен</string>
|
||||
<string name="upload_post_failed">Не удалось загрузить пост</string>
|
||||
<string name="request_format_error">Ошибка загрузки: некорректный формат запроса</string>
|
||||
<string name="picture_format_error">Ошибка загрузки: неверный формат изображения.</string>
|
||||
<string name="upload_picture_failed">Ошибка загрузки изображения!</string>
|
||||
<string name="description_max_characters">Описание должно содержать максимум %1$s символов.</string>
|
||||
<string name="save_image_success">Изображение успешно сохранено</string>
|
||||
<string name="save_image_failed">Не удалось сохранить изображение</string>
|
||||
<string name="permission_denied">Недостаточно прав</string>
|
||||
<string name="permission_denied">Отказано в доступе</string>
|
||||
<string name="dark_theme">Тёмная</string>
|
||||
<string name="light_theme">Светлая</string>
|
||||
<string name="default_system">По умолчанию (как в системе)</string>
|
||||
<string name="default_system">По умолчанию (Как в системе)</string>
|
||||
<string name="follow_status_failed">Не удалось получить статус подписки</string>
|
||||
<string name="follow_error">Не удалось подписаться</string>
|
||||
<string name="action_not_allowed">Это действие запрещено</string>
|
||||
<string name="follow_error">Подписка не удалась</string>
|
||||
<string name="action_not_allowed">Это действие недопустимо</string>
|
||||
<string name="unfollow_error">Не удалось отписаться</string>
|
||||
<string name="access_token_invalid">Недействительный токен доступа</string>
|
||||
<string name="access_token_invalid">Токен доступа недействителен</string>
|
||||
<string name="default_nposts">-
|
||||
\nПосты</string>
|
||||
<string name="default_nfollowers">-
|
||||
\nПодписчики</string>
|
||||
<string name="default_nfollowing">-
|
||||
\nПодписки</string>
|
||||
\nПодписан</string>
|
||||
<string name="no_username">Нет имени пользователя</string>
|
||||
<string name="follow">Подписаться</string>
|
||||
<string name="edit_profile">Редактировать профиль</string>
|
||||
@ -94,15 +85,15 @@
|
||||
<string name="hashtags">ХЭШТЕГИ</string>
|
||||
<string name="follow_button_failed">Не удалось отобразить кнопку подписки</string>
|
||||
<string name="posting_image_accessibility_hint">Изображение, которое будет опубликовано</string>
|
||||
<string name="media_upload_completed">{gmd_cloud_done} Загрузка завершена</string>
|
||||
<string name="retry">Попробовать снова</string>
|
||||
<string name="media_upload_failed">{gmd_cloud_off} Загрузка не удалась, попробуйте снова или проверьте сеть</string>
|
||||
<string name="nothing_to_see_here">Здесь ничего нет!</string>
|
||||
<string name="media_upload_completed">{gmd_cloud_done} Загрузка медиа завершена</string>
|
||||
<string name="retry">Ещё раз</string>
|
||||
<string name="media_upload_failed">{gmd_cloud_off} Загрузка медиа не удалась, попробуйте снова или проверьте сеть</string>
|
||||
<string name="nothing_to_see_here">Здесь нечего смотреть!</string>
|
||||
<string name="crop_result_error">Не удалось получить изображение после обрезки</string>
|
||||
<string name="busy_dialog_ok_button">Хорошо, подожду.</string>
|
||||
<string name="busy_dialog_ok_button">Ок, подожду.</string>
|
||||
<string name="busy_dialog_text">Изображение обрабатывается, пожалуйста, ожидайте!</string>
|
||||
<string name="open_drawer_menu">Открыть навигационное меню</string>
|
||||
<string name="panda_pull_to_refresh_to_try_again">Панде грустно. Потяни, чтобы обновить.</string>
|
||||
<string name="open_drawer_menu">Открыть меню навигации</string>
|
||||
<string name="panda_pull_to_refresh_to_try_again">Эта панда несчастлива. Потяни, чтобы обновить.</string>
|
||||
<string name="something_went_wrong">Что-то пошло не так…</string>
|
||||
<string name="discover">ОБЗОР</string>
|
||||
<string name="profile_picture">Изображение профиля</string>
|
||||
@ -115,9 +106,9 @@
|
||||
<string name="report">Пожаловаться</string>
|
||||
<string name="status_more_options">Больше опций</string>
|
||||
<string name="search_empty_error">Поисковый запрос не может быть пустым</string>
|
||||
<string name="follows_title">Подписки %1$s</string>
|
||||
<string name="followers_title">Подписчики %1$s</string>
|
||||
<string name="post_title">Пост %1$s</string>
|
||||
<string name="follows_title">%1$s подписок</string>
|
||||
<string name="followers_title">%1$s подписчиков</string>
|
||||
<string name="post_title">%1$s пост</string>
|
||||
<string name="about">О приложении</string>
|
||||
<string name="license_info">PixelDroid это свободное ПО с открытым исходным кодом, выпускаемое под лицензией GNU General Public License (версии 3 или новее)</string>
|
||||
<string name="project_website">Сайт проекта: https://pixeldroid.org</string>
|
||||
@ -125,8 +116,77 @@
|
||||
<string name="about_pixeldroid">О PixelDroid</string>
|
||||
<string name="unfollow">Отписаться</string>
|
||||
<string name="add_photo">Добавить фото</string>
|
||||
<string name="poll_notification">Опрос %1$s завершён</string>
|
||||
<string name="instance_not_pixelfed_cancel">Отмена</string>
|
||||
<string name="instance_not_pixelfed_continue">OK, всё равно продолжить</string>
|
||||
<string name="poll_notification">%1$s завершил опрос</string>
|
||||
<string name="instance_not_pixelfed_cancel">Отмена входа</string>
|
||||
<string name="instance_not_pixelfed_continue">OK, продолжить всё равно</string>
|
||||
<string name="instance_not_pixelfed_warning">Это не похоже на инстанс Pixelfed, приложение может работать нестабильно.</string>
|
||||
<string name="save_image_description">Сохранить описание изображения</string>
|
||||
<string name="post_image">Одно из изображений в посте</string>
|
||||
<string name="verify_credentials">Невозможно получить информацию о пользователе</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">Описание должно содержать максимум %d символ.</item>
|
||||
<item quantity="few">Описание должно содержать максимум %d символа.</item>
|
||||
<item quantity="many">Описание должно содержать максимум %d символов.</item>
|
||||
<item quantity="other">Описание должно содержать максимум %d символов.</item>
|
||||
</plurals>
|
||||
<string name="no_cancel_edit">Нет, отменить редактирование</string>
|
||||
<string name="save_before_returning">Сохранить ваши редактирования\?</string>
|
||||
<string name="mascot_description">Изображение красной панды, талисман Pixelfed , использующей телефон</string>
|
||||
<string name="issues_contribute">Сообщайте о проблемах или вносите свой вклад в приложение:</string>
|
||||
<string name="help_translate">Помогите перевести PixelDroid на ваш язык:</string>
|
||||
<string name="language">Язык</string>
|
||||
<string name="delete_dialog">Удалить этот пост\?</string>
|
||||
<string name="delete">Удалить</string>
|
||||
<plurals name="nb_following">
|
||||
<item quantity="one">%d Подписка</item>
|
||||
<item quantity="few">%d Подписки</item>
|
||||
<item quantity="many">%d Подписок</item>
|
||||
<item quantity="other">%d Подписок</item>
|
||||
</plurals>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">%d Подписчик</item>
|
||||
<item quantity="few">%d Подписчика</item>
|
||||
<item quantity="many">%d Подписчиков</item>
|
||||
<item quantity="other">%d Подписчиков</item>
|
||||
</plurals>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">%d Пост</item>
|
||||
<item quantity="few">%d Поста</item>
|
||||
<item quantity="many">%d Постов</item>
|
||||
<item quantity="other">%d Постов</item>
|
||||
</plurals>
|
||||
<string name="post_is_album">Этот пост в альбоме</string>
|
||||
<string name="submit_comment">Отправить комментарий</string>
|
||||
<string name="add_comment">Добавить комментарий</string>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d комментарий</item>
|
||||
<item quantity="few">%d комментария</item>
|
||||
<item quantity="many">%d комментариев</item>
|
||||
<item quantity="other">%d комментариев</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d Репост</item>
|
||||
<item quantity="few">%d Репоста</item>
|
||||
<item quantity="many">%d Репостов</item>
|
||||
<item quantity="other">%d Репостов</item>
|
||||
</plurals>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d Лайк</item>
|
||||
<item quantity="few">%d Лайка</item>
|
||||
<item quantity="many">%d Лайков</item>
|
||||
<item quantity="other">%d Лайков</item>
|
||||
</plurals>
|
||||
<string name="image_preview">Предварительный просмотр редактируемого изображения</string>
|
||||
<string name="filter_thumbnail">Фильтр миниатюр</string>
|
||||
<string name="no_media_description">Добавить описание медиа файла здесь…</string>
|
||||
<string name="switch_to_carousel">Показывать в режиме «карусели»</string>
|
||||
<string name="whats_an_instance_explanation">Вас может смутить текстовое поле, запрашивающее доменное имя вашего \'инстанса\'.
|
||||
\n
|
||||
\nPixelfed это федеративная платформа и часть \"федиверса\", что означает, что она может общаться с другими платформами, говорящими на том же языке, как например Mastodon (см. https://joinmastodon.org).
|
||||
\n
|
||||
\nЭто также означает, что вы должны выбрать, какой сервер или \'инстанс\' Pixelfed использовать. Если вы еще ничего не знаете об этом, перейдите по ссылке: https://pixelfed.org/join
|
||||
\n
|
||||
\nДополнительную информации о Pixelfed вы можете посмотреть здесь: https://pixelfed.org</string>
|
||||
<string name="crop_button">Кнопка для обрезки или поворота изображения</string>
|
||||
<string name="switch_to_grid">Переключить в виде сетки</string>
|
||||
</resources>
|
@ -64,12 +64,6 @@
|
||||
<string name="follow_error">Kunde inte följa</string>
|
||||
<string name="follow_button_failed">Kunde inte visa följarknapp</string>
|
||||
<string name="follow_status_failed">Kunde inte hämta status för följare</string>
|
||||
<string name="nb_following">%1$s
|
||||
\nFöljer</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\nFöljare</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\nInlägg</string>
|
||||
<string name="comment">Kommentera</string>
|
||||
<string name="comment_posted">Kommentar: %$s inlagd!</string>
|
||||
<string name="comment_error">Kommentarsfel!</string>
|
||||
@ -78,8 +72,6 @@
|
||||
<string name="write_permission_download_pic">Du måste tillåta skrivrättigheter för att ladda ned bilder!</string>
|
||||
<string name="empty_comment">Kommentaren får inte vara tom!</string>
|
||||
<string name="posted_on">Inlagt på %1$s</string>
|
||||
<string name="shares">%1$s delningar</string>
|
||||
<string name="likes">%1$s gillningar</string>
|
||||
<string name="no_description">Ingen beskrivning</string>
|
||||
<string name="feed_failed">Kunde inte hämta flöde</string>
|
||||
<string name="loading_toast">Något gick fel vid laddningen</string>
|
||||
@ -90,7 +82,6 @@
|
||||
<string name="request_format_error">Uppladdningsfel: felaktig begäran</string>
|
||||
<string name="picture_format_error">Uppladdningsfel: fel bildformat.</string>
|
||||
<string name="upload_picture_failed">Fel vid uppladdning av bild!</string>
|
||||
<string name="description_max_characters">Beskrivningen måste minst innehålla %1$s tecken.</string>
|
||||
<string name="save_image_success">Bild sparades</string>
|
||||
<string name="save_image_failed">Kunde inte spara bild</string>
|
||||
<string name="permission_denied">Åtkomst nekas</string>
|
||||
|
@ -64,12 +64,6 @@
|
||||
<string name="follow_error">无法关注</string>
|
||||
<string name="follow_button_failed">无法显示关注按钮</string>
|
||||
<string name="follow_status_failed">无法获得关注状态</string>
|
||||
<string name="nb_following">%1$s
|
||||
\n正在关注</string>
|
||||
<string name="nb_followers">%1$s
|
||||
\n关注者</string>
|
||||
<string name="nb_posts">%1$s
|
||||
\n帖文</string>
|
||||
<string name="comment">评论</string>
|
||||
<string name="comment_posted">评论: %1$s 已发布!</string>
|
||||
<string name="comment_error">评论错误!</string>
|
||||
@ -78,19 +72,16 @@
|
||||
<string name="write_permission_download_pic">您需要允许读写权限才能下载图片!</string>
|
||||
<string name="empty_comment">评论不能为空!</string>
|
||||
<string name="posted_on">发表于 %1$s</string>
|
||||
<string name="shares">%1$s 次分享</string>
|
||||
<string name="likes">%1$s 个赞</string>
|
||||
<string name="no_description">没有描述</string>
|
||||
<string name="feed_failed">无法获取订阅</string>
|
||||
<string name="loading_toast">加载时出了一些问题</string>
|
||||
<string name="normal_filter">正常</string>
|
||||
<string name="upload_post_error">帖文上传失败</string>
|
||||
<string name="upload_post_error">帖文上传错误</string>
|
||||
<string name="upload_post_failed">帖文上传失败</string>
|
||||
<string name="upload_post_success">帖文成功上传</string>
|
||||
<string name="request_format_error">上传错误:错误的请求格式</string>
|
||||
<string name="picture_format_error">上传错误:图片格式错误。</string>
|
||||
<string name="upload_picture_failed">图片上传错误!</string>
|
||||
<string name="description_max_characters">描述最多只能包含 %1$s 个字符。</string>
|
||||
<string name="save_image_success">图片成功保存</string>
|
||||
<string name="save_image_failed">无法保存图像</string>
|
||||
<string name="permission_denied">没有权限</string>
|
||||
@ -101,4 +92,17 @@
|
||||
<string name="crop_result_error">图像裁剪后无法恢复</string>
|
||||
<string name="busy_dialog_ok_button">好的</string>
|
||||
<string name="busy_dialog_text">图像仍在处理中,请先等待完成!</string>
|
||||
<string name="post_image">帖文中的一张图片</string>
|
||||
<string name="add_photo">添加照片</string>
|
||||
<string name="poll_notification">%1$s投票已经结束</string>
|
||||
<string name="instance_not_pixelfed_cancel">取消登录</string>
|
||||
<string name="instance_not_pixelfed_continue">了解,请继续</string>
|
||||
<string name="instance_not_pixelfed_warning">这似乎不是一个 Pixelfed 实例,可能会导致应用意外退出</string>
|
||||
<string name="verify_credentials">无法获得用户信息</string>
|
||||
<string name="about">关于</string>
|
||||
<string name="project_website">项目主页:https://pixeldroid.org</string>
|
||||
<string name="unfollow">取消关注</string>
|
||||
<string name="about_pixeldroid">关于 PixelDroid</string>
|
||||
<string name="share_link">分享链接</string>
|
||||
<string name="delete">删除</string>
|
||||
</resources>
|
@ -28,18 +28,29 @@
|
||||
|
||||
<!-- Login page -->
|
||||
<string name="whats_an_instance">"What's an instance?"</string>
|
||||
<string name="whats_an_instance_explanation">"You might be confused by the text field asking for the domain of your 'instance'.
|
||||
|
||||
Pixelfed is a federated platform, and part of the 'fediverse', which means it can talk to other platforms which speak the same language, like Mastodon (see https://joinmastodon.org).
|
||||
|
||||
It also means you have to choose which server, or 'instance' of Pixelfed to use. If you don't know any, you can look here: https://pixelfed.org/join
|
||||
|
||||
For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||
<string name="domain_of_your_instance">Domain of your instance</string>
|
||||
<string name="connect_to_pixelfed">Connect to Pixelfed</string>
|
||||
<string name="login_connection_required_once">You need to be online to be able to add the first account and use PixelDroid :(</string>
|
||||
<string name="add_account_name">Add Account</string>
|
||||
<string name="add_account_description">Add another Pixelfed Account</string>
|
||||
<string name="api_not_enabled_dialog">The API is not activated on this instance. Contact your administrator to ask them to activate it.</string>
|
||||
<!-- Drawer -->
|
||||
<string name="logout">Log out</string>
|
||||
<string name="add_account_name">Add Account</string>
|
||||
<string name="add_account_description">Add another Pixelfed Account</string>
|
||||
<!-- Post creation -->
|
||||
<string name="permission_denied">Permission denied</string>
|
||||
<string name="save_image_failed">Unable to save image</string>
|
||||
<string name="save_image_success">Image successfully saved</string>
|
||||
<string name="description_max_characters">"Description must contain %1$s characters at most."</string>
|
||||
<plurals name="description_max_characters">
|
||||
<item quantity="one">"Description must contain %d character at most."</item>
|
||||
<item quantity="other">"Description must contain %d characters at most."</item>
|
||||
</plurals>
|
||||
<string name="upload_picture_failed">Picture upload error!</string>
|
||||
<string name="picture_format_error">Upload error: wrong picture format.</string>
|
||||
<string name="request_format_error">Upload error: bad request format</string>
|
||||
@ -50,7 +61,15 @@
|
||||
<string name="post">post</string>
|
||||
<string name="add_photo">Add a photo</string>
|
||||
<string name="post_image">One of the images in the post</string>
|
||||
<string name="click_image_edit">Click the image to edit it</string>
|
||||
<string name="switch_to_grid">Switch to grid view</string>
|
||||
<string name="switch_to_carousel">Switch to carousel</string>
|
||||
<string name="save_image_description">Save image description</string>
|
||||
<string name="no_media_description">Add a media description here…</string>
|
||||
<string name="total_exceeds_album_limit">You chose more images than the maximum your server allows (%1$s). Images beyond the limit have been ignored.</string>
|
||||
<string name="size_exceeds_instance_limit">Size of image number %1$s in the album exceeds the maximum size allowed by the instance (%2$s kB but the limit is %3$s kB). You might not be able to upload it.</string>
|
||||
<string name="upload_error">Error code returned by server: %1$s</string>
|
||||
|
||||
|
||||
<!-- Post editing -->
|
||||
<string name="lbl_brightness">BRIGHTNESS</string>
|
||||
<string name="lbl_contrast">CONTRAST</string>
|
||||
@ -64,6 +83,9 @@
|
||||
<string name="crop_result_error">"Couldn't retrieve image after crop"</string>
|
||||
<string name="image_preview">Preview of the image being edited</string>
|
||||
<string name="crop_button">Button to crop or rotate the image</string>
|
||||
<string name="save_before_returning">Save your edits?</string>
|
||||
<string name="no_cancel_edit">No, cancel edit</string>
|
||||
|
||||
<!-- Camera -->
|
||||
<string name="capture_button_alt">Capture</string>
|
||||
<string name="switch_camera_button_alt">Switch camera</string>
|
||||
@ -79,8 +101,14 @@
|
||||
<string name="image_download_success">Image downloaded successfully</string>
|
||||
<!-- Post attributes -->
|
||||
<string name="no_description">No description</string>
|
||||
<string name="likes">"%1$s Likes"</string>
|
||||
<string name="shares">"%1$s Shares"</string>
|
||||
<plurals name="likes">
|
||||
<item quantity="one">%d Like</item>
|
||||
<item quantity="other">%d Likes</item>
|
||||
</plurals>
|
||||
<plurals name="shares">
|
||||
<item quantity="one">%d Share</item>
|
||||
<item quantity="other">%d Shares</item>
|
||||
</plurals>
|
||||
<string name="posted_on">"Posted on %1$s"</string>
|
||||
<string name="NoCommentsToShow">No comments on this post…</string>
|
||||
<string name="empty_comment">Comment must not be empty!</string>
|
||||
@ -90,14 +118,32 @@
|
||||
<string name="comment_error">Comment error!</string>
|
||||
<string name="comment_posted">"Comment: %1$s posted!"</string>
|
||||
<string name="comment">Comment</string>
|
||||
<string name="number_comments">%1$s comments</string>
|
||||
<plurals name="number_comments">
|
||||
<item quantity="one">%d comment</item>
|
||||
<item quantity="other">%d comments</item>
|
||||
</plurals>
|
||||
<string name="add_comment">Add a comment</string>
|
||||
<string name="submit_comment">Submit comment</string>
|
||||
<string name="post_is_album">This post is an album</string>
|
||||
<!-- Profile page -->
|
||||
<string name="nb_posts">"%1$s\nPosts"</string>
|
||||
<string name="nb_followers">"%1$s\nFollowers"</string>
|
||||
<string name="nb_following">"%1$s\nFollowing"</string>
|
||||
<plurals name="nb_posts">
|
||||
<item quantity="one">"%d
|
||||
Post"</item>
|
||||
<item quantity="other">"%d
|
||||
Posts"</item>
|
||||
</plurals>
|
||||
<plurals name="nb_followers">
|
||||
<item quantity="one">"%d
|
||||
Follower"</item>
|
||||
<item quantity="other">"%d
|
||||
Followers"</item>
|
||||
</plurals>
|
||||
<plurals name="nb_following">
|
||||
<item quantity="one">"%d
|
||||
Following"</item>
|
||||
<item quantity="other">"%d
|
||||
Following"</item>
|
||||
</plurals>
|
||||
<string name="follow_status_failed">Could not get follow status</string>
|
||||
<string name="follow_button_failed">Could not display follow button</string>
|
||||
<string name="follow_error">Could not follow</string>
|
||||
@ -117,7 +163,7 @@
|
||||
<string name="accounts">ACCOUNTS</string>
|
||||
<string name="hashtags">HASHTAGS</string>
|
||||
<!-- Sensitive media -->
|
||||
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Hidden Media \n (click to show)</string>
|
||||
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Hidden Media\n (click to show)</string>
|
||||
<!-- Shown when image has finished uploading. {gmd_cloud_done} is an icon, position it as is appropriate in target language -->
|
||||
<string name="media_upload_completed">{gmd_cloud_done} Media upload completed</string>
|
||||
<!-- Shown when image uploading has failed. {gmd_cloud_off} is an icon, position it as is appropriate in target language -->
|
||||
@ -154,11 +200,4 @@
|
||||
<string name="help_translate">Help translate PixelDroid to your language:</string>
|
||||
<string name="issues_contribute">Report issues or contribute to the application:</string>
|
||||
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
|
||||
<string name="save_before_returning">Save your edits?</string>
|
||||
<string name="no_cancel_edit">No, cancel edit</string>
|
||||
<string name="switch_to_grid">Switch to grid view</string>
|
||||
<string name="switch_to_carousel">Switch to carousel</string>
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
|
||||
</resources>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
@ -31,7 +31,7 @@ class APIUnitTest {
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
media_attachments= listOf(Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null)),
|
||||
remote_url=null, text_url=null, description=null, blurhash=null, meta = null)),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
emojis= emptyList(), reblogs_count=0, favourites_count=0, replies_count=0, url="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
|
@ -20,7 +20,7 @@ class PostUnitTest {
|
||||
media_attachments= listOf(
|
||||
Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null)
|
||||
remote_url=null, text_url=null, description=null, blurhash=null, meta = null)
|
||||
),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
|
Loading…
x
Reference in New Issue
Block a user