Merge branch 'edit_ux_improvements' into 'master'
Improve UX in post creation and edit activities Closes #267 See merge request pixeldroid/PixelDroid!285
This commit is contained in:
commit
01879cfd28
|
@ -4,14 +4,14 @@ plugins {
|
|||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
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 +56,7 @@ android {
|
|||
}
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
dataBinding = true
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
@ -83,6 +84,7 @@ dependencies {
|
|||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
|
||||
|
@ -157,6 +159,7 @@ dependencies {
|
|||
implementation 'com.github.ligi.tracedroid:lib:3.0'
|
||||
implementation 'com.github.ligi.tracedroid:supportemail:3.0'
|
||||
|
||||
implementation 'me.relex:circleindicator:2.1.4'
|
||||
|
||||
/**
|
||||
* Not in release, so not mentioned in licenses list
|
||||
|
|
|
@ -737,3 +737,31 @@
|
|||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/jetpack/androidx
|
||||
- artifact: androidx.databinding:databinding-adapters:+
|
||||
name: databinding-adapters
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
- artifact: androidx.databinding:databinding-runtime:+
|
||||
name: databinding-runtime
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
- artifact: androidx.databinding:databinding-common:+
|
||||
name: databinding-common
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/studio
|
||||
- artifact: androidx.lifecycle:lifecycle-common-java8:+
|
||||
name: lifecycle-common-java8
|
||||
copyrightHolder: Google Inc
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://developer.android.com/topic/libraries/architecture/index.html
|
||||
- artifact: me.relex:circleindicator:+
|
||||
name: circleindicator
|
||||
copyrightHolder: relex and contributors
|
||||
license: The Apache Software License, Version 2.0
|
||||
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
url: https://github.com/ongakuer/CircleIndicator
|
||||
|
|
|
@ -175,7 +175,7 @@ class HomeFeedTest {
|
|||
//Open the comment section
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(0, clickChildViewWithId(R.id.ViewComments)))
|
||||
(0, clickChildViewWithId(R.id.viewComments)))
|
||||
Thread.sleep(1000)
|
||||
onView(first(withId(R.id.commentContainer)))
|
||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||
|
@ -186,7 +186,7 @@ class HomeFeedTest {
|
|||
//Open the comment section
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<StatusViewHolder>
|
||||
(2, clickChildViewWithId(R.id.ViewComments)))
|
||||
(2, clickChildViewWithId(R.id.viewComments)))
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.list)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,6 +9,8 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.databinding.ActivityLoginBinding
|
||||
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.*
|
||||
|
@ -17,7 +19,6 @@ import com.h.pixeldroid.utils.db.storeInstance
|
|||
import com.h.pixeldroid.utils.hasInternet
|
||||
import com.h.pixeldroid.utils.normalizeDomain
|
||||
import com.h.pixeldroid.utils.openUrl
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -56,9 +57,12 @@ class LoginActivity : BaseActivity() {
|
|||
private lateinit var pixelfedAPI: PixelfedAPI
|
||||
private var inputVisibility: Int = View.GONE
|
||||
|
||||
private lateinit var binding: ActivityLoginBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_login)
|
||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
loadingAnimation(true)
|
||||
appName = getString(R.string.app_name)
|
||||
|
@ -66,14 +70,14 @@ class LoginActivity : BaseActivity() {
|
|||
preferences = getSharedPreferences("$PACKAGE_ID.pref", Context.MODE_PRIVATE)
|
||||
|
||||
if (hasInternet(applicationContext)) {
|
||||
connect_instance_button.setOnClickListener {
|
||||
registerAppToServer(normalizeDomain(editText.text.toString()))
|
||||
binding.connectInstanceButton.setOnClickListener {
|
||||
registerAppToServer(normalizeDomain(binding.editText.text.toString()))
|
||||
}
|
||||
whatsAnInstanceTextView.setOnClickListener{ whatsAnInstance() }
|
||||
binding.whatsAnInstanceTextView.setOnClickListener{ whatsAnInstance() }
|
||||
inputVisibility = View.VISIBLE
|
||||
} else {
|
||||
login_activity_connection_required.visibility = View.VISIBLE
|
||||
login_activity_connection_required_button.setOnClickListener {
|
||||
binding.loginActivityConnectionRequired.visibility = View.VISIBLE
|
||||
binding.loginActivityConnectionRequiredButton.setOnClickListener {
|
||||
finish()
|
||||
startActivity(intent)
|
||||
}
|
||||
|
@ -267,7 +271,7 @@ class LoginActivity : BaseActivity() {
|
|||
|
||||
private fun failedRegistration(message: String = getString(R.string.registration_failed)) {
|
||||
loadingAnimation(false)
|
||||
editText.error = message
|
||||
binding.editText.error = message
|
||||
wipeSharedSettings()
|
||||
}
|
||||
|
||||
|
@ -278,12 +282,12 @@ class LoginActivity : BaseActivity() {
|
|||
|
||||
private fun loadingAnimation(on: Boolean){
|
||||
if(on) {
|
||||
login_activity_instance_input_layout.visibility = View.GONE
|
||||
progressLayout.visibility = View.VISIBLE
|
||||
binding.loginActivityInstanceInputLayout.visibility = View.GONE
|
||||
binding.progressLayout.visibility = View.VISIBLE
|
||||
}
|
||||
else {
|
||||
login_activity_instance_input_layout.visibility = inputVisibility
|
||||
progressLayout.visibility = View.GONE
|
||||
binding.loginActivityInstanceInputLayout.visibility = inputVisibility
|
||||
binding.progressLayout.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,22 +14,21 @@ import androidx.core.view.GravityCompat
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.room.withTransaction
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.utils.db.addUser
|
||||
import com.h.pixeldroid.databinding.ActivityMainBinding
|
||||
import com.h.pixeldroid.postCreation.camera.CameraFragment
|
||||
import com.h.pixeldroid.utils.db.entities.HomeStatusDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.PublicFeedStatusDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.posts.feeds.cachedFeeds.notifications.NotificationsFragment
|
||||
import com.h.pixeldroid.posts.feeds.cachedFeeds.postFeeds.PostFeedFragment
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.profile.ProfileActivity
|
||||
import com.h.pixeldroid.searchDiscover.SearchDiscoverFragment
|
||||
import com.h.pixeldroid.settings.SettingsActivity
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.db.addUser
|
||||
import com.h.pixeldroid.utils.db.entities.HomeStatusDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.PublicFeedStatusDatabaseEntity
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.hasInternet
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.materialdrawer.iconics.iconicsIcon
|
||||
|
@ -40,12 +39,8 @@ import com.mikepenz.materialdrawer.model.interfaces.*
|
|||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.widget.AccountHeaderView
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.ligi.tracedroid.sending.TraceDroidEmailSender
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
|
@ -57,11 +52,14 @@ class MainActivity : BaseActivity() {
|
|||
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
@ExperimentalPagingApi
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.AppTheme_NoActionBar)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
TraceDroidEmailSender.sendStackTraces("contact@pixeldroid.org", this)
|
||||
|
||||
|
@ -96,8 +94,8 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
private fun setupDrawer() {
|
||||
main_drawer_button.setOnClickListener{
|
||||
drawer_layout.open()
|
||||
binding.mainDrawerButton.setOnClickListener{
|
||||
binding.drawerLayout.open()
|
||||
}
|
||||
|
||||
header = AccountHeaderView(this).apply {
|
||||
|
@ -112,7 +110,7 @@ class MainActivity : BaseActivity() {
|
|||
descriptionRes = R.string.add_account_description
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_add
|
||||
}, 0)
|
||||
attachToSliderView(drawer)
|
||||
attachToSliderView(binding.drawer)
|
||||
dividerBelowHeader = false
|
||||
closeDrawerOnProfileListClick = true
|
||||
}
|
||||
|
@ -144,7 +142,7 @@ class MainActivity : BaseActivity() {
|
|||
//with the received one. This happens asynchronously.
|
||||
getUpdatedAccount()
|
||||
|
||||
drawer.itemAdapter.add(
|
||||
binding.drawer.itemAdapter.add(
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.menu_account
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_person
|
||||
|
@ -157,7 +155,7 @@ class MainActivity : BaseActivity() {
|
|||
nameRes = R.string.logout
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_close
|
||||
})
|
||||
drawer.onDrawerItemClickListener = { v, drawerItem, position ->
|
||||
binding.drawer.onDrawerItemClickListener = { v, drawerItem, position ->
|
||||
when (position){
|
||||
1 -> launchActivity(ProfileActivity())
|
||||
2 -> launchActivity(SettingsActivity())
|
||||
|
@ -271,7 +269,7 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
|
||||
private fun setupTabs(tab_array: List<() -> Fragment>){
|
||||
view_pager.adapter = object : FragmentStateAdapter(this) {
|
||||
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return tab_array[position]()
|
||||
}
|
||||
|
@ -281,7 +279,7 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
TabLayoutMediator(tabs, view_pager) { tab, position ->
|
||||
TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position ->
|
||||
tab.icon = ContextCompat.getDrawable(applicationContext,
|
||||
when(position){
|
||||
0 -> R.drawable.ic_home_white_24dp
|
||||
|
@ -312,8 +310,8 @@ class MainActivity : BaseActivity() {
|
|||
* Closes the drawer if it is open, when we press the back button
|
||||
*/
|
||||
override fun onBackPressed() {
|
||||
if(drawer_layout.isDrawerOpen(GravityCompat.START)){
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
if(binding.drawerLayout.isDrawerOpen(GravityCompat.START)){
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
|
|
@ -1,75 +1,86 @@
|
|||
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
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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 androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.MainActivity
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.postCreation.camera.CameraActivity
|
||||
import com.h.pixeldroid.postCreation.carousel.CarouselItem
|
||||
import com.h.pixeldroid.postCreation.carousel.ImageCarousel
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import com.h.pixeldroid.utils.api.objects.Instance
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
|
||||
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.image_album_creation.view.*
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
private const val TAG = "Post Creation Activity"
|
||||
private const val MORE_PICTURES_REQUEST_CODE = 0xffff
|
||||
|
||||
data class PhotoData(
|
||||
var imageUri: Uri,
|
||||
var uploadId: String? = null,
|
||||
var progress: Int? = null
|
||||
)
|
||||
|
||||
class PostCreationActivity : BaseActivity() {
|
||||
|
||||
private lateinit var recycler : RecyclerView
|
||||
private lateinit var adapter : PostCreationAdapter
|
||||
|
||||
private lateinit var accessToken: String
|
||||
private lateinit var pixelfedAPI: PixelfedAPI
|
||||
|
||||
private var muListOfIds: MutableList<String> = mutableListOf()
|
||||
private var progressList: ArrayList<Int> = arrayListOf()
|
||||
|
||||
|
||||
private var positionResult = 0
|
||||
private var user: UserDatabaseEntity? = null
|
||||
|
||||
private var posts: ArrayList<String> = ArrayList()
|
||||
private val photoData: ArrayList<PhotoData> = ArrayList()
|
||||
|
||||
private lateinit var binding: ActivityPostCreationBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_post_creation)
|
||||
binding = ActivityPostCreationBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// get image URIs
|
||||
if(intent.clipData != null) {
|
||||
val count = intent.clipData!!.itemCount
|
||||
for (i in 0 until count) {
|
||||
val imageUri: String = intent.clipData!!.getItemAt(i).uri.toString()
|
||||
posts.add(imageUri)
|
||||
intent.clipData!!.getItemAt(i).uri.let {
|
||||
photoData.add(PhotoData(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,9 +88,7 @@ class PostCreationActivity : BaseActivity() {
|
|||
|
||||
val instances = db.instanceDao().getAll()
|
||||
|
||||
val textField = findViewById<TextInputLayout>(R.id.postTextInputLayout)
|
||||
|
||||
textField.counterMaxLength = if (user != null){
|
||||
binding.postTextInputLayout.counterMaxLength = if (user != null){
|
||||
val thisInstances =
|
||||
instances.filter { instanceDatabaseEntity ->
|
||||
instanceDatabaseEntity.uri.contains(user!!.instance_uri)
|
||||
|
@ -92,64 +101,155 @@ class PostCreationActivity : BaseActivity() {
|
|||
accessToken = user?.accessToken.orEmpty()
|
||||
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
|
||||
// check if the pictures are alright
|
||||
// TODO
|
||||
|
||||
//upload the picture and display progress while doing so
|
||||
muListOfIds = posts.map { "" }.toMutableList()
|
||||
progressList = posts.map { 0 } as ArrayList<Int>
|
||||
upload()
|
||||
|
||||
adapter = PostCreationAdapter(posts)
|
||||
recycler = findViewById(R.id.image_grid)
|
||||
recycler.layoutManager = GridLayoutManager(this, 3)
|
||||
recycler.adapter = adapter
|
||||
val carousel: ImageCarousel = binding.carousel
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
carousel.layoutCarouselCallback = {
|
||||
//TODO transition instead of at once
|
||||
if(it){
|
||||
// Became a carousel
|
||||
binding.toolbar3.visibility = VISIBLE
|
||||
} else {
|
||||
// Became a grid
|
||||
binding.toolbar3.visibility = INVISIBLE
|
||||
}
|
||||
}
|
||||
carousel.addPhotoButtonCallback = {
|
||||
addPhoto(applicationContext)
|
||||
}
|
||||
|
||||
// get the description and send the post
|
||||
findViewById<Button>(R.id.post_creation_send_button).setOnClickListener {
|
||||
if (validateDescription() && muListOfIds.isNotEmpty()) post()
|
||||
binding.postCreationSendButton.setOnClickListener {
|
||||
if (validateDescription() && photoData.isNotEmpty()) upload()
|
||||
}
|
||||
|
||||
// Button to retry image upload when it fails
|
||||
findViewById<Button>(R.id.retry_upload_button).setOnClickListener {
|
||||
upload_error.visibility = View.GONE
|
||||
muListOfIds = posts.map { "" }.toMutableList()
|
||||
progressList = posts.map { 0 } as ArrayList<Int>
|
||||
binding.retryUploadButton.setOnClickListener {
|
||||
binding.uploadError.visibility = View.GONE
|
||||
photoData.forEach {
|
||||
it.uploadId = null
|
||||
it.progress = null
|
||||
}
|
||||
upload()
|
||||
}
|
||||
|
||||
binding.editPhotoButton.setOnClickListener {
|
||||
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
||||
edit(currentPosition)
|
||||
}
|
||||
}
|
||||
|
||||
binding.addPhotoButton.setOnClickListener {
|
||||
addPhoto(it.context)
|
||||
}
|
||||
|
||||
binding.savePhotoButton.setOnClickListener {
|
||||
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
||||
savePicture(it, currentPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
binding.removePhotoButton.setOnClickListener {
|
||||
carousel.currentPosition.takeIf { it != -1 }?.let { currentPosition ->
|
||||
photoData.removeAt(currentPosition)
|
||||
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
val pair = getOutputFile(name)
|
||||
val outputStream: OutputStream = pair.first
|
||||
val path: String = pair.second
|
||||
|
||||
contentResolver.openInputStream(photoData[currentPosition].imageUri)!!.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
if(path.startsWith("file")) {
|
||||
MediaScannerConnection.scanFile(
|
||||
this,
|
||||
arrayOf(path.toUri().toFile().absolutePath),
|
||||
null
|
||||
) { path, uri ->
|
||||
if (uri == null) {
|
||||
Log.e(
|
||||
"NEW IMAGE SCAN FAILED",
|
||||
"Tried to scan $path, but it failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Snackbar.make(
|
||||
button, getString(R.string.save_image_success),
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun getOutputFile(name: String): Pair<OutputStream, String> {
|
||||
val outputStream: OutputStream
|
||||
val path: String
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val resolver: ContentResolver = contentResolver
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
||||
contentValues.put(
|
||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
Environment.DIRECTORY_PICTURES
|
||||
)
|
||||
val imageUri: Uri =
|
||||
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
|
||||
path = imageUri.toString()
|
||||
outputStream = resolver.openOutputStream(Objects.requireNonNull(imageUri))!!
|
||||
} else {
|
||||
@Suppress("DEPRECATION") val imagesDir =
|
||||
Environment.getExternalStoragePublicDirectory(getString(R.string.app_name))
|
||||
imagesDir.mkdir()
|
||||
val file = File(imagesDir, name)
|
||||
path = Uri.fromFile(file).toString()
|
||||
outputStream = file.outputStream()
|
||||
}
|
||||
return Pair(outputStream, path)
|
||||
}
|
||||
|
||||
|
||||
private fun validateDescription(): Boolean {
|
||||
val textField = findViewById<TextInputLayout>(R.id.postTextInputLayout)
|
||||
val content = textField.editText?.length() ?: 0
|
||||
if (content > textField.counterMaxLength) {
|
||||
// error, too many characters
|
||||
textField.error = getString(R.string.description_max_characters).format(textField.counterMaxLength)
|
||||
return false
|
||||
binding.postTextInputLayout.run {
|
||||
val content = editText?.length() ?: 0
|
||||
if (content > counterMaxLength) {
|
||||
// error, too many characters
|
||||
error = getString(R.string.description_max_characters).format(counterMaxLength)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the images that are in the [posts] array.
|
||||
* Keeps track of them in the [progressList] (for the upload progress), and the [muListOfIds]
|
||||
* (for the list of ids of the uploads).
|
||||
* @param newImagesStartingIndex is the index in the [posts] array we want to start uploading at.
|
||||
* Indices before this are already uploading, or done uploading, from before.
|
||||
* @param editedImage contains the index of the image that was edited. If set, other images are
|
||||
* not uploaded again: they should already be uploading, or be done uploading, from before.
|
||||
* Uploads the images that are in the [photoData] array.
|
||||
* Keeps track of them in the [PhotoData.progress] (for the upload progress), and the
|
||||
* [PhotoData.uploadId] (for the list of ids of the uploads).
|
||||
*/
|
||||
private fun upload(newImagesStartingIndex: Int = 0, editedImage: Int? = null) {
|
||||
private fun upload() {
|
||||
enableButton(false)
|
||||
uploadProgressBar.visibility = View.VISIBLE
|
||||
upload_completed_textview.visibility = View.INVISIBLE
|
||||
binding.uploadProgressBar.visibility = View.VISIBLE
|
||||
binding.uploadCompletedTextview.visibility = View.INVISIBLE
|
||||
binding.removePhotoButton.isEnabled = false
|
||||
binding.editPhotoButton.isEnabled = false
|
||||
binding.addPhotoButton.isEnabled = false
|
||||
|
||||
val range: IntRange = if(editedImage == null){
|
||||
newImagesStartingIndex until posts.size
|
||||
} else IntRange(editedImage, editedImage)
|
||||
|
||||
for (index in range) {
|
||||
val imageUri = Uri.parse(posts[index])
|
||||
for (data: PhotoData in photoData) {
|
||||
val imageUri = data.imageUri
|
||||
val imageInputStream = contentResolver.openInputStream(imageUri)!!
|
||||
|
||||
val size =
|
||||
|
@ -177,9 +277,9 @@ class PostCreationActivity : BaseActivity() {
|
|||
val sub = imagePart.progressSubject
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { percentage ->
|
||||
progressList[index] = percentage.toInt()
|
||||
uploadProgressBar.progress =
|
||||
progressList.sum() / progressList.size
|
||||
data.progress = percentage.toInt()
|
||||
binding.uploadProgressBar.progress =
|
||||
photoData.sumBy { it.progress ?: 0 } / photoData.size
|
||||
}
|
||||
|
||||
var postSub: Disposable? = null
|
||||
|
@ -190,21 +290,21 @@ class PostCreationActivity : BaseActivity() {
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ attachment: Attachment ->
|
||||
progressList[index] = 0
|
||||
muListOfIds[index] = attachment.id!!
|
||||
data.progress = 0
|
||||
data.uploadId = attachment.id!!
|
||||
},
|
||||
{ e ->
|
||||
upload_error.visibility = View.VISIBLE
|
||||
binding.uploadError.visibility = View.VISIBLE
|
||||
e.printStackTrace()
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
},
|
||||
{
|
||||
progressList[index] = 100
|
||||
if(progressList.all{it == 100}){
|
||||
enableButton(true)
|
||||
uploadProgressBar.visibility = View.GONE
|
||||
upload_completed_textview.visibility = View.VISIBLE
|
||||
data.progress = 100
|
||||
if(photoData.all{it.progress == 100}){
|
||||
binding.uploadProgressBar.visibility = View.GONE
|
||||
binding.uploadCompletedTextview.visibility = View.VISIBLE
|
||||
post()
|
||||
}
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
|
@ -214,14 +314,14 @@ class PostCreationActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
private fun post() {
|
||||
val description = new_post_description_input_field.text.toString()
|
||||
val description = binding.newPostDescriptionInputField.text.toString()
|
||||
enableButton(false)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
pixelfedAPI.postStatus(
|
||||
authorization = "Bearer $accessToken",
|
||||
statusText = description,
|
||||
media_ids = muListOfIds.toList()
|
||||
media_ids = photoData.mapNotNull { it.uploadId }.toList()
|
||||
)
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
|
@ -243,22 +343,22 @@ class PostCreationActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
private fun enableButton(enable: Boolean = true){
|
||||
post_creation_send_button.isEnabled = enable
|
||||
binding.postCreationSendButton.isEnabled = enable
|
||||
if(enable){
|
||||
posting_progress_bar.visibility = View.GONE
|
||||
post_creation_send_button.visibility = View.VISIBLE
|
||||
binding.postingProgressBar.visibility = View.GONE
|
||||
binding.postCreationSendButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
posting_progress_bar.visibility = View.VISIBLE
|
||||
post_creation_send_button.visibility = View.GONE
|
||||
binding.postingProgressBar.visibility = View.VISIBLE
|
||||
binding.postCreationSendButton.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun onClick(position: Int) {
|
||||
private fun edit(position: Int) {
|
||||
positionResult = position
|
||||
|
||||
val intent = Intent(this, PhotoEditActivity::class.java)
|
||||
.putExtra("picture_uri", Uri.parse(posts[position]))
|
||||
.putExtra("picture_uri", photoData[position].imageUri)
|
||||
.putExtra("no upload", false)
|
||||
startActivityForResult(intent, positionResult)
|
||||
}
|
||||
|
@ -267,14 +367,13 @@ class PostCreationActivity : BaseActivity() {
|
|||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == positionResult) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
posts[positionResult] = data.getStringExtra("result")!!
|
||||
adapter.notifyItemChanged(positionResult)
|
||||
muListOfIds[positionResult] = ""
|
||||
progressList[positionResult] = 0
|
||||
upload(editedImage = positionResult)
|
||||
} else if(resultCode == Activity.RESULT_CANCELED){
|
||||
Toast.makeText(applicationContext, "Editing canceled", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
photoData[positionResult].imageUri = data.getStringExtra("result")!!.toUri()
|
||||
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
|
||||
photoData[positionResult].progress = null
|
||||
photoData[positionResult].uploadId = null
|
||||
} else if(resultCode != Activity.RESULT_CANCELED){
|
||||
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else if (requestCode == MORE_PICTURES_REQUEST_CODE) {
|
||||
|
@ -282,71 +381,14 @@ class PostCreationActivity : BaseActivity() {
|
|||
|
||||
val count = data.clipData!!.itemCount
|
||||
for (i in 0 until count) {
|
||||
val imageUri: String = data.clipData!!.getItemAt(i).uri.toString()
|
||||
posts.add(imageUri)
|
||||
progressList.add(0)
|
||||
muListOfIds.add("")
|
||||
val imageUri: Uri = data.clipData!!.getItemAt(i).uri
|
||||
photoData.add(PhotoData(imageUri))
|
||||
}
|
||||
adapter.notifyDataSetChanged()
|
||||
upload(newImagesStartingIndex = posts.size - count)
|
||||
} else if(resultCode == Activity.RESULT_CANCELED){
|
||||
Toast.makeText(applicationContext, "Adding images canceled", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
|
||||
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
|
||||
} else if(resultCode != Activity.RESULT_CANCELED){
|
||||
Toast.makeText(applicationContext, "Error while adding images", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -43,46 +43,50 @@ class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
seekbarSaturation.max = SATURATION_MAX
|
||||
seekbarSaturation.progress = SATURATION_START
|
||||
|
||||
seekbarBrightness.setOnSeekBarChangeListener(this)
|
||||
seekbarContrast.setOnSeekBarChangeListener(this)
|
||||
seekbarSaturation.setOnSeekBarChangeListener(this)
|
||||
setOnSeekBarChangeListeners(this)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun setOnSeekBarChangeListeners(listener: EditImageFragment?){
|
||||
seekbarBrightness.setOnSeekBarChangeListener(listener)
|
||||
seekbarContrast.setOnSeekBarChangeListener(listener)
|
||||
seekbarSaturation.setOnSeekBarChangeListener(listener)
|
||||
}
|
||||
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
var prog = progress
|
||||
|
||||
if(listener != null) {
|
||||
listener?.let {
|
||||
when(seekBar!!.id) {
|
||||
R.id.seekbar_brightness -> listener!!.onBrightnessChange(progress - 100)
|
||||
R.id.seekbar_brightness -> it.onBrightnessChange(progress - 100)
|
||||
R.id.seekbar_saturation -> {
|
||||
prog += 10
|
||||
val tempProgress = .10f * prog
|
||||
listener!!.onSaturationChange(tempProgress)
|
||||
it.onSaturationChange(.10f * prog)
|
||||
}
|
||||
R.id.seekbar_contrast -> {
|
||||
val tempProgress = .10f * prog
|
||||
listener!!.onContrastChange(tempProgress)
|
||||
it.onContrastChange(.10f * prog)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetControl() {
|
||||
// Make sure to ignore seekbar change events, since we don't want to have the reset cause
|
||||
// filter applications due to the onProgressChanged calls
|
||||
setOnSeekBarChangeListeners(null)
|
||||
seekbarBrightness.progress = BRIGHTNESS_START
|
||||
seekbarContrast.progress = CONTRAST_START
|
||||
seekbarSaturation.progress = SATURATION_START
|
||||
setOnSeekBarChangeListeners(this)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {
|
||||
if(listener != null)
|
||||
listener!!.onEditStarted()
|
||||
listener?.onEditStarted()
|
||||
}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {
|
||||
if(listener != null)
|
||||
listener!!.onEditCompleted()
|
||||
listener?.onEditCompleted()
|
||||
}
|
||||
|
||||
fun setListener(listener: PhotoEditActivity) {
|
||||
|
|
|
@ -32,7 +32,6 @@ class FilterListFragment : Fragment() {
|
|||
val view = inflater.inflate(R.layout.fragment_filter_list, container, false)
|
||||
|
||||
tbItemList = ArrayList()
|
||||
adapter = ThumbnailAdapter(requireActivity(), tbItemList, this)
|
||||
|
||||
recyclerView = view.findViewById(R.id.recycler_view)
|
||||
recyclerView.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
|
||||
|
@ -40,6 +39,8 @@ class FilterListFragment : Fragment() {
|
|||
|
||||
val space = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt()
|
||||
recyclerView.addItemDecoration(SpaceItemDecoration(space))
|
||||
|
||||
adapter = ThumbnailAdapter(requireActivity(), tbItemList, this)
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
return view
|
||||
|
@ -99,10 +100,13 @@ class FilterListFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
fun resetSelectedFilter(){
|
||||
adapter.resetSelected()
|
||||
displayImage(null)
|
||||
}
|
||||
|
||||
fun onFilterSelected(filter: Filter) {
|
||||
if(listener != null ){
|
||||
listener!!.onFilterSelected(filter)
|
||||
}
|
||||
listener?.onFilterSelected(filter)
|
||||
}
|
||||
|
||||
fun setListener(listFragmentListener: PhotoEditActivity) {
|
||||
|
|
|
@ -2,20 +2,14 @@ package com.h.pixeldroid.postCreation.photoEdit
|
|||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View.GONE
|
||||
|
@ -23,26 +17,23 @@ import android.view.View.VISIBLE
|
|||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityPhotoEditBinding
|
||||
import com.h.pixeldroid.databinding.ActivityPostCreationBinding
|
||||
import com.h.pixeldroid.postCreation.PostCreationActivity
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.yalantis.ucrop.UCrop
|
||||
import com.zomato.photofilters.imageprocessors.Filter
|
||||
import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter
|
||||
import com.zomato.photofilters.imageprocessors.subfilters.ContrastSubFilter
|
||||
import com.zomato.photofilters.imageprocessors.subfilters.SaturationSubfilter
|
||||
import kotlinx.android.synthetic.main.activity_photo_edit.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors.newSingleThreadExecutor
|
||||
import java.util.concurrent.Future
|
||||
|
@ -50,10 +41,11 @@ import java.util.concurrent.Future
|
|||
// This is an arbitrary number we are using to keep track of the permission
|
||||
// request. Where an app has multiple context for requesting permission,
|
||||
// this can help differentiate the different contexts.
|
||||
private const val REQUEST_CODE_PERMISSIONS_SAVE_PHOTO = 8
|
||||
private const val REQUEST_CODE_PERMISSIONS_SEND_PHOTO = 7
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(
|
||||
android.Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
class PhotoEditActivity : BaseActivity() {
|
||||
|
||||
|
@ -73,9 +65,6 @@ class PhotoEditActivity : BaseActivity() {
|
|||
private lateinit var filterListFragment: FilterListFragment
|
||||
private lateinit var editImageFragment: EditImageFragment
|
||||
|
||||
private lateinit var viewPager: NonSwipeableViewPager
|
||||
private lateinit var tabLayout: TabLayout
|
||||
|
||||
private var brightnessFinal = BRIGHTNESS_START
|
||||
private var saturationFinal = SATURATION_START
|
||||
private var contrastFinal = CONTRAST_START
|
||||
|
@ -95,40 +84,40 @@ class PhotoEditActivity : BaseActivity() {
|
|||
internal var imageUri: Uri? = null
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityPhotoEditBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_photo_edit)
|
||||
binding = ActivityPhotoEditBinding.inflate(layoutInflater)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
|
||||
supportActionBar?.setTitle(R.string.toolbar_title_edit)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setHomeButtonEnabled(true)
|
||||
|
||||
val cropButton: FloatingActionButton = findViewById(R.id.cropImageButton)
|
||||
|
||||
initialUri = intent.getParcelableExtra("picture_uri")
|
||||
imageUri = initialUri
|
||||
|
||||
// set on-click listener
|
||||
cropButton.setOnClickListener {
|
||||
// Crop button on-click listener
|
||||
binding.cropImageButton.setOnClickListener {
|
||||
startCrop()
|
||||
}
|
||||
|
||||
loadImage()
|
||||
|
||||
viewPager = findViewById(R.id.viewPager)
|
||||
tabLayout = findViewById(R.id.tabs)
|
||||
setupViewPager(viewPager)
|
||||
tabLayout.setupWithViewPager(viewPager)
|
||||
setupViewPager(binding.viewPager)
|
||||
binding.tabs.setupWithViewPager(binding.viewPager)
|
||||
}
|
||||
|
||||
|
||||
//<editor-fold desc="ON LAUNCH">
|
||||
private fun loadImage() {
|
||||
originalImage = MediaStore.Images.Media.getBitmap(contentResolver, imageUri)
|
||||
compressedImage = resizeImage(originalImage!!)
|
||||
compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
filteredImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
Glide.with(this).load(compressedImage).into(image_preview)
|
||||
Glide.with(this).load(compressedImage).into(binding.imagePreview)
|
||||
}
|
||||
|
||||
private fun resizeImage(image: Bitmap): Bitmap {
|
||||
|
@ -169,49 +158,60 @@ class PhotoEditActivity : BaseActivity() {
|
|||
|
||||
when(item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
}
|
||||
R.id.action_upload -> {
|
||||
saveImageToGallery(false)
|
||||
if (noEdits()) onBackPressed()
|
||||
else {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.apply {
|
||||
setMessage(R.string.save_before_returning)
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
saveImageToGallery()
|
||||
}
|
||||
setNegativeButton(R.string.no_cancel_edit) { _, _ ->
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
// Create the AlertDialog
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
R.id.action_save -> {
|
||||
saveImageToGallery(true)
|
||||
return true
|
||||
saveImageToGallery()
|
||||
}
|
||||
R.id.action_reset -> {
|
||||
resetControls()
|
||||
actualFilter = null
|
||||
imageUri = initialUri
|
||||
loadImage()
|
||||
filterListFragment.resetSelectedFilter()
|
||||
}
|
||||
}
|
||||
|
||||
//<editor-fold desc="FILTERS">
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
fun onFilterSelected(filter: Filter) {
|
||||
resetControls()
|
||||
filteredImage = compressedOriginalImage!!.copy(BITMAP_CONFIG, true)
|
||||
image_preview.setImageBitmap(filter.processFilter(filteredImage))
|
||||
binding.imagePreview.setImageBitmap(filter.processFilter(filteredImage))
|
||||
compressedImage = filteredImage.copy(BITMAP_CONFIG, true)
|
||||
actualFilter = filter
|
||||
resetControls()
|
||||
}
|
||||
|
||||
private fun resetControls() {
|
||||
editImageFragment.resetControl()
|
||||
|
||||
brightnessFinal = BRIGHTNESS_START
|
||||
saturationFinal = SATURATION_START
|
||||
contrastFinal = CONTRAST_START
|
||||
|
||||
editImageFragment.resetControl()
|
||||
}
|
||||
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="EDITS">
|
||||
|
||||
private fun applyFilterAndShowImage(filter: Filter, image: Bitmap?) {
|
||||
future?.cancel(true)
|
||||
future = executor.submit {
|
||||
val bitmap = filter.processFilter(image!!.copy(BITMAP_CONFIG, true))
|
||||
image_preview.post {
|
||||
image_preview.setImageBitmap(bitmap)
|
||||
binding.imagePreview.post {
|
||||
binding.imagePreview.setImageBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -255,8 +255,6 @@ class PhotoEditActivity : BaseActivity() {
|
|||
compressedImage = myFilter.processFilter(bitmap)
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="CROPPING">
|
||||
|
||||
private fun startCrop() {
|
||||
val file = File.createTempFile("temp_crop_img", ".png", cacheDir)
|
||||
|
@ -293,8 +291,8 @@ class PhotoEditActivity : BaseActivity() {
|
|||
val resultCrop: Uri? = UCrop.getOutput(data!!)
|
||||
if(resultCrop != null) {
|
||||
imageUri = resultCrop
|
||||
image_preview.setImageURI(resultCrop)
|
||||
val bitmap = (image_preview.drawable as BitmapDrawable).bitmap
|
||||
binding.imagePreview.setImageURI(resultCrop)
|
||||
val bitmap = (binding.imagePreview.drawable as BitmapDrawable).bitmap
|
||||
originalImage = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
compressedImage = resizeImage(originalImage!!.copy(BITMAP_CONFIG, true))
|
||||
compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
|
@ -314,8 +312,6 @@ class PhotoEditActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="FLOW">
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
|
@ -325,12 +321,9 @@ class PhotoEditActivity : BaseActivity() {
|
|||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
// permission was granted
|
||||
when (requestCode) {
|
||||
REQUEST_CODE_PERMISSIONS_SAVE_PHOTO -> permissionsGrantedToSave(true)
|
||||
REQUEST_CODE_PERMISSIONS_SEND_PHOTO -> permissionsGrantedToSave(false)
|
||||
}
|
||||
permissionsGrantedToSave()
|
||||
} else {
|
||||
Snackbar.make(coordinator_edit, getString(R.string.permission_denied),
|
||||
Snackbar.make(binding.root, getString(R.string.permission_denied),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
@ -354,16 +347,16 @@ class PhotoEditActivity : BaseActivity() {
|
|||
finish()
|
||||
}
|
||||
|
||||
private fun saveImageToGallery(save: Boolean) {
|
||||
private fun saveImageToGallery() {
|
||||
// runtime permission and process
|
||||
if (!allPermissionsGranted()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
REQUIRED_PERMISSIONS,
|
||||
if(save) REQUEST_CODE_PERMISSIONS_SAVE_PHOTO else REQUEST_CODE_PERMISSIONS_SEND_PHOTO
|
||||
REQUEST_CODE_PERMISSIONS_SEND_PHOTO
|
||||
)
|
||||
} else {
|
||||
permissionsGrantedToSave(save)
|
||||
permissionsGrantedToSave()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,32 +368,6 @@ class PhotoEditActivity : BaseActivity() {
|
|||
applicationContext, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
private fun getOutputFile(name: String): Pair<OutputStream, String> {
|
||||
val outputStream: OutputStream
|
||||
val path: String
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val resolver: ContentResolver = contentResolver
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
|
||||
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
|
||||
contentValues.put(
|
||||
MediaStore.MediaColumns.RELATIVE_PATH,
|
||||
Environment.DIRECTORY_PICTURES
|
||||
)
|
||||
val imageUri: Uri =
|
||||
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)!!
|
||||
path = imageUri.toString()
|
||||
outputStream = resolver.openOutputStream(Objects.requireNonNull(imageUri))!!
|
||||
} else {
|
||||
val imagesDir =
|
||||
Environment.getExternalStoragePublicDirectory(getString(R.string.app_name))
|
||||
imagesDir.mkdir()
|
||||
val file = File(imagesDir, name)
|
||||
path = Uri.fromFile(file).toString()
|
||||
outputStream = file.outputStream()
|
||||
}
|
||||
return Pair(outputStream, path)
|
||||
}
|
||||
|
||||
private fun OutputStream.writeBitmap(bitmap: Bitmap) {
|
||||
use { out ->
|
||||
|
@ -410,7 +377,13 @@ class PhotoEditActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun permissionsGrantedToSave(save: Boolean) {
|
||||
private fun noEdits(): Boolean =
|
||||
brightnessFinal == BRIGHTNESS_START
|
||||
&& contrastFinal == CONTRAST_START
|
||||
&& saturationFinal == SATURATION_START
|
||||
&& actualFilter?.let { it.name == getString(R.string.normal_filter)} ?: true
|
||||
|
||||
private fun permissionsGrantedToSave() {
|
||||
if (saving) {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.apply {
|
||||
|
@ -422,78 +395,37 @@ class PhotoEditActivity : BaseActivity() {
|
|||
return
|
||||
}
|
||||
saving = true
|
||||
progressBarSaveFile.visibility = VISIBLE
|
||||
binding.progressBarSaveFile.visibility = VISIBLE
|
||||
saveFuture = saveExecutor.submit {
|
||||
val outputStream: OutputStream
|
||||
var path: String
|
||||
if (!save) {
|
||||
//put picture in cache
|
||||
try {
|
||||
val path: String
|
||||
if(!noEdits()) {
|
||||
// Save modified image in cache
|
||||
val tempFile = File.createTempFile("temp_edit_img", ".png", cacheDir)
|
||||
path = Uri.fromFile(tempFile).toString()
|
||||
outputStream = tempFile.outputStream()
|
||||
} else {
|
||||
// Save the picture to gallery
|
||||
val name = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
|
||||
.format(System.currentTimeMillis()) + ".png"
|
||||
val pair = getOutputFile(name)
|
||||
outputStream = pair.first
|
||||
path = pair.second
|
||||
}
|
||||
try {
|
||||
if(brightnessFinal != BRIGHTNESS_START || contrastFinal != CONTRAST_START
|
||||
|| saturationFinal != SATURATION_START
|
||||
|| (actualFilter != null && actualFilter!!.name != getString(R.string.normal_filter))) {
|
||||
outputStream.writeBitmap(applyFinalFilters(originalImage))
|
||||
tempFile.outputStream().writeBitmap(applyFinalFilters(originalImage))
|
||||
}
|
||||
else {
|
||||
if(save) {
|
||||
contentResolver.openInputStream(imageUri!!)!!.use { input ->
|
||||
outputStream.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
path = imageUri.toString()
|
||||
}
|
||||
|
||||
if(saving) {
|
||||
this.runOnUiThread {
|
||||
sendBackImage(path)
|
||||
binding.progressBarSaveFile.visibility = GONE
|
||||
saving = false
|
||||
}
|
||||
else path = imageUri.toString()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
this.runOnUiThread {
|
||||
Snackbar.make(
|
||||
coordinator_edit, getString(R.string.save_image_failed),
|
||||
binding.root, getString(R.string.save_image_failed),
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
if(saving) {
|
||||
this.runOnUiThread {
|
||||
if(!save) {
|
||||
sendBackImage(path)
|
||||
} else {
|
||||
if(path.startsWith("file")) {
|
||||
MediaScannerConnection.scanFile(
|
||||
this,
|
||||
arrayOf(path.toUri().toFile().absolutePath),
|
||||
null
|
||||
|
||||
) { path, uri ->
|
||||
if (uri == null) {
|
||||
Log.e(
|
||||
"NEW IMAGE SCAN FAILED",
|
||||
"Tried to scan $path, but it failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Snackbar.make(
|
||||
coordinator_edit, getString(R.string.save_image_success),
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
progressBarSaveFile.visibility = GONE
|
||||
binding.progressBarSaveFile.visibility = GONE
|
||||
saving = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@ package com.h.pixeldroid.postCreation.photoEdit
|
|||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ThumbnailListItemBinding
|
||||
import com.zomato.photofilters.utils.ThumbnailItem
|
||||
import kotlinx.android.synthetic.main.thumbnail_list_item.view.*
|
||||
|
||||
class ThumbnailAdapter (private val context: Context,
|
||||
private val tbItemList: List<ThumbnailItem>,
|
||||
|
@ -18,9 +17,14 @@ class ThumbnailAdapter (private val context: Context,
|
|||
|
||||
private var selectedIndex = 0
|
||||
|
||||
fun resetSelected(){
|
||||
selectedIndex = 0
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
|
||||
val itemView = LayoutInflater.from(context).inflate(R.layout.thumbnail_list_item, parent, false)
|
||||
return MyViewHolder(itemView)
|
||||
val itemBinding = ThumbnailListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return MyViewHolder(itemBinding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
|
@ -44,8 +48,8 @@ class ThumbnailAdapter (private val context: Context,
|
|||
holder.filterName.setTextColor(ContextCompat.getColor(context, R.color.filterLabelNormal))
|
||||
}
|
||||
|
||||
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
var thumbnail: ImageView = itemView.thumbnail
|
||||
var filterName: TextView = itemView.filter_name
|
||||
class MyViewHolder(itemBinding: ThumbnailListItemBinding): RecyclerView.ViewHolder(itemBinding.root) {
|
||||
var thumbnail: ImageView = itemBinding.thumbnail
|
||||
var filterName: TextView = itemBinding.filterName
|
||||
}
|
||||
}
|
|
@ -5,13 +5,13 @@ import android.util.Log
|
|||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityPostBinding
|
||||
import com.h.pixeldroid.utils.api.objects.DiscoverPost
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.DISCOVER_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.DOMAIN_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import kotlinx.android.synthetic.main.activity_post.*
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -20,9 +20,13 @@ class PostActivity : BaseActivity() {
|
|||
lateinit var domain : String
|
||||
private lateinit var accessToken : String
|
||||
|
||||
private lateinit var binding: ActivityPostBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_post)
|
||||
binding = ActivityPostBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val status = intent.getSerializableExtra(POST_TAG) as Status?
|
||||
|
@ -38,7 +42,7 @@ class PostActivity : BaseActivity() {
|
|||
arguments.putString(DOMAIN_TAG, domain)
|
||||
|
||||
if (discoverPost != null) {
|
||||
postProgressBar.visibility = View.VISIBLE
|
||||
binding.postProgressBar.visibility = View.VISIBLE
|
||||
getDiscoverPost(arguments, discoverPost)
|
||||
} else {
|
||||
initializeFragment(arguments, status)
|
||||
|
@ -59,7 +63,7 @@ class PostActivity : BaseActivity() {
|
|||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
val status = api.getStatus("Bearer $accessToken", id)
|
||||
postProgressBar.visibility = View.GONE
|
||||
binding.postProgressBar.visibility = View.GONE
|
||||
initializeFragment(arguments, status)
|
||||
} catch (exception: IOException) {
|
||||
//TODO show error message
|
||||
|
@ -76,6 +80,6 @@ class PostActivity : BaseActivity() {
|
|||
supportFragmentManager.isStateSaved
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.postFragmentSingle, postFragment).commit()
|
||||
postFragmentSingle.visibility = View.VISIBLE
|
||||
binding.postFragmentSingle.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.DOMAIN_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.BaseFragment
|
||||
import com.h.pixeldroid.utils.bindingLifecycleAware
|
||||
|
||||
|
||||
class PostFragment : BaseFragment() {
|
||||
|
@ -17,30 +18,32 @@ class PostFragment : BaseFragment() {
|
|||
private lateinit var statusDomain: String
|
||||
private var currentStatus: Status? = null
|
||||
|
||||
var binding: PostFragmentBinding by bindingLifecycleAware()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
currentStatus = arguments?.getSerializable(POST_TAG) as Status?
|
||||
statusDomain = arguments?.getString(DOMAIN_TAG)!!
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val root: View = inflater.inflate(R.layout.post_fragment, container, false)
|
||||
binding = PostFragmentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
|
||||
val holder = StatusViewHolder(root)
|
||||
val holder = StatusViewHolder(binding)
|
||||
|
||||
holder.bind(currentStatus, api, db, lifecycleScope)
|
||||
|
||||
return root
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,25 +1,24 @@
|
|||
package com.h.pixeldroid.posts
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.api.objects.Report
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.databinding.ActivityReportBinding
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import kotlinx.android.synthetic.main.activity_report.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class ReportActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityReportBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_report)
|
||||
binding = ActivityReportBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setTitle(R.string.report)
|
||||
|
||||
|
@ -29,21 +28,21 @@ class ReportActivity : BaseActivity() {
|
|||
val user = db.userDao().getActiveUser()
|
||||
|
||||
|
||||
report_target_textview.text = getString(R.string.report_target).format(status?.account?.acct)
|
||||
binding.reportTargetTextview.text = getString(R.string.report_target).format(status?.account?.acct)
|
||||
|
||||
|
||||
reportButton.setOnClickListener{
|
||||
reportButton.visibility = View.INVISIBLE
|
||||
reportProgressBar.visibility = View.VISIBLE
|
||||
binding.reportButton.setOnClickListener{
|
||||
binding.reportButton.visibility = View.INVISIBLE
|
||||
binding.reportProgressBar.visibility = View.VISIBLE
|
||||
|
||||
textInputLayout.editText?.isEnabled = false
|
||||
binding.textInputLayout.editText?.isEnabled = false
|
||||
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), textInputLayout.editText?.text.toString())
|
||||
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), binding.textInputLayout.editText?.text.toString())
|
||||
|
||||
reportStatus(true)
|
||||
} catch (exception: IOException) {
|
||||
|
@ -57,15 +56,15 @@ class ReportActivity : BaseActivity() {
|
|||
|
||||
private fun reportStatus(success: Boolean){
|
||||
if(success){
|
||||
reportProgressBar.visibility = View.GONE
|
||||
reportButton.isEnabled = false
|
||||
reportButton.text = getString(R.string.reported)
|
||||
reportButton.visibility = View.VISIBLE
|
||||
binding.reportProgressBar.visibility = View.GONE
|
||||
binding.reportButton.isEnabled = false
|
||||
binding.reportButton.text = getString(R.string.reported)
|
||||
binding.reportButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
textInputLayout.error = getString(R.string.report_error)
|
||||
reportButton.visibility = View.VISIBLE
|
||||
textInputLayout.editText?.isEnabled = true
|
||||
reportProgressBar.visibility = View.GONE
|
||||
binding.textInputLayout.error = getString(R.string.report_error)
|
||||
binding.reportButton.visibility = View.VISIBLE
|
||||
binding.textInputLayout.editText?.isEnabled = true
|
||||
binding.reportProgressBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,60 +16,32 @@ import android.widget.*
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import at.connyduck.sparkbutton.SparkButton
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.AlbumImageViewBinding
|
||||
import com.h.pixeldroid.databinding.CommentBinding
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import com.h.pixeldroid.utils.api.objects.Context
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener
|
||||
import kotlinx.android.synthetic.main.comment.view.*
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
/**
|
||||
* View Holder for a [Status] RecyclerView list item.
|
||||
*/
|
||||
class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
val profilePic : ImageView = view.findViewById(R.id.profilePic)
|
||||
val postPic : ImageView = view.findViewById(R.id.postPicture)
|
||||
val username : TextView = view.findViewById(R.id.username)
|
||||
val usernameDesc: TextView = view.findViewById(R.id.usernameDesc)
|
||||
val description : TextView = view.findViewById(R.id.description)
|
||||
val nlikes : TextView = view.findViewById(R.id.nlikes)
|
||||
val nshares : TextView = view.findViewById(R.id.nshares)
|
||||
|
||||
//Spark buttons
|
||||
val liker : SparkButton = view.findViewById(R.id.liker)
|
||||
val reblogger : SparkButton = view.findViewById(R.id.reblogger)
|
||||
|
||||
val submitCmnt : ImageButton = view.findViewById(R.id.submitComment)
|
||||
val commenter : ImageView = view.findViewById(R.id.commenter)
|
||||
val comment : EditText = view.findViewById(R.id.editComment)
|
||||
val commentCont : LinearLayout = view.findViewById(R.id.commentContainer)
|
||||
val commentIn : LinearLayout = view.findViewById(R.id.commentIn)
|
||||
val viewComment : TextView = view.findViewById(R.id.ViewComments)
|
||||
val postDate : TextView = view.findViewById(R.id.postDate)
|
||||
val postDomain : TextView = view.findViewById(R.id.postDomain)
|
||||
val sensitiveW : TextView = view.findViewById(R.id.sensitiveWarning)
|
||||
val postPager : ViewPager2 = view.findViewById(R.id.postPager)
|
||||
val more : ImageButton = view.findViewById(R.id.status_more)
|
||||
class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
private var status: Status? = null
|
||||
|
||||
|
@ -80,7 +52,7 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
val metrics = itemView.context.resources.displayMetrics
|
||||
//Limit the height of the different images
|
||||
postPic.maxHeight = metrics.heightPixels * 3/4
|
||||
binding.postPicture.maxHeight = metrics.heightPixels * 3/4
|
||||
|
||||
//Setup the post layout
|
||||
val picRequest = Glide.with(itemView)
|
||||
|
@ -89,37 +61,35 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
||||
setupPost(itemView, picRequest, user.instance_uri, false)
|
||||
setupPost(picRequest, user.instance_uri, false)
|
||||
|
||||
activateButtons(this, pixelfedAPI, db, lifecycleScope)
|
||||
activateButtons(pixelfedAPI, db, lifecycleScope)
|
||||
|
||||
}
|
||||
|
||||
private fun setupPost(
|
||||
rootView: View,
|
||||
request: RequestBuilder<Drawable>,
|
||||
//homeFragment: Fragment,
|
||||
domain: String,
|
||||
isActivity: Boolean
|
||||
) {
|
||||
//Setup username as a button that opens the profile
|
||||
rootView.findViewById<TextView>(R.id.username).apply {
|
||||
binding.username.apply {
|
||||
text = status?.account?.getDisplayName() ?: ""
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
setOnClickListener { status?.account?.openProfile(rootView.context) }
|
||||
}
|
||||
|
||||
rootView.findViewById<TextView>(R.id.usernameDesc).apply {
|
||||
binding.usernameDesc.apply {
|
||||
text = status?.account?.getDisplayName() ?: ""
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
rootView.findViewById<TextView>(R.id.nlikes).apply {
|
||||
binding.nlikes.apply {
|
||||
text = status?.getNLikes(rootView.context)
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
rootView.findViewById<TextView>(R.id.nshares).apply {
|
||||
binding.nshares.apply {
|
||||
text = status?.getNShares(rootView.context)
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
@ -127,82 +97,81 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
//Convert the date to a readable string
|
||||
setTextViewFromISO8601(
|
||||
status?.created_at!!,
|
||||
rootView.postDate,
|
||||
binding.postDate,
|
||||
isActivity,
|
||||
rootView.context
|
||||
binding.root.context
|
||||
)
|
||||
|
||||
rootView.postDomain.text = status?.getStatusDomain(domain)
|
||||
binding.postDomain.text = status?.getStatusDomain(domain)
|
||||
|
||||
//Setup images
|
||||
ImageConverter.setRoundImageFromURL(
|
||||
rootView,
|
||||
binding.root,
|
||||
status?.getProfilePicUrl(),
|
||||
rootView.profilePic
|
||||
binding.profilePic
|
||||
)
|
||||
rootView.profilePic.setOnClickListener { status?.account?.openProfile(rootView.context) }
|
||||
binding.profilePic.setOnClickListener { status?.account?.openProfile(binding.root.context) }
|
||||
|
||||
//Setup post pic only if there are media attachments
|
||||
if(!status?.media_attachments.isNullOrEmpty()) {
|
||||
setupPostPics(rootView, request)
|
||||
setupPostPics(binding, request)
|
||||
} else {
|
||||
rootView.postPicture.visibility = View.GONE
|
||||
rootView.postPager.visibility = View.GONE
|
||||
rootView.postTabs.visibility = View.GONE
|
||||
binding.postPicture.visibility = View.GONE
|
||||
binding.postPager.visibility = View.GONE
|
||||
binding.postTabs.visibility = View.GONE
|
||||
}
|
||||
|
||||
|
||||
//Set comment initial visibility
|
||||
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
|
||||
rootView.findViewById<LinearLayout>(R.id.commentContainer).visibility = View.GONE
|
||||
binding.commentIn.visibility = View.GONE
|
||||
binding.commentContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun setupPostPics(
|
||||
rootView: View,
|
||||
binding: PostFragmentBinding,
|
||||
request: RequestBuilder<Drawable>,
|
||||
//homeFragment: Fragment
|
||||
) {
|
||||
|
||||
// Standard layout
|
||||
rootView.postPicture.visibility = View.VISIBLE
|
||||
rootView.postPager.visibility = View.GONE
|
||||
rootView.postTabs.visibility = View.GONE
|
||||
binding.postPicture.visibility = View.VISIBLE
|
||||
binding.postPager.visibility = View.GONE
|
||||
binding.postTabs.visibility = View.GONE
|
||||
|
||||
|
||||
if(status?.media_attachments?.size == 1) {
|
||||
request.load(status?.getPostUrl()).into(rootView.postPicture)
|
||||
val imgDescription = status?.media_attachments?.get(0)?.description.orEmpty().ifEmpty { rootView.context.getString(
|
||||
request.load(status?.getPostUrl()).into(binding.postPicture)
|
||||
val imgDescription = status?.media_attachments?.get(0)?.description.orEmpty().ifEmpty { binding.root.context.getString(
|
||||
R.string.no_description) }
|
||||
rootView.postPicture.contentDescription = imgDescription
|
||||
binding.postPicture.contentDescription = imgDescription
|
||||
|
||||
rootView.postPicture.setOnLongClickListener {
|
||||
binding.postPicture.setOnLongClickListener {
|
||||
Snackbar.make(it, imgDescription, Snackbar.LENGTH_SHORT).show()
|
||||
true
|
||||
}
|
||||
|
||||
} else if(status?.media_attachments?.size!! > 1) {
|
||||
setupTabsLayout(rootView, request)
|
||||
setupTabsLayout(binding, request)
|
||||
}
|
||||
|
||||
if (status?.sensitive!!) {
|
||||
status?.setupSensitiveLayout(rootView)
|
||||
status?.setupSensitiveLayout(binding)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabsLayout(
|
||||
rootView: View,
|
||||
binding: PostFragmentBinding,
|
||||
request: RequestBuilder<Drawable>,
|
||||
) {
|
||||
//Only show the viewPager and tabs
|
||||
rootView.postPicture.visibility = View.GONE
|
||||
rootView.postPager.visibility = View.VISIBLE
|
||||
rootView.postTabs.visibility = View.VISIBLE
|
||||
binding.postPicture.visibility = View.GONE
|
||||
binding.postPager.visibility = View.VISIBLE
|
||||
binding.postTabs.visibility = View.VISIBLE
|
||||
|
||||
//Attach the given tabs to the view pager
|
||||
rootView.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList())
|
||||
binding.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList())
|
||||
|
||||
TabLayoutMediator(rootView.postTabs, rootView.postPager) { tab, _ ->
|
||||
tab.icon = ContextCompat.getDrawable(rootView.context, R.drawable.ic_dot_blue_12dp)
|
||||
TabLayoutMediator(binding.postTabs, binding.postPager) { tab, _ ->
|
||||
tab.icon = ContextCompat.getDrawable(binding.root.context, R.drawable.ic_dot_blue_12dp)
|
||||
}.attach()
|
||||
}
|
||||
|
||||
|
@ -212,7 +181,7 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
rootView.findViewById<TextView>(R.id.description).apply {
|
||||
binding.description.apply {
|
||||
if (status?.content.isNullOrBlank()) {
|
||||
visibility = View.GONE
|
||||
} else {
|
||||
|
@ -229,39 +198,36 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun activateButtons(holder: StatusViewHolder, api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
||||
private fun activateButtons(api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
||||
val credential = "Bearer ${user.accessToken}"
|
||||
//Set the special HTML text
|
||||
setDescription(holder.view, api, credential, lifecycleScope)
|
||||
setDescription(binding.root, api, credential, lifecycleScope)
|
||||
|
||||
//Activate onclickListeners
|
||||
activateLiker(
|
||||
holder, api, credential,
|
||||
status?.favourited ?: false,
|
||||
lifecycleScope
|
||||
api, credential, status?.favourited ?: false,
|
||||
lifecycleScope
|
||||
)
|
||||
activateReblogger(
|
||||
holder, api, credential,
|
||||
status?.reblogged ?: false,
|
||||
lifecycleScope
|
||||
api, credential, status?.reblogged ?: false,
|
||||
lifecycleScope
|
||||
)
|
||||
activateCommenter(holder, api, credential, lifecycleScope)
|
||||
activateCommenter(api, credential, lifecycleScope)
|
||||
|
||||
showComments(holder, api, credential, lifecycleScope)
|
||||
showComments(api, credential, lifecycleScope)
|
||||
|
||||
activateMoreButton(holder, api, db, lifecycleScope)
|
||||
activateMoreButton(api, db, lifecycleScope)
|
||||
}
|
||||
|
||||
private fun activateReblogger(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
isReblogged: Boolean,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
holder.reblogger.apply {
|
||||
binding.reblogger.apply {
|
||||
//Set initial button state
|
||||
isChecked = isReblogged
|
||||
|
||||
|
@ -270,10 +236,10 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
lifecycleScope.launchWhenCreated {
|
||||
if (buttonState) {
|
||||
// Button is active
|
||||
undoReblogPost(holder, api, credential)
|
||||
undoReblogPost(api, credential)
|
||||
} else {
|
||||
// Button is inactive
|
||||
reblogPost(holder, api, credential)
|
||||
reblogPost(api, credential)
|
||||
}
|
||||
}
|
||||
//show animation or not?
|
||||
|
@ -283,7 +249,6 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
}
|
||||
|
||||
private suspend fun reblogPost(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String
|
||||
) {
|
||||
|
@ -294,20 +259,19 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
val resp = api.reblogStatus(credential, it)
|
||||
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares(holder.view.context)
|
||||
holder.reblogger.isChecked = resp.reblogged!!
|
||||
binding.nshares.text = resp.getNShares(binding.root.context)
|
||||
binding.reblogger.isChecked = resp.reblogged!!
|
||||
} catch (exception: IOException) {
|
||||
Log.e("REBLOG ERROR", exception.toString())
|
||||
holder.reblogger.isChecked = false
|
||||
binding.reblogger.isChecked = false
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.reblogger.isChecked = false
|
||||
binding.reblogger.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun undoReblogPost(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
|
@ -317,20 +281,20 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
val resp = api.undoReblogStatus(credential, it)
|
||||
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares(holder.view.context)
|
||||
holder.reblogger.isChecked = resp.reblogged!!
|
||||
binding.nshares.text = resp.getNShares(binding.root.context)
|
||||
binding.reblogger.isChecked = resp.reblogged!!
|
||||
} catch (exception: IOException) {
|
||||
Log.e("REBLOG ERROR", exception.toString())
|
||||
holder.reblogger.isChecked = true
|
||||
binding.reblogger.isChecked = true
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.reblogger.isChecked = true
|
||||
binding.reblogger.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun activateMoreButton(holder: StatusViewHolder, api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
||||
holder.more.setOnClickListener {
|
||||
private fun activateMoreButton(api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
||||
binding.statusMore.setOnClickListener {
|
||||
PopupMenu(it.context, it).apply {
|
||||
setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
|
@ -354,46 +318,46 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
true
|
||||
}
|
||||
R.id.post_more_menu_save_to_gallery -> {
|
||||
Dexter.withContext(holder.view.context)
|
||||
Dexter.withContext(binding.root.context)
|
||||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(object : BasePermissionListener() {
|
||||
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
|
||||
Toast.makeText(
|
||||
holder.view.context,
|
||||
holder.view.context.getString(R.string.write_permission_download_pic),
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.write_permission_download_pic),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
|
||||
status?.downloadImage(
|
||||
holder.view.context,
|
||||
status?.media_attachments?.get(holder.postPager.currentItem)?.url
|
||||
binding.root.context,
|
||||
status?.media_attachments?.get(binding.postPager.currentItem)?.url
|
||||
?: "",
|
||||
holder.view
|
||||
binding.root
|
||||
)
|
||||
}
|
||||
}).check()
|
||||
true
|
||||
}
|
||||
R.id.post_more_menu_share_picture -> {
|
||||
Dexter.withContext(holder.view.context)
|
||||
Dexter.withContext(binding.root.context)
|
||||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(object : BasePermissionListener() {
|
||||
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
|
||||
Toast.makeText(
|
||||
holder.view.context,
|
||||
holder.view.context.getString(R.string.write_permission_share_pic),
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.write_permission_share_pic),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
|
||||
status?.downloadImage(
|
||||
holder.view.context,
|
||||
status?.media_attachments?.get(holder.postPager.currentItem)?.url
|
||||
binding.root.context,
|
||||
status?.media_attachments?.get(binding.postPager.currentItem)?.url
|
||||
?: "",
|
||||
holder.view,
|
||||
binding.root,
|
||||
share = true,
|
||||
)
|
||||
}
|
||||
|
@ -401,10 +365,10 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
true
|
||||
}
|
||||
R.id.post_more_menu_delete -> {
|
||||
val builder = AlertDialog.Builder(holder.itemView.context)
|
||||
val builder = AlertDialog.Builder(binding.root.context)
|
||||
builder.apply {
|
||||
setMessage(R.string.delete_dialog)
|
||||
setPositiveButton(R.string.OK) { _, _ ->
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
|
||||
lifecycleScope.launch {
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
@ -413,14 +377,14 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
db.publicPostDao().delete(id, user.user_id, user.instance_uri)
|
||||
try {
|
||||
api.deleteStatus("Bearer ${user.accessToken}", id)
|
||||
holder.itemView.visibility = View.GONE
|
||||
binding.root.visibility = View.GONE
|
||||
} catch (exception: IOException) {
|
||||
} catch (exception: HttpException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
show()
|
||||
}
|
||||
|
||||
|
@ -446,14 +410,13 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
}
|
||||
|
||||
private fun activateLiker(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
isLiked: Boolean,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
|
||||
holder.liker.apply {
|
||||
binding.liker.apply {
|
||||
//Set initial state
|
||||
isChecked = isLiked
|
||||
|
||||
|
@ -462,10 +425,10 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
lifecycleScope.launchWhenCreated {
|
||||
if (buttonState) {
|
||||
// Button is active, unlike
|
||||
unLikePostCall(holder, api, credential)
|
||||
unLikePostCall(api, credential)
|
||||
} else {
|
||||
// Button is inactive, like
|
||||
likePostCall(holder, api, credential)
|
||||
likePostCall(api, credential)
|
||||
}
|
||||
}
|
||||
//show animation or not?
|
||||
|
@ -474,38 +437,36 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
}
|
||||
|
||||
//Activate double tap liking
|
||||
holder.apply {
|
||||
var clicked = false
|
||||
postPic.setOnClickListener {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Check that the post isn't hidden
|
||||
if(sensitiveW.visibility == View.GONE) {
|
||||
//Check for double click
|
||||
if(clicked) {
|
||||
if (holder.liker.isChecked) {
|
||||
// Button is active, unlike
|
||||
holder.liker.isChecked = false
|
||||
unLikePostCall(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive, like
|
||||
holder.liker.playAnimation()
|
||||
holder.liker.isChecked = true
|
||||
likePostCall(holder, api, credential)
|
||||
}
|
||||
var clicked = false
|
||||
binding.postPicture.setOnClickListener {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Check that the post isn't hidden
|
||||
if(binding.sensitiveWarning.visibility == View.GONE) {
|
||||
//Check for double click
|
||||
if(clicked) {
|
||||
if (binding.liker.isChecked) {
|
||||
// Button is active, unlike
|
||||
binding.liker.isChecked = false
|
||||
unLikePostCall(api, credential)
|
||||
} else {
|
||||
clicked = true
|
||||
|
||||
//Reset clicked to false after 500ms
|
||||
postPic.handler.postDelayed(fun() { clicked = false }, 500)
|
||||
// Button is inactive, like
|
||||
binding.liker.playAnimation()
|
||||
binding.liker.isChecked = true
|
||||
likePostCall(api, credential)
|
||||
}
|
||||
} else {
|
||||
clicked = true
|
||||
|
||||
//Reset clicked to false after 500ms
|
||||
binding.postPicture.handler.postDelayed(fun() { clicked = false }, 500)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun likePostCall(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
|
@ -516,20 +477,19 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
val resp = api.likePost(credential, it)
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes(holder.view.context)
|
||||
holder.liker.isChecked = resp.favourited ?: false
|
||||
binding.nlikes.text = resp.getNLikes(binding.root.context)
|
||||
binding.liker.isChecked = resp.favourited ?: false
|
||||
} catch (exception: IOException) {
|
||||
Log.e("LIKE ERROR", exception.toString())
|
||||
holder.liker.isChecked = false
|
||||
binding.liker.isChecked = false
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.liker.isChecked = false
|
||||
binding.liker.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun unLikePostCall(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
|
@ -540,37 +500,36 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
val resp = api.unlikePost(credential, it)
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes(holder.view.context)
|
||||
holder.liker.isChecked = resp.favourited ?: false
|
||||
binding.nlikes.text = resp.getNLikes(binding.root.context)
|
||||
binding.liker.isChecked = resp.favourited ?: false
|
||||
} catch (exception: IOException) {
|
||||
Log.e("UNLIKE ERROR", exception.toString())
|
||||
holder.liker.isChecked = true
|
||||
binding.liker.isChecked = true
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.liker.isChecked = true
|
||||
binding.liker.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showComments(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
//Show all comments of a post
|
||||
if (status?.replies_count == 0) {
|
||||
holder.viewComment.text = holder.view.context.getString(R.string.NoCommentsToShow)
|
||||
binding.viewComments.text = binding.root.context.getString(R.string.NoCommentsToShow)
|
||||
} else {
|
||||
holder.viewComment.apply {
|
||||
text = holder.view.context.getString(R.string.number_comments)
|
||||
binding.viewComments.apply {
|
||||
text = binding.root.context.getString(R.string.number_comments)
|
||||
.format(status?.replies_count)
|
||||
setOnClickListener {
|
||||
visibility = View.GONE
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Retrieve the comments
|
||||
retrieveComments(holder, api, credential)
|
||||
retrieveComments(api, credential)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -578,52 +537,49 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
}
|
||||
|
||||
private fun activateCommenter(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
//Toggle comment button
|
||||
toggleCommentInput(holder)
|
||||
toggleCommentInput()
|
||||
|
||||
//Activate commenterpostPicture
|
||||
holder.submitCmnt.setOnClickListener {
|
||||
val textIn = holder.comment.text
|
||||
binding.submitComment.setOnClickListener {
|
||||
val textIn = binding.editComment.text
|
||||
//Open text input
|
||||
if(textIn.isNullOrEmpty()) {
|
||||
Toast.makeText(
|
||||
holder.view.context,
|
||||
holder.view.context.getString(R.string.empty_comment),
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.empty_comment),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
//Post the comment
|
||||
lifecycleScope.launchWhenCreated {
|
||||
postComment(holder, api, credential)
|
||||
postComment(api, credential)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleCommentInput(
|
||||
holder : StatusViewHolder
|
||||
) {
|
||||
private fun toggleCommentInput() {
|
||||
//Toggle comment button
|
||||
holder.commenter.setOnClickListener {
|
||||
when(holder.commentIn.visibility) {
|
||||
binding.commenter.setOnClickListener {
|
||||
when(binding.commentIn.visibility) {
|
||||
View.VISIBLE -> {
|
||||
holder.commentIn.visibility = View.GONE
|
||||
binding.commentIn.visibility = View.GONE
|
||||
ImageConverter.setImageFromDrawable(
|
||||
holder.view,
|
||||
holder.commenter,
|
||||
binding.root,
|
||||
binding.commenter,
|
||||
R.drawable.ic_comment_empty
|
||||
)
|
||||
}
|
||||
View.GONE -> {
|
||||
holder.commentIn.visibility = View.VISIBLE
|
||||
binding.commentIn.visibility = View.VISIBLE
|
||||
ImageConverter.setImageFromDrawable(
|
||||
holder.view,
|
||||
holder.commenter,
|
||||
binding.root,
|
||||
binding.commenter,
|
||||
R.drawable.ic_comment_blue
|
||||
)
|
||||
}
|
||||
|
@ -633,15 +589,16 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
fun addComment(context: android.content.Context, commentContainer: LinearLayout, commentUsername: String, commentContent: String) {
|
||||
|
||||
val view = LayoutInflater.from(context)
|
||||
.inflate(R.layout.comment, commentContainer, true)
|
||||
|
||||
view.user.text = commentUsername
|
||||
view.commentText.text = commentContent
|
||||
val itemBinding = CommentBinding.inflate(
|
||||
LayoutInflater.from(context), commentContainer, false
|
||||
)
|
||||
|
||||
itemBinding.user.text = commentUsername
|
||||
itemBinding.commentText.text = commentContent
|
||||
}
|
||||
|
||||
private suspend fun retrieveComments(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
|
@ -649,15 +606,15 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
try {
|
||||
val statuses = api.statusComments(it, credential).descendants
|
||||
|
||||
holder.commentCont.removeAllViews()
|
||||
binding.commentContainer.removeAllViews()
|
||||
|
||||
//Create the new views for each comment
|
||||
for (status in statuses) {
|
||||
addComment(holder.view.context, holder.commentCont, status.account!!.username!!,
|
||||
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!,
|
||||
status.content!!
|
||||
)
|
||||
}
|
||||
holder.commentCont.visibility = View.VISIBLE
|
||||
binding.commentContainer.visibility = View.VISIBLE
|
||||
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT FETCH ERROR", exception.toString())
|
||||
|
@ -668,37 +625,36 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
}
|
||||
|
||||
private suspend fun postComment(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
val textIn = holder.comment.text
|
||||
val textIn = binding.editComment.text
|
||||
val nonNullText = textIn.toString()
|
||||
status?.id?.let {
|
||||
try {
|
||||
val response = api.postStatus(credential, nonNullText, it)
|
||||
holder.commentIn.visibility = View.GONE
|
||||
binding.commentIn.visibility = View.GONE
|
||||
|
||||
//Add the comment to the comment section
|
||||
addComment(
|
||||
holder.view.context, holder.commentCont, response.account!!.username!!,
|
||||
binding.root.context, binding.commentContainer, response.account!!.username!!,
|
||||
response.content!!
|
||||
)
|
||||
|
||||
Toast.makeText(
|
||||
holder.view.context,
|
||||
holder.view.context.getString(R.string.comment_posted).format(textIn),
|
||||
binding.root.context,
|
||||
binding.root.context.getString(R.string.comment_posted).format(textIn),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
holder.view.context, holder.view.context.getString(R.string.comment_error),
|
||||
binding.root.context, binding.root.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
holder.view.context, holder.view.context.getString(R.string.comment_error),
|
||||
binding.root.context, binding.root.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Log.e("ERROR_CODE", exception.code().toString())
|
||||
|
@ -709,26 +665,32 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): StatusViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.post_fragment, parent, false)
|
||||
return StatusViewHolder(view)
|
||||
val itemBinding = PostFragmentBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return StatusViewHolder(itemBinding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>) : RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
|
||||
class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>) :
|
||||
RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.album_image_view, parent, false))
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val itemBinding = AlbumImageViewBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return ViewHolder(itemBinding)
|
||||
}
|
||||
|
||||
override fun getItemCount() = media_attachments.size
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
Glide.with(holder.view)
|
||||
Glide.with(holder.binding.root)
|
||||
.asDrawable().fitCenter().placeholder(ColorDrawable(Color.GRAY))
|
||||
.load(media_attachments[position].url).into(holder.image)
|
||||
|
||||
val description = media_attachments[position].description
|
||||
.orEmpty().ifEmpty{ holder.view.context.getString(R.string.no_description)}
|
||||
.orEmpty().ifEmpty{ holder.binding.root.context.getString(R.string.no_description)}
|
||||
|
||||
holder.image.setOnLongClickListener {
|
||||
Snackbar.make(it, description, Snackbar.LENGTH_SHORT).show()
|
||||
|
@ -738,7 +700,7 @@ class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>) : R
|
|||
holder.image.contentDescription = description
|
||||
}
|
||||
|
||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view){
|
||||
val image: ImageView = view.findViewById(R.id.imageImageView)
|
||||
class ViewHolder(val binding: AlbumImageViewBinding) : RecyclerView.ViewHolder(binding.root){
|
||||
val image: ImageView = binding.imageImageView
|
||||
}
|
||||
}
|
|
@ -18,22 +18,21 @@ import androidx.paging.PagingDataAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.h.pixeldroid.posts.PostActivity
|
||||
import com.h.pixeldroid.profile.ProfileActivity
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Notification
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import kotlinx.android.synthetic.main.fragment_notifications.view.*
|
||||
|
||||
import com.h.pixeldroid.databinding.FragmentNotificationsBinding
|
||||
import com.h.pixeldroid.posts.PostActivity
|
||||
import com.h.pixeldroid.posts.feeds.cachedFeeds.CachedFeedFragment
|
||||
import com.h.pixeldroid.posts.feeds.cachedFeeds.FeedViewModel
|
||||
import com.h.pixeldroid.posts.feeds.cachedFeeds.ViewModelFactory
|
||||
import com.h.pixeldroid.posts.parseHTMLText
|
||||
import com.h.pixeldroid.posts.setTextViewFromISO8601
|
||||
import com.h.pixeldroid.profile.ProfileActivity
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Notification
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
|
||||
|
||||
|
||||
/**
|
||||
|
@ -71,12 +70,12 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
|
|||
/**
|
||||
* View Holder for a [Notification] RecyclerView list item.
|
||||
*/
|
||||
class NotificationViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val notificationType: TextView = view.notification_type
|
||||
private val notificationTime: TextView = view.notification_time
|
||||
private val postDescription: TextView = view.notification_post_description
|
||||
private val avatar: ImageView = view.notification_avatar
|
||||
private val photoThumbnail: ImageView = view.notification_photo_thumbnail
|
||||
class NotificationViewHolder(binding: FragmentNotificationsBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
private val notificationType: TextView = binding.notificationType
|
||||
private val notificationTime: TextView = binding.notificationTime
|
||||
private val postDescription: TextView = binding.notificationPostDescription
|
||||
private val avatar: ImageView = binding.notificationAvatar
|
||||
private val photoThumbnail: ImageView = binding.notificationPhotoThumbnail
|
||||
|
||||
private var notification: Notification? = null
|
||||
|
||||
|
@ -216,9 +215,10 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
|
|||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): NotificationViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.fragment_notifications, parent, false)
|
||||
return NotificationViewHolder(view)
|
||||
val itemBinding = FragmentNotificationsBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return NotificationViewHolder(itemBinding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.AccountListEntryBinding
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG
|
||||
import com.h.pixeldroid.utils.api.objects.Account.Companion.FOLLOWERS_TAG
|
||||
import kotlinx.android.synthetic.main.account_list_entry.view.*
|
||||
|
||||
|
||||
/**
|
||||
|
@ -75,10 +75,10 @@ class AccountListFragment : UncachedFeedFragment<Account>() {
|
|||
/**
|
||||
* View Holder for an [Account] RecyclerView list item.
|
||||
*/
|
||||
class AccountViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val avatar : ImageView = view.account_entry_avatar
|
||||
private val username : TextView = view.account_entry_username
|
||||
private val acct: TextView = view.account_entry_acct
|
||||
class AccountViewHolder(binding: AccountListEntryBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
private val avatar : ImageView = binding.accountEntryAvatar
|
||||
private val username : TextView = binding.accountEntryUsername
|
||||
private val acct: TextView = binding.accountEntryAcct
|
||||
|
||||
private var account: Account? = null
|
||||
|
||||
|
@ -104,9 +104,10 @@ class AccountViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): AccountViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.account_list_entry, parent, false)
|
||||
return AccountViewHolder(view)
|
||||
val itemBinding = AccountListEntryBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return AccountViewHolder(itemBinding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ import androidx.paging.PagingDataAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment
|
||||
import com.h.pixeldroid.databinding.FragmentTagsBinding
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment
|
||||
import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory
|
||||
import com.h.pixeldroid.utils.api.objects.Results
|
||||
import com.h.pixeldroid.utils.api.objects.Tag
|
||||
import kotlinx.android.synthetic.main.fragment_tags.view.*
|
||||
|
||||
/**
|
||||
* Fragment to show a list of [hashtag][Tag]s, as a result of a search.
|
||||
|
@ -100,9 +100,9 @@ class HashTagAdapter : PagingDataAdapter<Tag, RecyclerView.ViewHolder>(
|
|||
/**
|
||||
* View Holder for a [Tag] RecyclerView list item.
|
||||
*/
|
||||
class HashTagViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
class HashTagViewHolder(binding: FragmentTagsBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
private val name : TextView = view.tag_name
|
||||
private val name : TextView = binding.tagName
|
||||
|
||||
private var tag: Tag? = null
|
||||
|
||||
|
@ -124,9 +124,10 @@ class HashTagViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): HashTagViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.fragment_tags, parent, false)
|
||||
return HashTagViewHolder(view)
|
||||
val itemBinding = FragmentTagsBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return HashTagViewHolder(itemBinding)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,14 +14,14 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Relationship
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.databinding.ActivityProfileBinding
|
||||
import com.h.pixeldroid.posts.parseHTMLText
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Account
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.openUrl
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.Call
|
||||
|
@ -33,16 +33,17 @@ import java.io.IOException
|
|||
class ProfileActivity : BaseActivity() {
|
||||
private lateinit var pixelfedAPI : PixelfedAPI
|
||||
private lateinit var adapter : ProfilePostsRecyclerViewAdapter
|
||||
private lateinit var recycler : RecyclerView
|
||||
private lateinit var refreshLayout: SwipeRefreshLayout
|
||||
private lateinit var accessToken : String
|
||||
private lateinit var domain : String
|
||||
private var user: UserDatabaseEntity? = null
|
||||
|
||||
private lateinit var binding: ActivityProfileBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_profile)
|
||||
binding = ActivityProfileBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
user = db.userDao().getActiveUser()
|
||||
|
@ -52,19 +53,16 @@ class ProfileActivity : BaseActivity() {
|
|||
accessToken = user?.accessToken.orEmpty()
|
||||
|
||||
// Set posts RecyclerView as a grid with 3 columns
|
||||
recycler = findViewById(R.id.profilePostsRecyclerView)
|
||||
recycler.layoutManager = GridLayoutManager(applicationContext, 3)
|
||||
binding.profilePostsRecyclerView.layoutManager = GridLayoutManager(applicationContext, 3)
|
||||
adapter = ProfilePostsRecyclerViewAdapter()
|
||||
recycler.adapter = adapter
|
||||
binding.profilePostsRecyclerView.adapter = adapter
|
||||
|
||||
// Set profile according to given account
|
||||
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
|
||||
|
||||
setContent(account)
|
||||
|
||||
refreshLayout = findViewById(R.id.profileRefreshLayout)
|
||||
|
||||
refreshLayout.setOnRefreshListener {
|
||||
binding.profileRefreshLayout.setOnRefreshListener {
|
||||
getAndSetAccount(account?.id ?: user!!.user_id)
|
||||
}
|
||||
}
|
||||
|
@ -101,9 +99,9 @@ class ProfileActivity : BaseActivity() {
|
|||
|
||||
|
||||
// On click open followers list
|
||||
findViewById<TextView>(R.id.nbFollowersTextView).setOnClickListener{ onClickFollowers(account) }
|
||||
binding.nbFollowersTextView.setOnClickListener{ onClickFollowers(account) }
|
||||
// On click open followers list
|
||||
findViewById<TextView>(R.id.nbFollowingTextView).setOnClickListener{ onClickFollowing(account) }
|
||||
binding.nbFollowingTextView.setOnClickListener{ onClickFollowing(account) }
|
||||
}
|
||||
|
||||
private fun getAndSetAccount(id: String){
|
||||
|
@ -121,59 +119,50 @@ class ProfileActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
private fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){
|
||||
val motionLayout = findViewById<MotionLayout>(R.id.motionLayout)
|
||||
val motionLayout = binding.motionLayout
|
||||
if(show){
|
||||
motionLayout?.transitionToEnd()
|
||||
motionLayout.transitionToEnd()
|
||||
} else {
|
||||
motionLayout?.transitionToStart()
|
||||
motionLayout.transitionToStart()
|
||||
}
|
||||
findViewById<ProgressBar>(R.id.profileProgressBar).visibility = View.GONE
|
||||
refreshLayout.isRefreshing = false
|
||||
binding.profileProgressBar.visibility = View.GONE
|
||||
binding.profileRefreshLayout.isRefreshing = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate profile page with user's data
|
||||
*/
|
||||
private fun setViews(account: Account) {
|
||||
val profilePicture = findViewById<ImageView>(R.id.profilePictureImageView)
|
||||
val profilePicture = binding.profilePictureImageView
|
||||
ImageConverter.setRoundImageFromURL(
|
||||
View(applicationContext),
|
||||
account.avatar,
|
||||
profilePicture
|
||||
)
|
||||
|
||||
val description = findViewById<TextView>(R.id.descriptionTextView)
|
||||
description.text = parseHTMLText(
|
||||
binding.descriptionTextView.text = parseHTMLText(
|
||||
account.note ?: "", emptyList(), pixelfedAPI,
|
||||
applicationContext, "Bearer $accessToken",
|
||||
lifecycleScope
|
||||
)
|
||||
|
||||
val accountName = findViewById<TextView>(R.id.accountNameTextView)
|
||||
accountName.text = account.getDisplayName()
|
||||
|
||||
val displayName = account.getDisplayName()
|
||||
|
||||
binding.accountNameTextView.text = displayName
|
||||
|
||||
supportActionBar?.title = displayName
|
||||
if(displayName != "@${account.acct}"){
|
||||
supportActionBar?.subtitle = "@${account.acct}"
|
||||
}
|
||||
|
||||
accountName.setTypeface(null, Typeface.BOLD)
|
||||
|
||||
val nbPosts = findViewById<TextView>(R.id.nbPostsTextView)
|
||||
nbPosts.text = applicationContext.getString(R.string.nb_posts)
|
||||
binding.nbPostsTextView.text = applicationContext.getString(R.string.nb_posts)
|
||||
.format(account.statuses_count.toString())
|
||||
nbPosts.setTypeface(null, Typeface.BOLD)
|
||||
|
||||
val nbFollowers = findViewById<TextView>(R.id.nbFollowersTextView)
|
||||
nbFollowers.text = applicationContext.getString(R.string.nb_followers)
|
||||
binding.nbFollowersTextView.text = applicationContext.getString(R.string.nb_followers)
|
||||
.format(account.followers_count.toString())
|
||||
nbFollowers.setTypeface(null, Typeface.BOLD)
|
||||
|
||||
val nbFollowing = findViewById<TextView>(R.id.nbFollowingTextView)
|
||||
nbFollowing.text = applicationContext.getString(R.string.nb_following)
|
||||
binding.nbFollowingTextView.text = applicationContext.getString(R.string.nb_following)
|
||||
.format(account.following_count.toString())
|
||||
nbFollowing.setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,9 +216,10 @@ class ProfileActivity : BaseActivity() {
|
|||
|
||||
private fun activateEditButton() {
|
||||
// Edit button redirects to Pixelfed's "edit account" page
|
||||
val editButton = findViewById<Button>(R.id.editButton)
|
||||
editButton.visibility = View.VISIBLE
|
||||
editButton.setOnClickListener{ onClickEditButton() }
|
||||
binding.editButton.apply {
|
||||
visibility = View.VISIBLE
|
||||
setOnClickListener{ onClickEditButton() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,14 +234,12 @@ class ProfileActivity : BaseActivity() {
|
|||
).firstOrNull()
|
||||
|
||||
if(relationship != null){
|
||||
val followButton = findViewById<Button>(R.id.followButton)
|
||||
|
||||
if (relationship.following) {
|
||||
setOnClickUnfollow(account)
|
||||
} else {
|
||||
setOnClickFollow(account)
|
||||
}
|
||||
followButton.visibility = View.VISIBLE
|
||||
binding.followButton.visibility = View.VISIBLE
|
||||
}
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
|
@ -269,53 +257,51 @@ class ProfileActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
private fun setOnClickFollow(account: Account) {
|
||||
val followButton = findViewById<Button>(R.id.followButton)
|
||||
|
||||
followButton.setText(R.string.follow)
|
||||
|
||||
followButton.setOnClickListener {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
try {
|
||||
pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
setOnClickUnfollow(account)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
binding.followButton.apply {
|
||||
setText(R.string.follow)
|
||||
setOnClickListener {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
try {
|
||||
pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
setOnClickUnfollow(account)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOnClickUnfollow(account: Account) {
|
||||
val followButton = findViewById<Button>(R.id.followButton)
|
||||
binding.followButton.apply {
|
||||
setText(R.string.unfollow)
|
||||
|
||||
followButton.setText(R.string.unfollow)
|
||||
|
||||
followButton.setOnClickListener {
|
||||
|
||||
lifecycleScope.launchWhenResumed {
|
||||
try {
|
||||
pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
setOnClickFollow(account)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
setOnClickListener {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
try {
|
||||
pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
setOnClickFollow(account)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.FragmentSearchBinding
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.profile.ProfilePostViewHolder
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.DiscoverPost
|
||||
|
@ -25,6 +27,7 @@ import com.h.pixeldroid.utils.api.objects.Status
|
|||
import com.h.pixeldroid.posts.PostActivity
|
||||
import com.h.pixeldroid.utils.BaseFragment
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.bindingLifecycleAware
|
||||
import com.mikepenz.iconics.IconicsColor
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
|
@ -46,37 +49,35 @@ class SearchDiscoverFragment : BaseFragment() {
|
|||
private lateinit var recycler : RecyclerView
|
||||
private lateinit var adapter : DiscoverRecyclerViewAdapter
|
||||
private lateinit var accessToken: String
|
||||
private lateinit var discoverProgressBar: ProgressBar
|
||||
private lateinit var discoverRefreshLayout: SwipeRefreshLayout
|
||||
|
||||
var binding: FragmentSearchBinding by bindingLifecycleAware()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_search, container, false)
|
||||
val search = view.findViewById<SearchView>(R.id.search)
|
||||
): View {
|
||||
binding = FragmentSearchBinding.inflate(inflater, container, false)
|
||||
|
||||
//Configure the search widget (see https://developer.android.com/guide/topics/search/search-dialog#ConfiguringWidget)
|
||||
// Configure the search widget (see https://developer.android.com/guide/topics/search/search-dialog#ConfiguringWidget)
|
||||
val searchManager = requireActivity().getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
search.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
|
||||
|
||||
search.isSubmitButtonEnabled = true
|
||||
binding.search.apply {
|
||||
setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
|
||||
isSubmitButtonEnabled = true
|
||||
}
|
||||
|
||||
// Set posts RecyclerView as a grid with 3 columns
|
||||
recycler = view.findViewById(R.id.discoverList)
|
||||
recycler = binding.discoverList
|
||||
recycler.layoutManager = GridLayoutManager(requireContext(), 3)
|
||||
adapter = DiscoverRecyclerViewAdapter()
|
||||
recycler.adapter = adapter
|
||||
|
||||
val discoverText = view.findViewById<TextView>(R.id.discoverText)
|
||||
|
||||
discoverText.setCompoundDrawables(IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_explore).apply {
|
||||
binding.discoverText.setCompoundDrawables(IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_explore).apply {
|
||||
sizeDp = 24
|
||||
paddingDp = 20
|
||||
color = IconicsColor.colorRes(R.color.colorDrawing)
|
||||
}, null, null, null)
|
||||
|
||||
return view
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -86,30 +87,27 @@ class SearchDiscoverFragment : BaseFragment() {
|
|||
|
||||
accessToken = db.userDao().getActiveUser()?.accessToken.orEmpty()
|
||||
|
||||
discoverProgressBar = view.findViewById(R.id.discoverProgressBar)
|
||||
discoverRefreshLayout = view.findViewById(R.id.discoverRefreshLayout)
|
||||
|
||||
getDiscover()
|
||||
|
||||
discoverRefreshLayout.setOnRefreshListener {
|
||||
binding.discoverRefreshLayout.setOnRefreshListener {
|
||||
getDiscover()
|
||||
}
|
||||
}
|
||||
|
||||
fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){
|
||||
val motionLayout = view?.findViewById<MotionLayout>(R.id.motionLayout)
|
||||
if(show){
|
||||
motionLayout?.transitionToEnd()
|
||||
} else {
|
||||
motionLayout?.transitionToStart()
|
||||
binding.motionLayout.apply {
|
||||
if(show){
|
||||
transitionToEnd()
|
||||
} else {
|
||||
transitionToStart()
|
||||
}
|
||||
}
|
||||
discoverRefreshLayout.isRefreshing = false
|
||||
discoverProgressBar.visibility = View.GONE
|
||||
binding.discoverRefreshLayout.isRefreshing = false
|
||||
binding.discoverProgressBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
|
||||
private fun getDiscover() {
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
val discoverPosts = api.discover("Bearer $accessToken")
|
||||
|
|
|
@ -4,18 +4,21 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import com.h.pixeldroid.BuildConfig
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityAboutBinding
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import kotlinx.android.synthetic.main.activity_about.*
|
||||
|
||||
class AboutActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_about)
|
||||
val binding = ActivityAboutBinding.inflate(layoutInflater)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setTitle(R.string.about_pixeldroid)
|
||||
|
||||
aboutVersionNumber.text = BuildConfig.VERSION_NAME
|
||||
licensesButton.setOnClickListener{
|
||||
binding.aboutVersionNumber.text = BuildConfig.VERSION_NAME
|
||||
binding.licensesButton.setOnClickListener{
|
||||
val intent = Intent(this, LicenseActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,18 @@ package com.h.pixeldroid.settings
|
|||
|
||||
import android.os.Bundle
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.databinding.ActivityLicensesBinding
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import kotlinx.android.synthetic.main.activity_licenses.*
|
||||
|
||||
|
||||
class LicenseActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_licenses)
|
||||
val binding = ActivityLicensesBinding.inflate(layoutInflater)
|
||||
|
||||
setContentView(binding.root)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setTitle(R.string.dependencies_licenses)
|
||||
webview.loadUrl("file:///android_asset/licenses.html")
|
||||
|
||||
binding.webview.loadUrl("file:///android_asset/licenses.html")
|
||||
}
|
||||
}
|
|
@ -10,7 +10,12 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.h.pixeldroid.R
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
fun hasInternet(context: Context): Boolean {
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
@ -66,3 +71,25 @@ fun setThemeFromPreferences(preferences: SharedPreferences, resources : Resource
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegated property to use in fragments to prevent memory leaks of bindings.
|
||||
* This makes it unnecessary to set binding to null in onDestroyView.
|
||||
* The value should be assigned in the Fragment's onCreateView()
|
||||
*/
|
||||
fun <T> Fragment.bindingLifecycleAware(): ReadWriteProperty<Fragment, T> =
|
||||
object : ReadWriteProperty<Fragment, T>, DefaultLifecycleObserver {
|
||||
|
||||
private var binding: T? = null
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
binding = null
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T = binding!!
|
||||
|
||||
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
|
||||
binding = value
|
||||
this@bindingLifecycleAware.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||
}
|
||||
}
|
|
@ -8,19 +8,14 @@ import android.graphics.ColorMatrix
|
|||
import android.graphics.ColorMatrixColorFilter
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.text.Spanned
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.databinding.PostFragmentBinding
|
||||
import com.h.pixeldroid.posts.getDomain
|
||||
import com.h.pixeldroid.posts.parseHTMLText
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
@ -93,26 +88,26 @@ open class Status(
|
|||
|
||||
}
|
||||
|
||||
fun setupSensitiveLayout(view: View) {
|
||||
fun setupSensitiveLayout(binding: PostFragmentBinding) {
|
||||
|
||||
// Set dark layout and warning message
|
||||
view.sensitiveWarning.visibility = VISIBLE
|
||||
binding.sensitiveWarning.visibility = VISIBLE
|
||||
val array = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 1f, 0f)
|
||||
val censorMatrix = ColorMatrix(array)
|
||||
view.postPicture.colorFilter = ColorMatrixColorFilter(censorMatrix)
|
||||
binding.postPicture.colorFilter = ColorMatrixColorFilter(censorMatrix)
|
||||
|
||||
fun uncensorPicture(view: View) {
|
||||
view.sensitiveWarning.visibility = GONE
|
||||
view.postPicture.clearColorFilter()
|
||||
fun uncensorPicture(binding: PostFragmentBinding) {
|
||||
binding.sensitiveWarning.visibility = GONE
|
||||
binding.postPicture.clearColorFilter()
|
||||
}
|
||||
|
||||
|
||||
view.findViewById<TextView>(R.id.sensitiveWarning).setOnClickListener {
|
||||
uncensorPicture(view)
|
||||
binding.sensitiveWarning.setOnClickListener {
|
||||
uncensorPicture(binding)
|
||||
}
|
||||
|
||||
view.findViewById<ImageView>(R.id.postPicture).setOnClickListener {
|
||||
uncensorPicture(view)
|
||||
binding.postPicture.setOnClickListener {
|
||||
uncensorPicture(binding)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="30dp"
|
||||
android:height="30dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,7v2.99s-1.99,0.01 -2,0L17,7h-3s0.01,-1.99 0,-2h3L17,2h2v3h3v2h-3zM16,11L16,8h-3L13,5L5,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-8h-3zM5,19l3,-4 2,3 3,-4 4,5L5,19z"
|
||||
android:fillColor="@color/colorDrawing"/>
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="30dp"
|
||||
android:height="30dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="30dp"
|
||||
android:height="30dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
|
@ -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>
|
|
@ -1,10 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="30dp"
|
||||
android:height="30dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</vector>
|
|
@ -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>
|
|
@ -1,8 +1,10 @@
|
|||
<?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:layout_height="match_parent"
|
||||
tools:context=".postCreation.PostCreationActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/upload_error"
|
||||
|
@ -41,29 +43,24 @@
|
|||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.h.pixeldroid.postCreation.carousel.ImageCarousel
|
||||
android:id="@+id/carousel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/uploadProgressBar"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<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="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/image_grid"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/postTextInputLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/upload_completed_textview" />
|
||||
app:layout_constraintBottom_toTopOf="@id/postTextInputLayout"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/buttonConstraints"
|
||||
|
@ -79,10 +76,10 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:backgroundTint="@color/colorButtonBg"
|
||||
android:enabled="false"
|
||||
android:visibility="gone"
|
||||
android:enabled="true"
|
||||
android:text="@string/post"
|
||||
android:textColor="@color/colorButtonText"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -93,10 +90,11 @@
|
|||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<ProgressBar
|
||||
|
@ -104,6 +102,7 @@
|
|||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toTopOf="@id/postTextInputLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
@ -133,4 +132,69 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/toolbar3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#40000000"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<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="@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" />
|
||||
|
||||
<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="@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" />
|
||||
|
||||
<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="@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" />
|
||||
|
||||
|
||||
<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="@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"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -34,6 +34,7 @@
|
|||
android:layout_marginStart="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/default_nposts"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
||||
|
@ -45,6 +46,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/default_nfollowers"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/nbPostsTextView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/nbFollowingTextView"
|
||||
app:layout_constraintStart_toEndOf="@+id/nbPostsTextView"
|
||||
|
@ -57,6 +59,7 @@
|
|||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/default_nfollowing"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/nbFollowersTextView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/nbFollowersTextView" />
|
||||
|
@ -69,9 +72,10 @@
|
|||
android:layout_marginTop="5dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:text="@string/no_username"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/profilePictureImageView"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/profilePictureImageView" />
|
||||
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/addPhotoSquare"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/add_photo_alternate_black_24dp"
|
||||
android:background="@drawable/add_photo_alternate_white_30dp"
|
||||
android:contentDescription="@string/add_photo" />
|
||||
</com.h.pixeldroid.postCreation.SquareLayout>
|
||||
|
|
|
@ -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_24"
|
||||
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>
|
|
@ -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>
|
|
@ -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" />
|
|
@ -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" />
|
|
@ -222,7 +222,7 @@
|
|||
tools:text="Yesterday" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ViewComments"
|
||||
android:id="@+id/viewComments"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
|
@ -267,7 +267,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@+id/ViewComments">
|
||||
app:layout_constraintTop_toBottomOf="@+id/viewComments">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -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" />
|
|
@ -1,20 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
>
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/action_upload"
|
||||
android:id="@+id/action_reset"
|
||||
android:orderInCategory="100"
|
||||
android:title="CREATE POST"
|
||||
android:icon="@drawable/ic_file_upload_24dp"
|
||||
android:title="RESET"
|
||||
android:icon="@drawable/restore_24dp"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:orderInCategory="101"
|
||||
android:title="SAVE"
|
||||
android:icon="@drawable/ic_save_24dp"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
</menu>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -101,9 +101,7 @@
|
|||
<string name="busy_dialog_ok_button">D\'acord, espera per això.</string>
|
||||
<string name="busy_dialog_text">Processant la imatge, espara abans de continuar!</string>
|
||||
<string name="nothing_to_see_here">Res per veure aquí!</string>
|
||||
<string name="OK">D\'ACORD</string>
|
||||
<string name="delete_dialog">Esborra aquest contingut\?</string>
|
||||
<string name="cancel">Cancel·lar</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="poll_notification">%1$s l\'enquesta ha acabat</string>
|
||||
<string name="about_pixeldroid">Quant a PixelDroid</string>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -132,9 +132,7 @@
|
|||
<string name="issues_contribute">مشکلات را گزارش و یا در توسعه برنامه مشارکت کنید:</string>
|
||||
<string name="help_translate">برای ترجمه پیکسدروید به زبان خود کمک کنید:</string>
|
||||
<string name="language">زبان</string>
|
||||
<string name="cancel">لغو</string>
|
||||
<string name="delete_dialog">این فرسته حذف شود؟</string>
|
||||
<string name="OK">باشه</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="post_is_album">این فرسته، یک آلبوم است</string>
|
||||
<string name="submit_comment">فرستادن نظر</string>
|
||||
|
|
|
@ -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>
|
||||
|
@ -112,9 +112,7 @@
|
|||
<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>
|
||||
<string name="cancel">Annuler</string>
|
||||
<string name="delete_dialog">Supprimer cette publication \?</string>
|
||||
<string name="OK">OK</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="discover">DÉCOUVRIR</string>
|
||||
<string name="profile_picture">Photo de profil</string>
|
||||
|
|
|
@ -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>
|
||||
|
@ -132,8 +132,6 @@
|
|||
<string name="issues_contribute">Informa de fallos ou colabora coa aplicación:</string>
|
||||
<string name="help_translate">Axuda a traducir PixelDroid ó teu idioma:</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="delete_dialog">Eliminar esta publicación\?</string>
|
||||
<string name="OK">OK</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
</resources>
|
|
@ -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>
|
||||
|
@ -130,9 +130,7 @@
|
|||
<string name="something_went_wrong">Qualcosa è andato storto…</string>
|
||||
<string name="poll_notification">%1$s\'s sondaggio è terminato</string>
|
||||
<string name="delete">Elimina</string>
|
||||
<string name="OK">OK</string>
|
||||
<string name="delete_dialog">Eliminare questo post\?</string>
|
||||
<string name="cancel">Cancella</string>
|
||||
<string name="language">Lingua</string>
|
||||
<string name="help_translate">Aiuta a tradurre PixelDroid nella tua lingua:</string>
|
||||
<string name="issues_contribute">Segnala problemi o contribuisci all\'applicazione:</string>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -113,9 +113,7 @@
|
|||
<string name="issues_contribute">Problemen in de app melden of er aan meewerken:</string>
|
||||
<string name="help_translate">Help mee om PixelDroid naar jouw eigen taal te vertalen:</string>
|
||||
<string name="language">Taal</string>
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="delete_dialog">Deze post verwijderen\?</string>
|
||||
<string name="OK">OK</string>
|
||||
<string name="delete">Verwijder</string>
|
||||
<string name="panda_pull_to_refresh_to_try_again">Deze panda ziet er niet gelukkig uit. Trek naar beneden om nog eens te proberen.</string>
|
||||
<string name="something_went_wrong">Er is iets misgegaan…</string>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -149,12 +149,12 @@
|
|||
<string name="something_went_wrong">Something went wrong…</string>
|
||||
<string name="panda_pull_to_refresh_to_try_again">This panda is not happy. Pull to refresh to try again.</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="OK">OK</string>
|
||||
<string name="delete_dialog">Delete this post?</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="language">Language</string>
|
||||
<string name="help_translate">Help translate PixelDroid to your language:</string>
|
||||
<string name="issues_contribute">Report issues or contribute to the application:</string>
|
||||
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
|
||||
<string name="save_before_returning">Save your edits?</string>
|
||||
<string name="no_cancel_edit">No, cancel edit</string>
|
||||
|
||||
</resources>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue