Start removing deprecated kotlin-android-extensions uses

This commit is contained in:
Matthieu 2021-01-13 01:28:08 +01:00
parent 8bfbe2fbb5
commit bebf0233dc
15 changed files with 354 additions and 352 deletions

View File

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

View File

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

View File

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

View File

@ -27,6 +27,7 @@ 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
@ -38,9 +39,6 @@ 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.activity_post_creation.view.*
import kotlinx.android.synthetic.main.image_album_creation.view.*
import okhttp3.MultipartBody
import retrofit2.HttpException
import java.io.File
@ -69,9 +67,12 @@ class PostCreationActivity : BaseActivity() {
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) {
@ -108,10 +109,10 @@ class PostCreationActivity : BaseActivity() {
//TODO transition instead of at once
if(it){
// Became a carousel
toolbar3.visibility = VISIBLE
binding.toolbar3.visibility = VISIBLE
} else {
// Became a grid
toolbar3.visibility = INVISIBLE
binding.toolbar3.visibility = INVISIBLE
}
}
carousel.addPhotoButtonCallback = {
@ -125,7 +126,7 @@ class PostCreationActivity : BaseActivity() {
// Button to retry image upload when it fails
findViewById<Button>(R.id.retry_upload_button).setOnClickListener {
upload_error.visibility = View.GONE
binding.uploadError.visibility = View.GONE
photoData.forEach {
it.uploadId = null
it.progress = null
@ -236,23 +237,19 @@ class PostCreationActivity : BaseActivity() {
}
/**
* 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() {
enableButton(false)
uploadProgressBar.visibility = View.VISIBLE
upload_completed_textview.visibility = View.INVISIBLE
removePhotoButton.isEnabled = false
editPhotoButton.isEnabled = false
addPhotoButton.isEnabled = false
binding.uploadProgressBar.visibility = View.VISIBLE
binding.uploadCompletedTextview.visibility = View.INVISIBLE
binding.removePhotoButton.isEnabled = false
binding.editPhotoButton.isEnabled = false
binding.addPhotoButton.isEnabled = false
for (data in photoData) {
for (data: PhotoData in photoData) {
val imageUri = data.imageUri
val imageInputStream = contentResolver.openInputStream(imageUri)!!
@ -282,7 +279,7 @@ class PostCreationActivity : BaseActivity() {
.subscribeOn(Schedulers.io())
.subscribe { percentage ->
data.progress = percentage.toInt()
uploadProgressBar.progress =
binding.uploadProgressBar.progress =
photoData.sumBy { it.progress ?: 0 } / photoData.size
}
@ -298,7 +295,7 @@ class PostCreationActivity : BaseActivity() {
data.uploadId = attachment.id!!
},
{ e ->
upload_error.visibility = View.VISIBLE
binding.uploadError.visibility = View.VISIBLE
e.printStackTrace()
postSub?.dispose()
sub.dispose()
@ -306,8 +303,8 @@ class PostCreationActivity : BaseActivity() {
{
data.progress = 100
if(photoData.all{it.progress == 100}){
uploadProgressBar.visibility = View.GONE
upload_completed_textview.visibility = View.VISIBLE
binding.uploadProgressBar.visibility = View.GONE
binding.uploadCompletedTextview.visibility = View.VISIBLE
post()
}
postSub?.dispose()
@ -318,7 +315,7 @@ 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 {
@ -347,13 +344,13 @@ 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
}
}
@ -373,7 +370,7 @@ class PostCreationActivity : BaseActivity() {
if (resultCode == Activity.RESULT_OK && data != null) {
photoData[positionResult].imageUri = data.getStringExtra("result")!!.toUri()
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
binding.carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
photoData[positionResult].progress = null
photoData[positionResult].uploadId = null
@ -389,7 +386,7 @@ class PostCreationActivity : BaseActivity() {
photoData.add(PhotoData(imageUri))
}
carousel.addData(photoData.map { CarouselItem(it.imageUri.toString()) })
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()
}

View File

@ -22,6 +22,8 @@ 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.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
@ -29,7 +31,6 @@ 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
@ -86,9 +87,14 @@ 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)
@ -118,7 +124,7 @@ class PhotoEditActivity : BaseActivity() {
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 {
@ -192,7 +198,7 @@ class PhotoEditActivity : BaseActivity() {
fun onFilterSelected(filter: Filter) {
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()
@ -211,8 +217,8 @@ class PhotoEditActivity : BaseActivity() {
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)
}
}
}
@ -292,8 +298,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)
@ -324,7 +330,7 @@ class PhotoEditActivity : BaseActivity() {
// permission was granted
permissionsGrantedToSave()
} else {
Snackbar.make(coordinator_edit, getString(R.string.permission_denied),
Snackbar.make(binding.root, getString(R.string.permission_denied),
Snackbar.LENGTH_LONG).show()
}
}
@ -396,7 +402,7 @@ class PhotoEditActivity : BaseActivity() {
return
}
saving = true
progressBarSaveFile.visibility = VISIBLE
binding.progressBarSaveFile.visibility = VISIBLE
saveFuture = saveExecutor.submit {
try {
val path: String
@ -413,17 +419,17 @@ class PhotoEditActivity : BaseActivity() {
if(saving) {
this.runOnUiThread {
sendBackImage(path)
progressBarSaveFile.visibility = GONE
binding.progressBarSaveFile.visibility = GONE
saving = false
}
}
} 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()
progressBarSaveFile.visibility = GONE
binding.progressBarSaveFile.visibility = GONE
saving = false
}
}

View File

@ -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>,
@ -24,8 +23,8 @@ class ThumbnailAdapter (private val context: Context,
}
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 {
@ -49,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
}
}

View File

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

View File

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

View File

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

View File

@ -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.root)
}
}
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()
}
@ -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,7 +365,7 @@ 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(android.R.string.ok) { _, _ ->
@ -413,7 +377,7 @@ 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) {
}
@ -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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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