Finish implementing carousel+grid postcreation

This commit is contained in:
Matthieu 2021-01-12 17:16:37 +01:00
parent 4b8f47155c
commit 8bfbe2fbb5
40 changed files with 1071 additions and 113 deletions

View File

@ -4,6 +4,7 @@ plugins {
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'jacoco'
@ -11,7 +12,7 @@ apply plugin: 'jacoco'
android {
compileSdkVersion 30
buildToolsVersion '30.0.2'
buildToolsVersion '30.0.3'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -56,6 +57,7 @@ android {
}
buildFeatures {
viewBinding = true
dataBinding = true
}
apply plugin: 'kotlin-kapt'
@ -157,7 +159,7 @@ dependencies {
implementation 'com.github.ligi.tracedroid:lib:3.0'
implementation 'com.github.ligi.tracedroid:supportemail:3.0'
implementation 'com.github.ImaginativeShohag:Why-Not-Image-Carousel:v1.1.0'
implementation 'me.relex:circleindicator:2.1.4'
/**
* Not in release, so not mentioned in licenses list

View File

@ -3,6 +3,7 @@ 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.media.MediaScannerConnection
import android.net.Uri
@ -13,6 +14,8 @@ import android.provider.MediaStore
import android.provider.OpenableColumns
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
@ -26,6 +29,8 @@ import com.h.pixeldroid.MainActivity
import com.h.pixeldroid.R
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
@ -34,10 +39,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_post_creation.*
import kotlinx.android.synthetic.main.activity_post_creation.view.*
import kotlinx.android.synthetic.main.image_album_creation.view.*
import okhttp3.MultipartBody
import org.imaginativeworld.whynotimagecarousel.CarouselItem
import org.imaginativeworld.whynotimagecarousel.ImageCarousel
import retrofit2.HttpException
import java.io.File
import java.io.IOException
@ -100,6 +104,19 @@ class PostCreationActivity : BaseActivity() {
val carousel: ImageCarousel = findViewById(R.id.carousel)
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
carousel.layoutCarouselCallback = {
//TODO transition instead of at once
if(it){
// Became a carousel
toolbar3.visibility = VISIBLE
} else {
// Became a grid
toolbar3.visibility = INVISIBLE
}
}
carousel.addPhotoButtonCallback = {
addPhoto(applicationContext)
}
// get the description and send the post
findViewById<Button>(R.id.post_creation_send_button).setOnClickListener {
@ -123,8 +140,7 @@ class PostCreationActivity : BaseActivity() {
}
findViewById<ImageButton>(R.id.addPhotoButton).setOnClickListener {
val intent = Intent(it.context, CameraActivity::class.java)
this@PostCreationActivity.startActivityForResult(intent, MORE_PICTURES_REQUEST_CODE)
addPhoto(it.context)
}
findViewById<ImageButton>(R.id.savePhotoButton).setOnClickListener {
@ -139,10 +155,14 @@ class PostCreationActivity : BaseActivity() {
photoData.removeAt(currentPosition)
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
}
//TODO("have to fix upload to allow deleting!")
}
}
private fun addPhoto(context: Context){
val intent = Intent(context, CameraActivity::class.java)
this@PostCreationActivity.startActivityForResult(intent, MORE_PICTURES_REQUEST_CODE)
}
private fun savePicture(button: View, currentPosition: Int) {
val name = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
.format(System.currentTimeMillis()) + ".png"
@ -375,58 +395,4 @@ class PostCreationActivity : BaseActivity() {
}
}
}
/*
inner class PostCreationAdapter(private val posts: ArrayList<String>): RecyclerView.Adapter<PostCreationAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
if(viewType == 0) LayoutInflater.from(parent.context)
.inflate(R.layout.image_album_creation, parent, false)
else LayoutInflater.from(parent.context)
.inflate(R.layout.add_more_album_creation, parent, false)
return ViewHolder(view)
}
override fun getItemViewType(position: Int): Int {
if(position == posts.size) return 1
return 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if(position != posts.size) {
holder.bindImage()
} else{
holder.bindPlusButton()
}
}
override fun getItemCount(): Int = posts.size + 1
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindImage() {
val image = Uri.parse(
posts[adapterPosition]
)
// load image
Glide.with(itemView.context)
.load(image)
.centerCrop()
.into(itemView.galleryImage)
// adding click or tap handler for the image layout
itemView.setOnClickListener {
this@PostCreationActivity.onClick(adapterPosition)
}
}
fun bindPlusButton() {
itemView.setOnClickListener {
val intent = Intent(itemView.context, CameraActivity::class.java)
this@PostCreationActivity.startActivityForResult(intent, MORE_PICTURES_REQUEST_CODE)
}
}
}
}
*/
}

View File

@ -169,7 +169,7 @@ class CameraFragment : Fragment() {
}
/** Declare and bind preview, capture and analysis use cases */
private fun bindCameraUseCases() {
private fun bindCameraUseCases(forceRebind: Boolean = false) {
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { viewFinder.display?.getRealMetrics(it) }
@ -188,7 +188,7 @@ class CameraFragment : Fragment() {
// CameraProvider
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
if (camera == null || preview == null || imageCapture == null || !cameraProvider.isBound(preview!!) || !cameraProvider.isBound(imageCapture!!)) {
if (forceRebind || camera == null || preview == null || imageCapture == null || !cameraProvider.isBound(preview!!) || !cameraProvider.isBound(imageCapture!!)) {
// Preview
@ -324,7 +324,7 @@ class CameraFragment : Fragment() {
REQUEST_CODE_PERMISSIONS
)
} else {
bindCameraUseCases()
bindCameraUseCases(forceRebind = true)
}
}
}

View File

@ -0,0 +1,110 @@
package com.h.pixeldroid.postCreation.carousel
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
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
) : 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)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return if(!carousel){
if(viewType == 0) {
val view =
LayoutInflater.from(parent.context)
.inflate(R.layout.image_album_creation, parent, false)
MyViewHolder(view, R.id.galleryImage)
} else {
val view =
LayoutInflater.from(parent.context)
.inflate(R.layout.add_more_album_creation, parent, false)
MyViewHolder(view, R.id.addPhotoSquare)
}
} else {
val view = LayoutInflater.from(parent.context)
.inflate(itemLayout, parent, false)
MyViewHolder(view, imageViewId)
}
}
override fun getItemCount(): Int {
return if(carousel) dataList.size
else dataList.size + 1
}
override fun getItemViewType(position: Int): Int {
if(position == dataList.size) return 1
return 0
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
if(carousel) {
holder.img.scaleType = imageScaleType
}
dataList.elementAtOrNull(position)?.let {
Glide.with(holder.itemView.context)
.load(it.imageUrl)
.placeholder(imagePlaceholder)
.into(holder.img)
}
// Init listeners
listener?.apply {
holder.itemView.setOnClickListener {
this.onClick(position)
}
holder.itemView.setOnLongClickListener {
this.onLongClick(position)
true
}
}
}
fun getItem(position: Int): CarouselItem? {
return if (position < dataList.size) {
dataList[position]
} else {
null
}
}
fun addAll(dataList: List<CarouselItem>) {
this.dataList.clear()
this.dataList.addAll(dataList)
notifyDataSetChanged()
}
fun add(item: CarouselItem) {
this.dataList.add(item)
notifyItemInserted(dataList.size - 1)
}
}

View File

@ -0,0 +1,8 @@
package com.h.pixeldroid.postCreation.carousel
data class CarouselItem constructor(
val imageUrl: String? = null,
val caption: String? = null
) {
constructor(imageUrl: String? = null) : this(imageUrl, null)
}

View File

@ -0,0 +1,17 @@
package com.h.pixeldroid.postCreation.carousel
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class CarouselLinearLayoutManager(
context: Context,
orientation: Int,
reverseLayout: Boolean
) : LinearLayoutManager(context, orientation, reverseLayout) {
override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
super.onLayoutChildren(recycler, state)
scrollHorizontallyBy(0, recycler, state)
}
}

View File

@ -0,0 +1,615 @@
package com.h.pixeldroid.postCreation.carousel
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
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 androidx.annotation.Dimension
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.*
import com.h.pixeldroid.R
import me.relex.circleindicator.CircleIndicator2
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
class ImageCarousel(
@NotNull context: Context,
@Nullable private var attributeSet: AttributeSet?
) : ConstraintLayout(context, attributeSet), OnItemClickListener {
private var adapter: CarouselAdapter? = null
private val scaleTypeArray = arrayOf(
ImageView.ScaleType.MATRIX,
ImageView.ScaleType.FIT_XY,
ImageView.ScaleType.FIT_START,
ImageView.ScaleType.FIT_CENTER,
ImageView.ScaleType.FIT_END,
ImageView.ScaleType.CENTER,
ImageView.ScaleType.CENTER_CROP,
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 var snapHelper: SnapHelper = PagerSnapHelper()
var indicator: CircleIndicator2? = null
set(newIndicator) {
indicator?.apply {
// if we remove it form the view, then the caption textView constraint won't work.
this.visibility = View.GONE
isBuiltInIndicator = false
}
field = newIndicator
initIndicator()
}
private var btnPrevious: View? = null
private var btnNext: View? = null
private var btnGrid: View? = null
private var btnCarousel: View? = null
private var isBuiltInIndicator = false
private var data: List<CarouselItem>? = null
var onItemClickListener: OnItemClickListener? = this
set(value) {
field = value
adapter?.listener = onItemClickListener
}
var onScrollListener: CarouselOnScrollListener? = null
set(value) {
field = value
initOnScrollStateChange()
}
/**
* Get or set current item position
*/
var currentPosition = -1
get() {
return snapHelper.getSnapPosition(recyclerView.layoutManager)
}
set(value) {
val position = when {
value >= data?.size ?: 0 -> {
-1
}
value < 0 -> {
-1
}
else -> {
value
}
}
field = position
if (position != -1) {
recyclerView.smoothScrollToPosition(position)
}
}
/**
* ****************************************************************
* Attributes
* ****************************************************************
*/
var showCaption = false
set(value) {
field = value
tvCaption.visibility = if (showCaption) View.VISIBLE else View.GONE
}
@Dimension(unit = Dimension.PX)
var captionTextSize: Int = 0
set(value) {
field = value
tvCaption.setTextSize(TypedValue.COMPLEX_UNIT_PX, captionTextSize.toFloat())
}
var showIndicator = false
set(value) {
field = value
when {
indicator == null -> initIndicator()
value -> indicator?.visibility = View.VISIBLE
else -> indicator?.visibility = View.INVISIBLE
}
}
var showNavigationButtons = false
set(value) {
field = value
previousButtonContainer.visibility =
if (showNavigationButtons) View.VISIBLE else View.GONE
nextButtonContainer.visibility =
if (showNavigationButtons) View.VISIBLE else View.GONE
}
var imageScaleType: ImageView.ScaleType = ImageView.ScaleType.CENTER_INSIDE
set(value) {
field = value
initAdapter()
}
var carouselBackground: Drawable? = null
set(value) {
field = value
recyclerView.background = carouselBackground
}
var imagePlaceholder: Drawable? = null
set(value) {
field = value
initAdapter()
}
@LayoutRes
var itemLayout: Int = R.layout.item_carousel
set(value) {
field = value
initAdapter()
}
@IdRes
var imageViewId: Int = R.id.img
set(value) {
field = value
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
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()
}
}
@Dimension(unit = Dimension.PX)
var nextButtonMargin: Int = 0
set(value) {
field = value
val nextButtonParams = nextButtonContainer.layoutParams as LayoutParams
nextButtonParams.setMargins(
0,
0,
nextButtonMargin,
0
)
nextButtonContainer.layoutParams = nextButtonParams
}
var showLayoutSwitchButton: Boolean = true
set(value) {
field = value
btnGrid = findViewById<ImageButton>(R.id.switchToGridButton)
btnCarousel = findViewById<ImageButton>(R.id.switchToCarouselButton)
btnGrid?.setOnClickListener {
layoutCarousel = false
}
btnCarousel?.setOnClickListener {
layoutCarousel = true
}
if(value){
if(layoutCarousel){
btnGrid?.visibility = VISIBLE
btnCarousel?.visibility = GONE
} else {
btnGrid?.visibility = GONE
btnCarousel?.visibility = VISIBLE
}
} else {
btnGrid?.visibility = GONE
btnCarousel?.visibility = GONE
}
}
var layoutCarouselCallback: ((Boolean) -> Unit)? = null
var layoutCarousel: Boolean = true
set(value) {
field = value
if(value){
recyclerView.layoutManager = CarouselLinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
btnNext?.visibility = VISIBLE
btnPrevious?.visibility = VISIBLE
} else {
recyclerView.layoutManager = GridLayoutManager(context, 3)
btnNext?.visibility = GONE
btnPrevious?.visibility = GONE
}
showIndicator = value
layoutCarouselCallback?.let { it(value) }
//update layout switch button to make it take into account the change
showLayoutSwitchButton = showLayoutSwitchButton
initAdapter()
}
var addPhotoButtonCallback: (() -> Unit)? = null
init {
initViews()
initAttributes()
initAdapter()
initListeners()
}
private fun initViews() {
carouselView = LayoutInflater.from(context).inflate(R.layout.image_carousel, this)
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.setHasFixedSize(true)
// For marquee effect
tvCaption.isSelected = true
}
private fun initAttributes() {
context.theme.obtainStyledAttributes(
attributeSet,
R.styleable.ImageCarousel,
0,
0
).apply {
try {
showCaption = getBoolean(
R.styleable.ImageCarousel_showCaption,
true
)
captionTextSize = getDimension(
R.styleable.ImageCarousel_captionTextSize,
14.spToPx(context).toFloat()
).toInt()
showIndicator = getBoolean(
R.styleable.ImageCarousel_showIndicator,
true
)
imageScaleType = scaleTypeArray[
getInteger(
R.styleable.ImageCarousel_imageScaleType,
ImageView.ScaleType.CENTER_INSIDE.ordinal
)
]
carouselBackground = ColorDrawable(Color.parseColor("#333333"))
imagePlaceholder = getDrawable(
R.styleable.ImageCarousel_imagePlaceholder
) ?: ContextCompat.getDrawable(context, R.drawable.ic_picture_fallback)
itemLayout = getResourceId(
R.styleable.ImageCarousel_itemLayout,
R.layout.item_carousel
)
imageViewId = getResourceId(
R.styleable.ImageCarousel_imageViewId,
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(
R.styleable.ImageCarousel_showNavigationButtons,
true
)
layoutCarousel = getBoolean(
R.styleable.ImageCarousel_layoutCarousel,
true
)
showLayoutSwitchButton = getBoolean(
R.styleable.ImageCarousel_showLayoutSwitchButton,
true
)
} finally {
recycle()
}
}
}
private fun initAdapter() {
adapter = CarouselAdapter(
itemLayout = itemLayout,
imageViewId = imageViewId,
listener = onItemClickListener,
imageScaleType = imageScaleType,
imagePlaceholder = imagePlaceholder,
carousel = layoutCarousel
)
recyclerView.adapter = adapter
data?.apply {
adapter?.addAll(this)
}
indicator?.apply {
try {
adapter?.registerAdapterDataObserver(this.adapterDataObserver)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}
private fun initListeners() {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (showCaption) {
val position = snapHelper.getSnapPosition(recyclerView.layoutManager)
if (position >= 0) {
val dataItem = adapter?.getItem(position)
dataItem?.apply {
tvCaption.text = this.caption
}
}
}
onScrollListener?.onScrolled(recyclerView, dx, dy)
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
onScrollListener?.apply {
val position = snapHelper.getSnapPosition(recyclerView.layoutManager)
val carouselItem = data?.get(position)
onScrollStateChanged(
recyclerView,
newState,
position,
carouselItem
)
}
}
})
}
private fun initIndicator() {
// If no custom indicator added, then default indicator will be shown.
if (indicator == null) {
indicator = carouselView.findViewById(R.id.indicator)
isBuiltInIndicator = true
}
snapHelper.apply {
try {
attachToRecyclerView(recyclerView)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
indicator?.apply {
if (isBuiltInIndicator) {
// Indicator visibility
this.visibility = if (showIndicator) View.VISIBLE else View.INVISIBLE
}
// Attach to recyclerview
attachToRecyclerView(recyclerView, snapHelper)
// Observe the adapter
adapter?.let { carouselAdapter ->
try {
carouselAdapter.registerAdapterDataObserver(this.adapterDataObserver)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}
}
private fun initOnScrollStateChange() {
data?.apply {
if (isNotEmpty()) {
onScrollListener?.onScrollStateChanged(
recyclerView,
RecyclerView.SCROLL_STATE_IDLE,
0,
this[0]
)
}
}
}
/**
* Add data to the carousel.
*/
fun addData(data: List<CarouselItem>) {
adapter?.apply {
addAll(data)
this@ImageCarousel.data = data
initOnScrollStateChange()
}
}
/**
* Goto previous item.
*/
fun previous() {
currentPosition--
}
/**
* Goto next item.
*/
fun next() {
currentPosition++
}
override fun onClick(position: Int) {
if(position == (data?.size ?: 0) ){
addPhotoButtonCallback?.invoke()
} else {
if (!layoutCarousel) layoutCarousel = true
currentPosition = position
}
}
override fun onLongClick(position: Int) {
//if(!layoutCarousel && position != (data?.size ?: 0) ) {
//TODO Highlight selected, show toolbar?
// Enable "long click mode?"
//}
}
}
interface OnItemClickListener {
fun onClick(position: Int)
fun onLongClick(position: Int)
}
interface CarouselOnScrollListener {
fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int,
position: Int,
carouselItem: CarouselItem?
) {}
fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {}
}

View File

@ -0,0 +1,52 @@
package com.h.pixeldroid.postCreation.carousel
import android.content.Context
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper
/**
* This method converts device specific pixels to density independent pixels.
*/
fun Int.pxToDp(context: Context): Int {
return (this / (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).toInt()
}
/**
* This method converts dp unit to equivalent pixels, depending on device density.
*/
fun Int.dpToPx(context: Context): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
context.resources.displayMetrics
).toInt()
}
/**
* This method converts sp unit to equivalent pixels, depending on device density.
*/
fun Int.spToPx(context: Context): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
this.toFloat(),
context.resources.displayMetrics
).toInt()
}
/**
* Get current snap item position of a recyclerView.
*
* @param layoutManager Target recyclerView
* @return Position of the item or RecyclerView.NO_POSITION (-1)
*/
fun SnapHelper.getSnapPosition(layoutManager: RecyclerView.LayoutManager?): Int {
if (layoutManager == null) {
return RecyclerView.NO_POSITION
}
val snapView: View = this.findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
return layoutManager.getPosition(snapView)
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM8,20L4,20v-4h4v4zM8,14L4,14v-4h4v4zM8,8L4,8L4,4h4v4zM14,20h-4v-4h4v4zM14,14h-4v-4h4v4zM14,8h-4L10,4h4v4zM20,20h-4v-4h4v4zM20,14h-4v-4h4v4zM20,8h-4L16,4h4v4z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7,19h10L17,4L7,4v15zM2,17h4L6,6L2,6v11zM18,6v11h4L22,6h-4z"
android:fillColor="#000000"/>
</vector>

View File

@ -3,7 +3,8 @@
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">
android:layout_height="match_parent"
tools:context=".postCreation.PostCreationActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/upload_error"
@ -42,7 +43,7 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<org.imaginativeworld.whynotimagecarousel.ImageCarousel
<com.h.pixeldroid.postCreation.carousel.ImageCarousel
android:id="@+id/carousel"
android:layout_width="match_parent"
android:layout_height="0dp"
@ -59,7 +60,7 @@
android:textSize="16sp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar3" />
app:layout_constraintBottom_toTopOf="@id/postTextInputLayout"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/buttonConstraints"
@ -142,50 +143,54 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/savePhotoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="TODO"
android:contentDescription="@string/save_to_gallery"
android:tooltipText='@string/save_to_gallery'
android:src="@drawable/download_file_30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/removePhotoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="TODO"
android:contentDescription="@string/delete"
android:tooltipText='@string/delete'
android:src="@drawable/delete_30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/savePhotoButton"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/editPhotoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="TODO"
android:contentDescription="@string/edit"
android:tooltipText='@string/edit'
android:src="@drawable/ic_baseline_edit_30"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/removePhotoButton"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/addPhotoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="30dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="TODO"
android:contentDescription="@string/add_photo"
android:tooltipText='@string/add_photo'
android:src="@drawable/add_photo_alternate_white_30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -7,6 +7,7 @@
android:focusable="true">
<ImageView
android:id="@+id/addPhotoSquare"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"

View File

@ -7,10 +7,6 @@
android:foreground="?selectableItemBackground"
android:clickable="true"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/galleryImage"
android:layout_width="match_parent"
@ -18,19 +14,4 @@
android:padding="8dp"
android:scaleType="centerCrop"
android:contentDescription="@string/post_image" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/circle_black_24dp"
android:backgroundTint="#7A3E3C3C"
android:foreground="@drawable/ic_baseline_edit_30"
android:foregroundGravity="center"
android:foregroundTint="#FFFFFF"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="@string/click_image_edit" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.h.pixeldroid.postCreation.SquareLayout>

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:foregroundTint="#FFFFFF">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:itemCount="5"
tools:listitem="@layout/item_carousel" />
<TextView
android:id="@+id/tv_caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="marquee"
android:gravity="center"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:textAlignment="center"
android:textColor="@color/white"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/indicator"
app:layout_goneMarginBottom="8dp"
tools:text="@tools:sample/lorem[5]" />
<me.relex.circleindicator.CircleIndicator2
android:id="@+id/indicator"
android:layout_width="wrap_content"
android:layout_height="32dp"
app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
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"
app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/recyclerView" />
<FrameLayout
android:id="@+id/next_button_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/recyclerView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/recyclerView" />
<ImageButton
android:id="@+id/switchToGridButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="TODO"
android:src="@drawable/grid_on_black_24dp"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="@+id/indicator"
app:layout_constraintEnd_toStartOf="@+id/indicator"
app:layout_constraintTop_toTopOf="@+id/indicator" />
<ImageButton
android:id="@+id/switchToCarouselButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="TODO"
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"
app:layout_constraintBottom_toBottomOf="@+id/indicator"
app:layout_constraintTop_toTopOf="@+id/indicator" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside"
tools:ignore="ContentDescription"
tools:src="@tools:sample/backgrounds/scenic" />

View File

@ -0,0 +1,19 @@
<?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

@ -0,0 +1,19 @@
<?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

@ -19,7 +19,7 @@
<string name="whats_an_instance">ماذا نعني بمثيل الخادم؟</string>
<string name="logout">الخروج</string>
<string name="tab_filters">الفلاتر</string>
<string name="tab_edit">تعديل</string>
<string name="edit">تعديل</string>
<string name="save_to_gallery">احتفظ بها في المعرض…</string>
<string name="image_download_downloading">التنزيل جارٍ…</string>
<string name="image_download_success">تم التنزيل بنجاح</string>

View File

@ -30,7 +30,7 @@
<string name="lbl_contrast">CONTRAST</string>
<string name="lbl_saturation">SATURACIÓ</string>
<string name="tab_filters">FILTRES</string>
<string name="tab_edit">EDITAR</string>
<string name="edit">EDITAR</string>
<string name="capture_button_alt">Captura</string>
<string name="gallery_button_alt">Galeria</string>
<string name="NoCommentsToShow">No hi ha comentaris en aquesta publicació …</string>

View File

@ -22,7 +22,7 @@
<string name="logout">Abmelden</string>
<string name="lbl_brightness">Helligkeit</string>
<string name="tab_filters">Filter</string>
<string name="tab_edit">bearbeiten</string>
<string name="edit">bearbeiten</string>
<string name="lbl_contrast">Kontrast</string>
<string name="lbl_saturation">Sättigung</string>
<string name="NoCommentsToShow">Keine Kommentare zu diesem Beitrag…</string>

View File

@ -29,7 +29,7 @@
<string name="lbl_contrast">CONTRASTE</string>
<string name="lbl_saturation">SATURACIÓN</string>
<string name="tab_filters">FILTROS</string>
<string name="tab_edit">EDITAR</string>
<string name="edit">EDITAR</string>
<string name="capture_button_alt">Capturar</string>
<string name="gallery_button_alt">Galería</string>
<string name="NoCommentsToShow">No hay comentarios en esta publicación…</string>

View File

@ -25,7 +25,7 @@
<string name="gallery_button_alt">Galeria</string>
<string name="NoCommentsToShow">Iruzkinik gabeko argitalpena…</string>
<string name="theme_title">Aplikazioaren gaia</string>
<string name="tab_edit">EDITATU</string>
<string name="edit">EDITATU</string>
<string name="switch_camera_button_alt">Aldatu kamera</string>
<string name="capture_button_alt">Atera</string>
<string name="theme_header">Gaia</string>

View File

@ -23,7 +23,7 @@
<string name="lbl_contrast">تضاد</string>
<string name="lbl_saturation">اشباع</string>
<string name="tab_filters">پالایه‌ها</string>
<string name="tab_edit">ویرایش</string>
<string name="edit">ویرایش</string>
<string name="save_to_gallery">ذخیره در نگارخانه…</string>
<string name="image_download_failed">بارگیری شکست خورد، دوباره تلاش کنید</string>
<string name="image_download_downloading">در حال بارگیری…</string>

View File

@ -29,7 +29,7 @@
<string name="lbl_contrast">CONTRASTE</string>
<string name="lbl_saturation">SATURATION</string>
<string name="tab_filters">FILTRES</string>
<string name="tab_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>

View File

@ -21,7 +21,7 @@
<string name="lbl_contrast">CONTRASTE</string>
<string name="lbl_saturation">SATURACIÓN</string>
<string name="tab_filters">FILTROS</string>
<string name="tab_edit">EDITAR</string>
<string name="edit">EDITAR</string>
<string name="save_to_gallery">Gardar na Galería…</string>
<string name="image_download_downloading">Descargando…</string>
<string name="image_download_success">Imaxe descargada correctamente</string>

View File

@ -37,7 +37,7 @@
<string name="domain_of_your_instance">Dominio della tua istanza</string>
<string name="login_connection_required_once">Devi essere online per poter aggiungere il primo account e utilizzare PixelDroid :(</string>
<string name="invalid_domain">Dominio non valido</string>
<string name="tab_edit">MODIFICA</string>
<string name="edit">MODIFICA</string>
<string name="permission_denied">Permesso negato</string>
<string name="instance_error">Impossibile ottenere informazioni sull\'istanza</string>
<string name="save_image_failed">Impossibile salvare l\'immagine</string>

View File

@ -35,7 +35,7 @@
<string name="browser_launch_failed">ブラウザが起動できませんでした。インストールされているか確認してください。</string>
<string name="mention_notification">%1$s さんがあなたにメンションしました</string>
<string name="shared_notification">%1$s さんがあなたの投稿を再共有しました</string>
<string name="tab_edit">編集</string>
<string name="edit">編集</string>
<string name="image_download_failed">ダウンロードに失敗しました、もう一度実行してください</string>
<string name="NoCommentsToShow">この投稿にはコメントがありません</string>
<string name="add_account_description">他のPixelfedアカウントを追加</string>

View File

@ -30,7 +30,7 @@
<string name="lbl_contrast">CONTRAST</string>
<string name="lbl_saturation">VERZADIGING</string>
<string name="tab_filters">FILTERS</string>
<string name="tab_edit">BEWERKEN</string>
<string name="edit">BEWERKEN</string>
<string name="capture_button_alt">Vastleggen</string>
<string name="switch_camera_button_alt">Van camera wisselen</string>
<string name="gallery_button_alt">Gallerij</string>

View File

@ -33,7 +33,7 @@
<string name="lbl_contrast">KONTRAST</string>
<string name="lbl_saturation">NASYCENIE</string>
<string name="tab_filters">FILTRY</string>
<string name="tab_edit">EDYCJA</string>
<string name="edit">EDYCJA</string>
<string name="normal_filter">Zwykły</string>
<string name="busy_dialog_ok_button">OK, zaczekam.</string>
<string name="crop_result_error">Nie udało się pobrać obrazka po przycięciu</string>

View File

@ -22,7 +22,7 @@
<string name="image_download_downloading">Baixando o arquivo…</string>
<string name="image_download_failed">O download não deu certo, por favor, tente novamente</string>
<string name="save_to_gallery">Salvar na Galeria…</string>
<string name="tab_edit">EDITAR</string>
<string name="edit">EDITAR</string>
<string name="tab_filters">FILTROS</string>
<string name="lbl_saturation">SATURAÇÃO</string>
<string name="lbl_contrast">CONTRASTE</string>

View File

@ -15,7 +15,7 @@
<string name="lbl_contrast">КОНТРАСТ</string>
<string name="lbl_saturation">НАСЫЩЕННОСТЬ</string>
<string name="tab_filters">ФИЛЬТРЫ</string>
<string name="tab_edit">РЕДАКТИРОВАТЬ</string>
<string name="edit">РЕДАКТИРОВАТЬ</string>
<string name="save_to_gallery">Сохранить в Галерею…</string>
<string name="image_download_downloading">Сохранение…</string>
<string name="image_download_success">Изображение успешно сохранено</string>

View File

@ -29,7 +29,7 @@
<string name="lbl_contrast">KONTRAST</string>
<string name="lbl_saturation">FÄRGMÄTTNAD</string>
<string name="tab_filters">FILTER</string>
<string name="tab_edit">REDIGERA</string>
<string name="edit">REDIGERA</string>
<string name="capture_button_alt">Lagra</string>
<string name="switch_camera_button_alt">Byt kamera</string>
<string name="gallery_button_alt">Galleri</string>

View File

@ -22,7 +22,7 @@
<string name="share_picture">Поділитися фотографією…</string>
<string name="logout">Вийти</string>
<string name="NoCommentsToShow">Немає коментарів до цієї публікації…</string>
<string name="tab_edit">Редагувати</string>
<string name="edit">Редагувати</string>
<string name="app_name">PixelDroid</string>
<string name="menu_settings">Налаштування</string>
</resources>

View File

@ -24,7 +24,7 @@
<string name="lbl_contrast">对比度</string>
<string name="lbl_saturation">饱和度</string>
<string name="tab_filters">滤镜</string>
<string name="tab_edit">编辑</string>
<string name="edit">编辑</string>
<string name="save_to_gallery">保存到相册……</string>
<string name="image_download_failed">下载失败,请重试</string>
<string name="image_download_downloading">下载中……</string>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageCarousel">
<attr name="showCaption" format="boolean" />
<attr name="captionTextSize" format="dimension" />
<attr name="showIndicator" format="boolean" />
<attr name="showNavigationButtons" format="boolean" />
<attr name="imageScaleType" format="enum">
<enum name="matrix" value="0" />
<enum name="fitXY" value="1" />
<enum name="fitStart" value="2" />
<enum name="fitCenter" value="3" />
<enum name="fitEnd" value="4" />
<enum name="center" value="5" />
<enum name="centerCrop" value="6" />
<enum name="centerInside" value="7" />
</attr>
<attr name="imagePlaceholder" format="reference|color" />
<attr name="itemLayout" format="reference" />
<attr name="imageViewId" format="reference" />
<attr name="showLayoutSwitchButton" format="boolean" />
<attr name="layoutCarousel" format="boolean" />
</declare-styleable>
</resources>

View File

@ -14,5 +14,6 @@
<color name="filterLabelSelected">#221F20</color>
<color name="colorPrimaryError">#FF0000</color>
<color name="colorText">#FFFFFF</color>
<color name="white">#FFFFFF</color>
<color name="colorDrawing">#000000</color>
</resources>

View File

@ -56,7 +56,7 @@
<string name="lbl_contrast">CONTRAST</string>
<string name="lbl_saturation">SATURATION</string>
<string name="tab_filters">FILTERS</string>
<string name="tab_edit">EDIT</string>
<string name="edit">Edit</string>
<string name="filter_thumbnail">Thumbnail of filter</string>
<string name="normal_filter">Normal</string>
<string name="busy_dialog_text">Still processing image, wait for that to finish first!</string>

View File

@ -27,6 +27,6 @@
app:icon="@drawable/info_black_24dp">
<intent
android:targetPackage="com.h.pixeldroid"
android:targetClass="com.h.pixeldroid.AboutActivity"/>
android:targetClass="com.h.pixeldroid.settings.AboutActivity"/>
</Preference>
</PreferenceScreen>