Add upload progress bars and error handling to PostCreationActivity (#191)

* Add upload bar to PostCreationActivity

* Add upload error handling

* Fix test, remove duplicate api endpoint

* try to trigger ci

* don't show error all the time

* remove unused strings
This commit is contained in:
Wv5twkFEKh54vo4tta9yu7dHa3 2020-05-21 19:31:41 +02:00 committed by GitHub
parent 5b0a344236
commit d942c30898
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 287 additions and 121 deletions

View File

@ -123,6 +123,7 @@ dependencies {
//iconics
implementation "com.mikepenz:materialdrawer-iconics:8.0.3"
implementation "com.mikepenz:iconics-views:5.0.2"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'

View File

@ -1,10 +1,10 @@
package com.h.pixeldroid
import android.content.Context
import android.content.Intent
import androidx.test.core.app.ActivityScenario
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.swipeLeft
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
@ -12,7 +12,6 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
@ -22,7 +21,6 @@ import com.h.pixeldroid.db.UserDatabaseEntity
import com.h.pixeldroid.testUtility.MockServer
import com.h.pixeldroid.utils.DBUtils
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.camera_ui_container.*
import org.hamcrest.Matcher
import org.junit.Before
import org.junit.Rule
@ -67,9 +65,6 @@ class PostFragmentUITests {
@get:Rule
var globalTimeout: Timeout = Timeout.seconds(30)
@get:Rule
var rule = ActivityScenarioRule(MainActivity::class.java)
private lateinit var db: AppDatabase

View File

@ -1,5 +1,8 @@
package com.h.pixeldroid.testUtility
import com.google.gson.Gson
import com.h.pixeldroid.objects.Application
class JsonValues {
companion object {
const val likedJson = """{"id":"156491373246287872","created_at":"2020-04-16T20:00:50.000000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"https:\/\/pixelfed.de\/p\/machintuck\/156491373246287872","url":"https:\/\/pixelfed.de\/p\/machintuck\/156491373246287872","replies_count":1,"reblogs_count":13,"favourites_count":3,"reblogged":false,"favourited":true,"muted":false,"bookmarked":false,"pinned":false,"content":"<a class=\"u-url mention\" href=\"https:\/\/pixelfed.de\/Dobios\" rel=\"external nofollow noopener\">@Dobios<\/a> <a class=\"u-url mention\" href=\"https:\/\/pixelfed.de\/Dante\" rel=\"external nofollow noopener\">@Dante<\/a>","reblog":null,"application":{"name":"web","website":null},"mentions":[{"id":"136800034732773376","url":"https:\/\/pixelfed.de\/Dobios","username":"Dobios","acct":"Dobios"},{"id":"136453537340198912","url":"https:\/\/pixelfed.de\/dante","username":"dante","acct":"dante"}],"tags":[{"name":"mushroom","url":"https:\/\/pixelfed.de\/discover\/tags\/mushroom"},{"name":"commentsstillbroken","url":"https:\/\/pixelfed.de\/discover\/tags\/commentsstillbroken"},{"name":"fixyourapi","url":"https:\/\/pixelfed.de\/discover\/tags\/fixyourapi"},{"name":"pls","url":"https:\/\/pixelfed.de\/discover\/tags\/pls"}],"emojis":[],"card":null,"poll":null,"account":{"id":"145183325781364736","username":"machintuck","acct":"machintuck","display_name":"Arthur","locked":false,"created_at":"2020-03-16T15:06:42.000000Z","followers_count":4,"following_count":4,"statuses_count":5,"note":"","url":"https:\/\/pixelfed.de\/machintuck","avatar":"https:\/\/pixelfed.de\/storage\/avatars\/014\/518\/332\/578\/136\/473\/6\/gbdKtKOhTkNA5UxCzeAQ_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","avatar_static":"https:\/\/pixelfed.de\/storage\/avatars\/014\/518\/332\/578\/136\/473\/6\/gbdKtKOhTkNA5UxCzeAQ_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","header":"","header_static":"","emojis":[],"moved":null,"fields":null,"bot":false,"software":"pixelfed","is_admin":false},"media_attachments":[{"id":"19228","type":"image","url":"https:\/\/pixelfed.de\/storage\/m\/d0931bf747b992a1c83e055753526516f2706111\/9b4393bfd32c643a265bd1c557b981f167d60969\/lbOqQOMeHLGmhYgehhZUBJ4JvjtKulh83BA97LoP.jpeg","remote_url":null,"preview_url":"https:\/\/pixelfed.de\/storage\/m\/d0931bf747b992a1c83e055753526516f2706111\/9b4393bfd32c643a265bd1c557b981f167d60969\/lbOqQOMeHLGmhYgehhZUBJ4JvjtKulh83BA97LoP_thumb.jpeg","text_url":null,"meta":null,"description":null}]}"""
@ -175,5 +178,8 @@ class JsonValues {
"version": "69.420",
"registrations": true
}"""
var applicationJson = Gson().toJson(Application(name="PixelDroid",
website=null, vapid_key=null, client_id="286",
client_secret="2q3dHY29U8GNZ2eY6cbcw010cWk3qVGmWXxAJzn7"))
}
}

View File

@ -45,6 +45,11 @@ class MockServer {
.setResponseCode(200).setBody(JsonValues.tokenJson)
}
when {
request.path?.startsWith("/api/v1/apps") == true -> {
return MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.setResponseCode(200).setBody(JsonValues.applicationJson)
}
request.path?.startsWith("/api/v1/notifications") == true -> {
return MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")

View File

@ -30,7 +30,8 @@
<activity android:name=".PostCreationActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity" />
tools:ignore="LockedOrientationActivity"
android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name=".FollowsActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"/>

View File

@ -5,8 +5,9 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Button
import android.widget.ImageView
import android.widget.Toast
@ -15,25 +16,27 @@ import androidx.core.net.toUri
import com.google.android.material.textfield.TextInputEditText
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.db.UserDatabaseEntity
import com.h.pixeldroid.objects.Attachment
import com.h.pixeldroid.objects.Instance
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.DBUtils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import kotlinx.android.synthetic.main.activity_post_creation.*
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okio.BufferedSink
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.io.*
class PostCreationActivity : AppCompatActivity() {
class PostCreationActivity : AppCompatActivity(){
private val TAG = "Post Creation Activity"
@ -43,6 +46,8 @@ class PostCreationActivity : AppCompatActivity() {
private lateinit var image: File
private var user: UserDatabaseEntity? = null
private var listOfIds: List<String> = emptyList()
private var maxLength: Int = Instance.DEFAULT_MAX_TOOT_CHARS
private var description: String = ""
@ -77,21 +82,28 @@ class PostCreationActivity : AppCompatActivity() {
accessToken = user?.accessToken.orEmpty()
pixelfedAPI = PixelfedAPI.create(domain)
// check if the picture is alright
// TODO
//upload the picture and display progress while doing so
upload()
// edit the picture
// TODO
// get the description and send the post to PixelFed
// get the description and send the post
findViewById<Button>(R.id.post_creation_send_button).setOnClickListener {
if (setDescription()) upload()
if (setDescription() && listOfIds.isNotEmpty()) post()
}
// Button to retry image upload when it fails
findViewById<Button>(R.id.retry_upload_button).setOnClickListener {
upload_error.visibility = GONE
upload()
}
}
override fun onDestroy() {
super.onDestroy()
//delete the temporary image
image.delete()
}
private fun saveImage(imageUri: Uri) {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val fileName = "PixelDroid_$timeStamp.png"
try {
val stream = applicationContext.contentResolver
.openAssetFileDescriptor(imageUri, "r")!!
@ -99,9 +111,8 @@ class PostCreationActivity : AppCompatActivity() {
val bm = BitmapFactory.decodeStream(stream)
val bos = ByteArrayOutputStream()
bm.compress(Bitmap.CompressFormat.PNG, 0, bos)
image = File(
applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
fileName)
image = File.createTempFile("temp_compressed_img", ".png", cacheDir)
val fos = FileOutputStream(image)
fos.write(bos.toByteArray())
fos.flush()
@ -116,7 +127,7 @@ class PostCreationActivity : AppCompatActivity() {
val textField = findViewById<TextInputEditText>(R.id.new_post_description_input_field)
val content = textField.text.toString()
if (content.length > maxLength) {
// error, too much characters
// error, too many characters
textField.error = getString(R.string.description_max_characters).format(maxLength)
return false
@ -126,44 +137,50 @@ class PostCreationActivity : AppCompatActivity() {
return true
}
private fun upload() {
val rBody: RequestBody = image.asRequestBody("image/*".toMediaTypeOrNull())
val part = MultipartBody.Part.createFormData("file", image.name, rBody)
pixelfedAPI.mediaUpload("Bearer $accessToken", part).enqueue(object:
Callback<Attachment> {
override fun onFailure(call: Call<Attachment>, t: Throwable) {
Log.e(TAG, t.toString() + call.request())
Toast.makeText(applicationContext,getString(R.string.upload_picture_failed),
Toast.LENGTH_SHORT).show()
private fun upload(){
val imagePart = ProgressRequestBody(image)
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", image.name, imagePart)
.build()
val sub = imagePart.progressSubject
.subscribeOn(Schedulers.io())
.subscribe { percentage ->
uploadProgressBar.progress = percentage.toInt()
}
override fun onResponse(call: Call<Attachment>, response: Response<Attachment>) {
if (response.code() == 200) {
val body = response.body()!!
if (body.type.name == "image") {
post(body.id)
} else
Toast.makeText(applicationContext, getString(R.string.picture_format_error),
Toast.LENGTH_SHORT).show()
} else {
Log.e(TAG,
"Server responded: $response${call.request()}${call.request().body}"
)
Toast.makeText(applicationContext,getString(R.string.request_format_error),
Toast.LENGTH_SHORT).show()
}
}
})
var postSub : Disposable?= null
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", requestBody.parts[0])
postSub = inter
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ attachment ->
listOfIds = listOf(attachment.id)
},{e->
upload_error.visibility = VISIBLE
e.printStackTrace()
postSub?.dispose()
sub.dispose()
}, {
uploadProgressBar.visibility = GONE
upload_completed_textview.visibility = VISIBLE
enableButton(true)
postSub?.dispose()
sub.dispose()
})
}
private fun post(id: String) {
if (id.isEmpty()) return
private fun post() {
enableButton(false)
pixelfedAPI.postStatus(
authorization = "Bearer $accessToken",
statusText = description,
media_ids = listOf(id)
media_ids = listOfIds
).enqueue(object: Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
enableButton(true)
Toast.makeText(applicationContext,getString(R.string.upload_post_failed),
Toast.LENGTH_SHORT).show()
Log.e(TAG, t.message + call.request())
@ -173,14 +190,80 @@ class PostCreationActivity : AppCompatActivity() {
if (response.code() == 200) {
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
Toast.LENGTH_SHORT).show()
startActivity(Intent(applicationContext, MainActivity::class.java))
val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
} else {
Toast.makeText(applicationContext,getString(R.string.upload_post_error),
Toast.LENGTH_SHORT).show()
Log.e(TAG, call.request().toString() + response.raw().toString())
enableButton(true)
}
}
})
}
private fun enableButton(enable: Boolean = true){
post_creation_send_button.isEnabled = enable
if(enable){
posting_progress_bar.visibility = GONE
post_creation_send_button.visibility = VISIBLE
} else{
posting_progress_bar.visibility = VISIBLE
post_creation_send_button.visibility = GONE
}
}
}
class ProgressRequestBody(private val mFile: File) : RequestBody() {
private val getProgressSubject: PublishSubject<Float> = PublishSubject.create()
val progressSubject: Observable<Float>
get() {
return getProgressSubject
}
override fun contentType(): MediaType? {
return "image/png".toMediaTypeOrNull()
}
@Throws(IOException::class)
override fun contentLength(): Long {
return mFile.length()
}
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val fileLength = contentLength()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val `in` = FileInputStream(mFile)
var uploaded: Long = 0
`in`.use {
var read: Int
var lastProgressPercentUpdate = 0.0f
read = it.read(buffer)
while (read != -1) {
uploaded += read.toLong()
sink.write(buffer, 0, read)
read = it.read(buffer)
val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f
//prevent publishing too many updates, which slows upload, by checking if the upload has progressed by at least 1 percent
if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
// publish progress
getProgressSubject.onNext(progress)
lastProgressPercentUpdate = progress
}
}
}
}
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
}
}

View File

@ -1,6 +1,7 @@
package com.h.pixeldroid.api
import com.h.pixeldroid.objects.*
import io.reactivex.Observable
import okhttp3.MultipartBody
import retrofit2.Call
import retrofit2.Retrofit
@ -9,6 +10,7 @@ import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import retrofit2.http.Field
/*
Implements the Pixelfed API
https://docs.pixelfed.org/technical-documentation/api-v1.html
@ -233,7 +235,7 @@ interface PixelfedAPI {
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Part file: MultipartBody.Part
): Call<Attachment>
): Observable<Attachment>
// get instance configuration
@GET("/api/v1/instance")
@ -245,4 +247,3 @@ interface PixelfedAPI {
@Header("Authorization") authorization: String
) : Call<DiscoverPosts>
}

View File

@ -15,7 +15,6 @@ class ThemeUtils {
fun setThemeFromPreferences(preferences: SharedPreferences, resources : Resources) {
val themes = resources.getStringArray(R.array.theme_values)
val theme = preferences.getString("theme", "")
Log.e("themePref", theme!!)
//Set the theme
when(theme) {
//Light

View File

@ -1,56 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/upload_error_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#90000000"
android:text="@string/media_upload_failed"
android:textColor="@color/colorPrimaryError"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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_constraintStart_toStartOf="@id/upload_error_text_view"
app:layout_constraintTop_toBottomOf="@id/upload_error_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/post_creation_picture_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="TODO" />
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/posting_image_accessibility_hint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<LinearLayout
android:layout_width="match_parent"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/buttonConstraints"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="bottom"
android:background="#88000000">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="@string/description"
app:errorEnabled="true"
android:layout_gravity="fill_horizontal"
android:paddingStart="15dp"
android:textColorHint="@color/colorPrimaryTab"
app:errorTextColor="@color/colorPrimaryError">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/new_post_description_input_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:ems="10"
android:inputType="textMultiLine"
android:textColor="@color/colorPrimary"/>
</com.google.android.material.textfield.TextInputLayout>
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/textInputLayout2">
<Button
android:id="@+id/post_creation_send_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="@color/colorButtonBg"
android:enabled="false"
android:visibility="gone"
android:text="@string/send"
android:textColor="@color/colorButtonText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/posting_progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send"
android:backgroundTint="@color/colorButtonBg"
android:textColor="@color/colorButtonText"
android:layout_margin="15dp"
android:layout_gravity="center_vertical"
tools:ignore="PrivateResource"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</merge>
<ProgressBar
android:id="@+id/uploadProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/textInputLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/description"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:textColorHint="@color/colorPrimaryTab"
app:errorEnabled="true"
app:errorTextColor="@color/colorPrimaryError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/buttonConstraints"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/new_post_description_input_field"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ems="10"
android:inputType="textMultiLine"
android:textColor="@color/colorPrimary" />
</com.google.android.material.textfield.TextInputLayout>
<com.mikepenz.iconics.view.IconicsTextView
android:id="@+id/upload_completed_textview"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/media_upload_completed"
android:textColor="@android:color/holo_green_light"
android:textSize="16sp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -35,7 +35,6 @@
<string name="domain_of_your_instance">اسم نطاق مثيل خادمك</string>
<string name="login_connection_required_once">يجب أن تكون متصلا بالأنترنت لتتمكن مِن إضافة أول حساب واستخدام PixelDroid :(</string>
<string name="capture_button_alt">لقطة شاشة</string>
<string name="you_are_in_offline_mode">إنك غير متصل الآن ولكنه لا يزال بإمكانك عرض بعض المحتوى!</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / وسائط مخفية
\n (اضعط للعرض)</string>
<string name="lbl_contrast">تباين عالٍ</string>

View File

@ -37,7 +37,6 @@
<string name="CommentDisplay">mostrar…</string>
<string name="domain_of_your_instance">Domini de la teva instància</string>
<string name="connect_to_pixelfed">Connectat a Pixelfed</string>
<string name="you_are_in_offline_mode">Estàs en mode fora de línia, però encara pots veure algun contingut!</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Imatges ocultes
\n(fes clic per mostrar)</string>
<string name="add_account_description">Afegeix un altre compte</string>

View File

@ -37,7 +37,6 @@
<string name="domain_of_your_instance">Domain Ihrer Instanz</string>
<string name="connect_to_pixelfed">Mit Pixelfed verbinden</string>
<string name="login_connection_required_once">Sie müssen online sein, um das erste Konto hinzuzufügen und um PixelDroid verwenden zu können :(</string>
<string name="you_are_in_offline_mode">Sie befinden sich im Offline-Modus, aber Sie können immer noch einige Inhalte ansehen!</string>
<string name="switch_camera_button_alt">Kamera wechseln</string>
<string name="registration_failed">Konnte die App nicht mit diesem Server verbinden</string>
<string name="instance_error">Konnte die Informationen der Instanz nicht abrufen</string>

View File

@ -36,7 +36,6 @@
<string name="CommentDisplay">para mostrar…</string>
<string name="domain_of_your_instance">Dominio de tu nodo</string>
<string name="connect_to_pixelfed">Conectar con Pixelfed</string>
<string name="you_are_in_offline_mode">Estás en modo sin conexión, ¡pero todavía puedes ver algo de contenido!</string>
<string name="login_connection_required_once">Necesitas estar conectado para poder añadir la primera cuenta y utilizar PixelDroid :(</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Contenido Oculto
\n (haz clic para mostrar)</string>

View File

@ -24,7 +24,6 @@
<string name="app_name">PixelDroid</string>
<string name="gallery_button_alt">Galeria</string>
<string name="NoCommentsToShow">Iruzkinik gabeko argitalpena…</string>
<string name="you_are_in_offline_mode">Lineaz kanpo editatzeko moduan zaude, baina hala ere eduki batzuk ikus ditzakezu!</string>
<string name="theme_title">Aplikazioaren gaia</string>
<string name="tab_edit">EDITATU</string>
<string name="switch_camera_button_alt">Aldatu kamera</string>

View File

@ -36,7 +36,6 @@
<string name="CommentDisplay">برای نمایش…</string>
<string name="domain_of_your_instance">دامنهٔ نمونهٔ شما</string>
<string name="connect_to_pixelfed">اتصال به پیکسل‌فد</string>
<string name="you_are_in_offline_mode">شما در حالت برخط نیستید، اما همچنان می‌توانید برخی محتواها را ببینید!</string>
<string name="app_name">پیکسل‌دروید</string>
<string name="menu_account">نمایهٔ من</string>
<string name="registration_failed">نتوانستیم برنامه را روی این کارساز ثبت کنیم</string>

View File

@ -35,7 +35,6 @@
<string name="gallery_button_alt">Galerie</string>
<string name="NoCommentsToShow">Aucun commentaire dans cette publication…</string>
<string name="domain_of_your_instance">Domaine de votre instance</string>
<string name="you_are_in_offline_mode">Vous êtes en mode hors ligne mais vous pouvez continuer accéder à un certain contenu !</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Média caché
\n (appuyez pour afficher)</string>
<string name="CommentDisplay">pour afficher…</string>

View File

@ -35,7 +35,6 @@
<string name="domain_of_your_instance">Dominio da túa instancia</string>
<string name="connect_to_pixelfed">Conectar con Pixelfed</string>
<string name="login_connection_required_once">Debes ter conexión a internet para poder engadir a conta e usar PixelDroid :(</string>
<string name="you_are_in_offline_mode">Non tes conexión, pero aínda así podes ver algún contido!</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Agochado
\n(preme para amosar)</string>
<string name="app_name">PixelDroid</string>

View File

@ -37,7 +37,6 @@
<string name="gallery_button_alt">Galleria</string>
<string name="domain_of_your_instance">Dominio della tua istanza</string>
<string name="login_connection_required_once">È necessario connettersi a Internet almeno una volta per utilizzare PixelDroid :(</string>
<string name="you_are_in_offline_mode">Sei in modalità offline, ma puoi comunque visualizzare alcuni contenuti!</string>
<string name="invalid_domain">Dominio non valido</string>
<string name="tab_edit">MODIFICA</string>
</resources>

View File

@ -39,7 +39,6 @@
<string name="tab_edit">編集</string>
<string name="image_download_failed">ダウンロードに失敗しました、もう一度実行してください</string>
<string name="NoCommentsToShow">この投稿にはコメントがありません…</string>
<string name="you_are_in_offline_mode">オフラインモードです、一部のコンテンツは引き続き表示できます</string>
<string name="add_account_description">他のPixelfedアカウントを追加</string>
<string name="add_account_name">アカウントを追加</string>
<string name="instance_error">インスタンス情報が取得できませんでした</string>

View File

@ -39,7 +39,6 @@
<string name="domain_of_your_instance">Domein van je instance</string>
<string name="connect_to_pixelfed">Met Pixelfed verbinden</string>
<string name="login_connection_required_once">Je moet met het internet verbonden zijn om je eerste account toe te voegen en PixelDroid te kunnen gebruiken :(</string>
<string name="you_are_in_offline_mode">Je bent in de offline modus, maar je kan nog steeds bepaalde inhoud zien!</string>
<string name="add_account_description">Andere Pixelfed account toevoegen</string>
<string name="add_account_name">Account toevoegen</string>
</resources>

View File

@ -12,7 +12,6 @@
<string name="invalid_domain">Domínio inválido</string>
<string name="add_account_description">Adicionar outra conta Pixelfed</string>
<string name="add_account_name">Adicionar Conta</string>
<string name="you_are_in_offline_mode">Você está em modo offline, mas ainda pode ver alguns conteúdos!</string>
<string name="login_connection_required_once">Você precisa estar conectado para poder adicionar a primeira conta e usar o PixelDroid :(</string>
<string name="connect_to_pixelfed">Conectar-se à Pixelfed</string>
<string name="domain_of_your_instance">Domínio da sua instância</string>

View File

@ -35,7 +35,6 @@
<string name="image_download_failed">Скачивание не удалось, попробуйте ещё раз</string>
<string name="share_picture">Поделиться изображение…</string>
<string name="login_connection_required_once">Вам необходимо быть в сети что бы добавить аккаунт и использовать PixelDroid :(</string>
<string name="you_are_in_offline_mode">Вы в оффлайн режиме, но вы можете видеть некоторый контент!</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Скрытое медиа
\n (кликните что бы показать)</string>
<string name="registration_failed">Не удалось зарегистрировать приложение на этом инстансе</string>

View File

@ -37,7 +37,6 @@
<string name="NoCommentsToShow">Inga kommentarer på detta inlägg…</string>
<string name="CommentDisplay">att visa…</string>
<string name="connect_to_pixelfed">Anslut till Pixelfed</string>
<string name="you_are_in_offline_mode">Du är i nedkopplad läge, men du kan ändå se lite innehåll!</string>
<string name="cw_nsfw_hidden_media_n_click_to_show">CW / NSFW / Dold media
\n(Tryck för att visa)</string>
<string name="add_account_description">Lägg till ett annat Pixelfed-konto</string>

View File

@ -34,7 +34,6 @@
<string name="NoCommentsToShow">这条帖文下没有评论……</string>
<string name="CommentDisplay">显示……</string>
<string name="domain_of_your_instance">实例的域名</string>
<string name="you_are_in_offline_mode">您处于离线模式,但仍可以查看已缓存内容!</string>
<string name="connect_to_pixelfed">连接至 Pixelfed</string>
<string name="share_picture">分享图片……</string>
<string name="login_connection_required_once">您需要联网才能添加第一个帐户并使用 PixelDroid :(</string>

View File

@ -102,6 +102,12 @@
<!-- Sensitive media -->
<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 -->
<string name="media_upload_failed">{gmd_cloud_off} Media upload failed, try again or check network conditions</string>
<string name="posting_image_accessibility_hint">Image being posted</string>
<string name="retry">Retry</string>
</resources>