Add image description functionality

This commit is contained in:
Matthieu 2021-01-25 00:02:03 +01:00
parent c801d629f3
commit 9dd6f3f1a8
16 changed files with 209 additions and 158 deletions

View File

@ -16,26 +16,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.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.api.objects.Instance
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
@ -54,7 +51,8 @@ private const val MORE_PICTURES_REQUEST_CODE = 0xffff
data class PhotoData(
var imageUri: Uri,
var uploadId: String? = null,
var progress: Int? = null
var progress: Int? = null,
var imageDescription: String? = null
)
class PostCreationActivity : BaseActivity() {
@ -102,7 +100,7 @@ class PostCreationActivity : BaseActivity() {
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){
@ -116,6 +114,9 @@ class PostCreationActivity : BaseActivity() {
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 +153,7 @@ 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) })
}
}
}
@ -283,7 +284,7 @@ class PostCreationActivity : BaseActivity() {
}
var postSub: Disposable? = null
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", requestBody.parts[0])
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", data.imageDescription, requestBody.parts[0])
postSub = inter
.subscribeOn(Schedulers.io())
@ -369,7 +370,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
@ -385,7 +386,7 @@ class PostCreationActivity : BaseActivity() {
photoData.add(PhotoData(imageUri))
}
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()
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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,37 @@ 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)
}
}
init {
@ -341,12 +329,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 +390,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(
@ -474,7 +453,11 @@ class ImageCarousel(
val dataItem = adapter?.getItem(position)
dataItem?.apply {
tvCaption.text = this.caption
caption.apply {
binding.editMediaDescriptionLayout.visibility = INVISIBLE
tvCaption.visibility = VISIBLE
currentDescription = this
}
}
}
}
@ -499,12 +482,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
}

View File

@ -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") description: String?,
@Part file: MultipartBody.Part
): Observable<Attachment>

View 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>

View File

@ -47,6 +47,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"/>

View File

@ -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>

View File

@ -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" />

View File

@ -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" />

View File

@ -146,7 +146,6 @@
<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>
</resources>

View File

@ -141,7 +141,6 @@
<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>
</resources>

View File

@ -108,7 +108,6 @@
<string name="report">Rapporter</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="language">Langue</string>

View File

@ -142,7 +142,6 @@
<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>

View File

@ -130,7 +130,6 @@
<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>

View File

@ -50,7 +50,11 @@
<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>
<!-- Post editing -->
<string name="lbl_brightness">BRIGHTNESS</string>
<string name="lbl_contrast">CONTRAST</string>
@ -64,6 +68,7 @@
<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>
<!-- Camera -->
<string name="capture_button_alt">Capture</string>
<string name="switch_camera_button_alt">Switch camera</string>
@ -156,7 +161,5 @@
<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>
</resources>