chinwag-android/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt

292 lines
12 KiB
Kotlin
Raw Normal View History

2017-11-06 08:32:36 +11:00
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.fragment
import android.graphics.Color
import android.os.Bundle
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
2017-11-06 08:32:36 +11:00
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
2017-11-06 08:32:36 +11:00
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.di.Injectable
2017-12-01 06:12:09 +11:00
import com.keylesspalace.tusky.entity.Attachment
2017-11-06 08:32:36 +11:00
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.network.MastodonApi
Theming improvements (#502) * Split theme definitions into day and night * Add support for Night Mode in code * Add theme chooser in preferences * Fix translations * Adjust IDs * Adjust preferences for custom themes * UI tweaks for custom theme support * Added code for custom theme support 🍅 * Fixed resource display in Kotlin 🍅 * Restored styles * Updated strings * Fixed getIdentifier() to fit into setTheme() * Removed redundant resources * Reset default theme to "Dusky" * Fixed night mode handler to maintain compatibility * Refactor functions to use helper methods * Added license block * Added preview to theme selector * Added color identifier getter helper method * Fixed reference in AccountMediaFragment * Cleanup * Fixed navbar foreground not changing color * Fix fallback theme switch(){} * Enable location-based daylight trigger * Cleanup * Modified theming strategy to reduce clutter in preferences * Updated translations for latest version * Removed "Default" theme flavor from settings * Updated Polish translations 🇵🇱 * Modified TwilightManager handling code to support Android M's UiModeManager features and moved it to its own function * Updated Polish translations 🇵🇱 * Cleanup; Fixed hardcoded string * Added missing escape in string * Removed permission request dialog. As we now use native UiModeManager APIs that don't need special permission for Android 6.0 and above, we no longer need to bother user with Android M+ specific location permission request dialog. * Increased readability of ThemeUtil class * Refactored ThemeUtils.setAppNightMode method * Cleanup
2018-01-20 23:39:01 +11:00
import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.visible
2017-11-06 08:32:36 +11:00
import com.keylesspalace.tusky.view.SquareImageView
import com.keylesspalace.tusky.viewdata.AttachmentViewData
2017-11-06 08:32:36 +11:00
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.fragment_timeline.*
2017-11-06 08:32:36 +11:00
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
import javax.inject.Inject
2017-11-06 08:32:36 +11:00
/**
* Created by charlag on 26/10/2017.
*
* Fragment with multiple columns of media previews for the specified account.
*/
class AccountMediaFragment : BaseFragment(), Injectable {
2017-11-06 08:32:36 +11:00
companion object {
@JvmStatic
fun newInstance(accountId: String): AccountMediaFragment {
val fragment = AccountMediaFragment()
val args = Bundle()
args.putString(ACCOUNT_ID_ARG, accountId)
fragment.arguments = args
2017-11-06 08:32:36 +11:00
return fragment
}
private const val ACCOUNT_ID_ARG = "account_id"
private const val TAG = "AccountMediaFragment"
}
@Inject
lateinit var api: MastodonApi
2017-11-06 08:32:36 +11:00
private val adapter = MediaGridAdapter()
private var currentCall: Call<List<Status>>? = null
private val statuses = mutableListOf<Status>()
private var fetchingStatus = FetchingStatus.NOT_FETCHING
private val callback = object : Callback<List<Status>> {
override fun onFailure(call: Call<List<Status>>?, t: Throwable?) {
fetchingStatus = FetchingStatus.NOT_FETCHING
2018-07-08 19:41:08 +10:00
if(isAdded) {
swipe_refresh_layout.isRefreshing = false
progress_bar.visibility = View.GONE
}
2017-11-06 08:32:36 +11:00
Log.d(TAG, "Failed to fetch account media", t)
}
override fun onResponse(call: Call<List<Status>>, response: Response<List<Status>>) {
fetchingStatus = FetchingStatus.NOT_FETCHING
2018-07-08 19:41:08 +10:00
if(isAdded) {
swipe_refresh_layout.isRefreshing = false
progress_bar.visibility = View.GONE
val body = response.body()
body?.let { fetched ->
statuses.addAll(0, fetched)
// flatMap requires iterable but I don't want to box each array into list
val result = mutableListOf<AttachmentViewData>()
for (status in fetched) {
result.addAll(AttachmentViewData.list(status))
}
adapter.addTop(result)
nothing_message.visible(statuses.isEmpty())
2017-11-06 08:32:36 +11:00
}
}
}
}
private val bottomCallback = object : Callback<List<Status>> {
override fun onFailure(call: Call<List<Status>>?, t: Throwable?) {
fetchingStatus = FetchingStatus.NOT_FETCHING
Log.d(TAG, "Failed to fetch account media", t)
}
override fun onResponse(call: Call<List<Status>>, response: Response<List<Status>>) {
fetchingStatus = FetchingStatus.NOT_FETCHING
val body = response.body()
body?.let { fetched ->
Log.d(TAG, "fetched ${fetched.size} statuses")
if (fetched.isNotEmpty()) Log.d(TAG, "first: ${fetched.first().id}, last: ${fetched.last().id}")
statuses.addAll(fetched)
Log.d(TAG, "now there are ${statuses.size} statuses")
// flatMap requires iterable but I don't want to box each array into list
val result = mutableListOf<AttachmentViewData>()
2017-11-06 08:32:36 +11:00
for (status in fetched) {
result.addAll(AttachmentViewData.list(status))
2017-11-06 08:32:36 +11:00
}
adapter.addBottom(result)
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timeline, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val columnCount = context?.resources?.getInteger(R.integer.profile_media_column_count) ?: 2
2017-11-06 08:32:36 +11:00
val layoutManager = GridLayoutManager(context, columnCount)
Theming improvements (#502) * Split theme definitions into day and night * Add support for Night Mode in code * Add theme chooser in preferences * Fix translations * Adjust IDs * Adjust preferences for custom themes * UI tweaks for custom theme support * Added code for custom theme support 🍅 * Fixed resource display in Kotlin 🍅 * Restored styles * Updated strings * Fixed getIdentifier() to fit into setTheme() * Removed redundant resources * Reset default theme to "Dusky" * Fixed night mode handler to maintain compatibility * Refactor functions to use helper methods * Added license block * Added preview to theme selector * Added color identifier getter helper method * Fixed reference in AccountMediaFragment * Cleanup * Fixed navbar foreground not changing color * Fix fallback theme switch(){} * Enable location-based daylight trigger * Cleanup * Modified theming strategy to reduce clutter in preferences * Updated translations for latest version * Removed "Default" theme flavor from settings * Updated Polish translations 🇵🇱 * Modified TwilightManager handling code to support Android M's UiModeManager features and moved it to its own function * Updated Polish translations 🇵🇱 * Cleanup; Fixed hardcoded string * Added missing escape in string * Removed permission request dialog. As we now use native UiModeManager APIs that don't need special permission for Android 6.0 and above, we no longer need to bother user with Android M+ specific location permission request dialog. * Increased readability of ThemeUtil class * Refactored ThemeUtils.setAppNightMode method * Cleanup
2018-01-20 23:39:01 +11:00
val bgRes = ThemeUtils.getColorId(context, R.attr.window_background)
adapter.baseItemColor = ContextCompat.getColor(recycler_view.context, bgRes)
2017-11-06 08:32:36 +11:00
recycler_view.layoutManager = layoutManager
recycler_view.adapter = adapter
2017-11-06 08:32:36 +11:00
val accountId = arguments?.getString(ACCOUNT_ID_ARG)
2017-11-06 08:32:36 +11:00
swipe_refresh_layout.setOnRefreshListener {
2017-11-06 08:32:36 +11:00
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return@setOnRefreshListener
currentCall = if (statuses.isEmpty()) {
fetchingStatus = FetchingStatus.INITIAL_FETCHING
Account activity redesign (#662) * Refactor-all-the-things version of the fix for issue #573 * Migrate SpanUtils to kotlin because why not * Minimal fix for issue #573 * Add tests for compose spanning * Clean up code suggestions * Make FakeSpannable.getSpans implementation less awkward * Add secondary validation pass for urls * Address code review feedback * Fixup type filtering in FakeSpannable again * Make all mentions in compose activity use the default link color * new layout for AccountActivity * fix the light theme * convert AccountActivity to Kotlin * introduce AccountViewModel * Merge branch 'master' into account-activity-redesign # Conflicts: # app/src/main/java/com/keylesspalace/tusky/AccountActivity.java * add Bot badge to profile * parse custom emojis in usernames * add possibility to cancel follow request * add third tab on profiles * add account fields to profile * add support for moved accounts * set click listener on account moved view * fix tests * use 24dp as statusbar size * add ability to hide reblogs from followed accounts * add button to edit own account to AccountActivity * set toolbar top margin programmatically * fix crash * add shadow behind statusbar * introduce ViewExtensions to clean up code * move code out of offsetChangedListener for perf reasons * clean up stuff * add error handling * improve type safety * fix ConstraintLayout warning * remove unneeded ressources * fix event dispatching * fix crash in event handling * set correct emoji on title * improve some things * wrap follower/foillowing/status views
2018-06-18 21:26:18 +10:00
api.accountStatuses(accountId, null, null, null, null, true)
2017-11-06 08:32:36 +11:00
} else {
fetchingStatus = FetchingStatus.REFRESHING
Account activity redesign (#662) * Refactor-all-the-things version of the fix for issue #573 * Migrate SpanUtils to kotlin because why not * Minimal fix for issue #573 * Add tests for compose spanning * Clean up code suggestions * Make FakeSpannable.getSpans implementation less awkward * Add secondary validation pass for urls * Address code review feedback * Fixup type filtering in FakeSpannable again * Make all mentions in compose activity use the default link color * new layout for AccountActivity * fix the light theme * convert AccountActivity to Kotlin * introduce AccountViewModel * Merge branch 'master' into account-activity-redesign # Conflicts: # app/src/main/java/com/keylesspalace/tusky/AccountActivity.java * add Bot badge to profile * parse custom emojis in usernames * add possibility to cancel follow request * add third tab on profiles * add account fields to profile * add support for moved accounts * set click listener on account moved view * fix tests * use 24dp as statusbar size * add ability to hide reblogs from followed accounts * add button to edit own account to AccountActivity * set toolbar top margin programmatically * fix crash * add shadow behind statusbar * introduce ViewExtensions to clean up code * move code out of offsetChangedListener for perf reasons * clean up stuff * add error handling * improve type safety * fix ConstraintLayout warning * remove unneeded ressources * fix event dispatching * fix crash in event handling * set correct emoji on title * improve some things * wrap follower/foillowing/status views
2018-06-18 21:26:18 +10:00
api.accountStatuses(accountId, null, statuses[0].id, null, null, true)
2017-11-06 08:32:36 +11:00
}
currentCall?.enqueue(callback)
}
swipe_refresh_layout.setColorSchemeResources(R.color.tusky_blue)
swipe_refresh_layout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(context, android.R.attr.colorBackground))
2017-11-06 08:32:36 +11:00
nothing_message.visibility = View.GONE
2017-11-06 08:32:36 +11:00
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recycler_view: RecyclerView, dx: Int, dy: Int) {
2017-11-06 08:32:36 +11:00
if (dy > 0) {
val itemCount = layoutManager.itemCount
val lastItem = layoutManager.findLastCompletelyVisibleItemPosition()
if (itemCount <= lastItem + 3 && fetchingStatus == FetchingStatus.NOT_FETCHING) {
statuses.lastOrNull()?.let { last ->
Log.d(TAG, "Requesting statuses with max_id: ${last.id}, (bottom)")
fetchingStatus = FetchingStatus.FETCHING_BOTTOM
Account activity redesign (#662) * Refactor-all-the-things version of the fix for issue #573 * Migrate SpanUtils to kotlin because why not * Minimal fix for issue #573 * Add tests for compose spanning * Clean up code suggestions * Make FakeSpannable.getSpans implementation less awkward * Add secondary validation pass for urls * Address code review feedback * Fixup type filtering in FakeSpannable again * Make all mentions in compose activity use the default link color * new layout for AccountActivity * fix the light theme * convert AccountActivity to Kotlin * introduce AccountViewModel * Merge branch 'master' into account-activity-redesign # Conflicts: # app/src/main/java/com/keylesspalace/tusky/AccountActivity.java * add Bot badge to profile * parse custom emojis in usernames * add possibility to cancel follow request * add third tab on profiles * add account fields to profile * add support for moved accounts * set click listener on account moved view * fix tests * use 24dp as statusbar size * add ability to hide reblogs from followed accounts * add button to edit own account to AccountActivity * set toolbar top margin programmatically * fix crash * add shadow behind statusbar * introduce ViewExtensions to clean up code * move code out of offsetChangedListener for perf reasons * clean up stuff * add error handling * improve type safety * fix ConstraintLayout warning * remove unneeded ressources * fix event dispatching * fix crash in event handling * set correct emoji on title * improve some things * wrap follower/foillowing/status views
2018-06-18 21:26:18 +10:00
currentCall = api.accountStatuses(accountId, last.id, null, null, null, true)
2017-11-06 08:32:36 +11:00
currentCall?.enqueue(bottomCallback)
}
}
}
}
})
}
// That's sort of an optimization to only load media once user has opened the tab
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (!isVisibleToUser) return
val accountId = arguments?.getString(ACCOUNT_ID_ARG)
2017-11-06 08:32:36 +11:00
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
fetchingStatus = FetchingStatus.INITIAL_FETCHING
Account activity redesign (#662) * Refactor-all-the-things version of the fix for issue #573 * Migrate SpanUtils to kotlin because why not * Minimal fix for issue #573 * Add tests for compose spanning * Clean up code suggestions * Make FakeSpannable.getSpans implementation less awkward * Add secondary validation pass for urls * Address code review feedback * Fixup type filtering in FakeSpannable again * Make all mentions in compose activity use the default link color * new layout for AccountActivity * fix the light theme * convert AccountActivity to Kotlin * introduce AccountViewModel * Merge branch 'master' into account-activity-redesign # Conflicts: # app/src/main/java/com/keylesspalace/tusky/AccountActivity.java * add Bot badge to profile * parse custom emojis in usernames * add possibility to cancel follow request * add third tab on profiles * add account fields to profile * add support for moved accounts * set click listener on account moved view * fix tests * use 24dp as statusbar size * add ability to hide reblogs from followed accounts * add button to edit own account to AccountActivity * set toolbar top margin programmatically * fix crash * add shadow behind statusbar * introduce ViewExtensions to clean up code * move code out of offsetChangedListener for perf reasons * clean up stuff * add error handling * improve type safety * fix ConstraintLayout warning * remove unneeded ressources * fix event dispatching * fix crash in event handling * set correct emoji on title * improve some things * wrap follower/foillowing/status views
2018-06-18 21:26:18 +10:00
currentCall = api.accountStatuses(accountId, null, null, null, null, true)
2017-11-06 08:32:36 +11:00
currentCall?.enqueue(callback)
}
}
private fun viewMedia(items: List<AttachmentViewData>, currentIndex: Int, view: View?) {
val type = items[currentIndex].attachment.type
2017-11-06 08:32:36 +11:00
when (type) {
Attachment.Type.IMAGE,
Attachment.Type.GIFV,
Attachment.Type.VIDEO -> {
val intent = ViewMediaActivity.newIntent(context, items, currentIndex)
if (view != null && activity != null) {
val url = items[currentIndex].attachment.url
2017-11-06 08:32:36 +11:00
ViewCompat.setTransitionName(view, url)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity!!, view, url)
2017-11-06 08:32:36 +11:00
startActivity(intent, options.toBundle())
} else {
startActivity(intent)
}
}
Attachment.Type.UNKNOWN -> {
2017-11-06 08:32:36 +11:00
}/* Intentionally do nothing. This case is here is to handle when new attachment
* types are added to the API before code is added here to handle them. So, the
* best fallback is to just show the preview and ignore requests to view them. */
2018-07-08 19:41:08 +10:00
2017-11-06 08:32:36 +11:00
}
}
private enum class FetchingStatus {
NOT_FETCHING, INITIAL_FETCHING, FETCHING_BOTTOM, REFRESHING
}
inner class MediaGridAdapter:
RecyclerView.Adapter<MediaGridAdapter.MediaViewHolder>() {
2017-11-06 08:32:36 +11:00
var baseItemColor = Color.BLACK
private val items = mutableListOf<AttachmentViewData>()
2017-11-06 08:32:36 +11:00
private val itemBgBaseHSV = FloatArray(3)
private val random = Random()
fun addTop(newItems: List<AttachmentViewData>) {
2017-11-06 08:32:36 +11:00
items.addAll(0, newItems)
notifyItemRangeInserted(0, newItems.size)
}
fun addBottom(newItems: List<AttachmentViewData>) {
2017-11-06 08:32:36 +11:00
if (newItems.isEmpty()) return
val oldLen = items.size
items.addAll(newItems)
notifyItemRangeInserted(oldLen, newItems.size)
}
override fun onAttachedToRecyclerView(recycler_view: RecyclerView) {
2017-11-06 08:32:36 +11:00
val hsv = FloatArray(3)
Color.colorToHSV(baseItemColor, hsv)
super.onAttachedToRecyclerView(recycler_view)
2017-11-06 08:32:36 +11:00
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder {
val view = SquareImageView(parent.context)
view.scaleType = ImageView.ScaleType.CENTER_CROP
return MediaViewHolder(view)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(holder: MediaViewHolder, position: Int) {
itemBgBaseHSV[2] = random.nextFloat() * (1f - 0.3f) + 0.3f
holder.imageView.setBackgroundColor(Color.HSVToColor(itemBgBaseHSV))
val item = items[position]
Picasso.with(holder.imageView.context)
.load(item.attachment.previewUrl)
2017-11-06 08:32:36 +11:00
.into(holder.imageView)
}
inner class MediaViewHolder(val imageView: ImageView)
: RecyclerView.ViewHolder(imageView),
View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
// saving some allocations
override fun onClick(v: View?) {
viewMedia(items, adapterPosition, imageView)
}
}
}
}