mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-02 09:46:51 +01:00
moved translation settings
This commit is contained in:
parent
8c8b3e86c2
commit
45a65d099f
@ -1,223 +0,0 @@
|
|||||||
/*
|
|
||||||
* Twidere - Twitter client for Android
|
|
||||||
*
|
|
||||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.mariotaku.twidere.preference;
|
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.DialogInterface.OnCancelListener;
|
|
||||||
import android.content.DialogInterface.OnClickListener;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
import android.support.v7.preference.Preference;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.mariotaku.microblog.library.MicroBlog;
|
|
||||||
import org.mariotaku.microblog.library.MicroBlogException;
|
|
||||||
import org.mariotaku.microblog.library.twitter.model.Language;
|
|
||||||
import org.mariotaku.microblog.library.twitter.model.ResponseList;
|
|
||||||
import org.mariotaku.twidere.R;
|
|
||||||
import org.mariotaku.twidere.extension.DialogExtensionsKt;
|
|
||||||
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
|
|
||||||
|
|
||||||
import java.text.Collator;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.mariotaku.twidere.TwidereConstants.LOGTAG;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This will be removed soon, target language selection will be changed inside
|
|
||||||
* translation UI.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class TranslationDestinationPreference extends Preference implements OnClickListener {
|
|
||||||
|
|
||||||
private String mSelectedLanguageCode = "en";
|
|
||||||
|
|
||||||
private GetLanguagesTask mGetAvailableTrendsTask;
|
|
||||||
|
|
||||||
private final LanguagesAdapter mAdapter;
|
|
||||||
|
|
||||||
private AlertDialog mDialog;
|
|
||||||
|
|
||||||
public TranslationDestinationPreference(final Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TranslationDestinationPreference(final Context context, final AttributeSet attrs) {
|
|
||||||
this(context, attrs, R.attr.preferenceStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TranslationDestinationPreference(final Context context, final AttributeSet attrs, final int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
mAdapter = new LanguagesAdapter(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(final DialogInterface dialog, final int which) {
|
|
||||||
final Language item = mAdapter.getItem(which);
|
|
||||||
if (item != null) {
|
|
||||||
persistString(item.getCode());
|
|
||||||
}
|
|
||||||
if (mDialog != null && mDialog.isShowing()) {
|
|
||||||
mDialog.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onClick() {
|
|
||||||
if (mGetAvailableTrendsTask != null) {
|
|
||||||
mGetAvailableTrendsTask.cancel(false);
|
|
||||||
}
|
|
||||||
mGetAvailableTrendsTask = new GetLanguagesTask(getContext());
|
|
||||||
mGetAvailableTrendsTask.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LanguageComparator implements Comparator<Language> {
|
|
||||||
private final Collator mCollator;
|
|
||||||
|
|
||||||
LanguageComparator(final Context context) {
|
|
||||||
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(final Language object1, final Language object2) {
|
|
||||||
return mCollator.compare(object1.getName(), object2.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LanguagesAdapter extends ArrayAdapter<Language> {
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
|
|
||||||
public LanguagesAdapter(final Context context) {
|
|
||||||
super(context, android.R.layout.simple_list_item_single_choice);
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int findItemPosition(final String code) {
|
|
||||||
if (TextUtils.isEmpty(code)) return -1;
|
|
||||||
final int count = getCount();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
final Language item = getItem(i);
|
|
||||||
if (code.equalsIgnoreCase(item.getCode())) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public View getView(final int position, final View convertView, @NonNull final ViewGroup parent) {
|
|
||||||
final View view = super.getView(position, convertView, parent);
|
|
||||||
final TextView text = (TextView) (view instanceof TextView ? view : view.findViewById(android.R.id.text1));
|
|
||||||
final Language item = getItem(position);
|
|
||||||
if (item != null && text != null) {
|
|
||||||
text.setSingleLine();
|
|
||||||
text.setText(item.getName());
|
|
||||||
}
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setData(final List<Language> data) {
|
|
||||||
clear();
|
|
||||||
if (data != null) {
|
|
||||||
addAll(data);
|
|
||||||
}
|
|
||||||
sort(new LanguageComparator(mContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class GetLanguagesTask extends AsyncTask<Object, Object, ResponseList<Language>> implements OnCancelListener {
|
|
||||||
|
|
||||||
private final ProgressDialog mProgress;
|
|
||||||
|
|
||||||
public GetLanguagesTask(final Context context) {
|
|
||||||
mProgress = new ProgressDialog(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel(final DialogInterface dialog) {
|
|
||||||
cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ResponseList<Language> doInBackground(final Object... args) {
|
|
||||||
final MicroBlog twitter = MicroBlogAPIFactory.getDefaultTwitterInstance(getContext());
|
|
||||||
if (twitter == null) return null;
|
|
||||||
try {
|
|
||||||
mSelectedLanguageCode = twitter.getAccountSettings().getLanguage();
|
|
||||||
return twitter.getLanguages();
|
|
||||||
} catch (final MicroBlogException e) {
|
|
||||||
Log.w(LOGTAG, e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(final ResponseList<Language> result) {
|
|
||||||
if (mProgress.isShowing()) {
|
|
||||||
mProgress.dismiss();
|
|
||||||
}
|
|
||||||
mAdapter.setData(result);
|
|
||||||
if (result == null) return;
|
|
||||||
final AlertDialog.Builder selectorBuilder = new AlertDialog.Builder(getContext());
|
|
||||||
selectorBuilder.setTitle(getTitle());
|
|
||||||
final String value = getPersistedString(mSelectedLanguageCode);
|
|
||||||
selectorBuilder.setSingleChoiceItems(mAdapter, mAdapter.findItemPosition(value),
|
|
||||||
TranslationDestinationPreference.this);
|
|
||||||
selectorBuilder.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
mDialog = selectorBuilder.create();
|
|
||||||
mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
|
||||||
@Override
|
|
||||||
public void onShow(final DialogInterface dialog) {
|
|
||||||
final AlertDialog alertDialog = (AlertDialog) dialog;
|
|
||||||
DialogExtensionsKt.applyTheme(alertDialog);
|
|
||||||
final ListView lv = alertDialog.getListView();
|
|
||||||
if (lv != null) {
|
|
||||||
lv.setFastScrollEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mDialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
if (mProgress.isShowing()) {
|
|
||||||
mProgress.dismiss();
|
|
||||||
}
|
|
||||||
mProgress.setMessage(getContext().getString(R.string.message_please_wait));
|
|
||||||
mProgress.setOnCancelListener(this);
|
|
||||||
mProgress.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -65,6 +65,7 @@ import org.mariotaku.twidere.fragment.message.MessagesConversationFragment
|
|||||||
import org.mariotaku.twidere.fragment.message.MessagesEntriesFragment
|
import org.mariotaku.twidere.fragment.message.MessagesEntriesFragment
|
||||||
import org.mariotaku.twidere.fragment.search.MastodonSearchFragment
|
import org.mariotaku.twidere.fragment.search.MastodonSearchFragment
|
||||||
import org.mariotaku.twidere.fragment.search.SearchFragment
|
import org.mariotaku.twidere.fragment.search.SearchFragment
|
||||||
|
import org.mariotaku.twidere.fragment.status.StatusFragment
|
||||||
import org.mariotaku.twidere.fragment.statuses.*
|
import org.mariotaku.twidere.fragment.statuses.*
|
||||||
import org.mariotaku.twidere.fragment.users.*
|
import org.mariotaku.twidere.fragment.users.*
|
||||||
import org.mariotaku.twidere.graphic.ActionBarColorDrawable
|
import org.mariotaku.twidere.graphic.ActionBarColorDrawable
|
||||||
@ -167,7 +168,7 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowInsetsCallback, IControl
|
|||||||
ft.commit()
|
ft.commit()
|
||||||
}
|
}
|
||||||
setTitle(linkId, uri)
|
setTitle(linkId, uri)
|
||||||
finishOnly = uri.getQueryParameter(QUERY_PARAM_FINISH_ONLY)?.toBoolean() ?: false
|
finishOnly = uri.getQueryParameter(QUERY_PARAM_FINISH_ONLY)?.toBoolean() == true
|
||||||
|
|
||||||
supportActionBar?.setBackgroundDrawable(ActionBarColorDrawable.create(overrideTheme.colorToolbar,
|
supportActionBar?.setBackgroundDrawable(ActionBarColorDrawable.create(overrideTheme.colorToolbar,
|
||||||
true))
|
true))
|
||||||
@ -913,8 +914,8 @@ class LinkHandlerActivity : BaseActivity(), SystemWindowInsetsCallback, IControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface HideUiOnScroll
|
interface HideUiOnScroll
|
||||||
|
|
||||||
private fun Uri.getUserKeyQueryParameter() : UserKey? {
|
private fun Uri.getUserKeyQueryParameter(): UserKey? {
|
||||||
val value = getQueryParameter(QUERY_PARAM_USER_KEY) ?: getQueryParameter(QUERY_PARAM_USER_ID)
|
val value = getQueryParameter(QUERY_PARAM_USER_KEY) ?: getQueryParameter(QUERY_PARAM_USER_ID)
|
||||||
return value?.let(UserKey::valueOf)
|
return value?.let(UserKey::valueOf)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,548 @@
|
|||||||
|
/*
|
||||||
|
* Twidere - Twitter client for Android
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.mariotaku.twidere.adapter
|
||||||
|
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.util.SparseBooleanArray
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Space
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import org.mariotaku.kpreferences.get
|
||||||
|
import org.mariotaku.ktextension.contains
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.TranslationResult
|
||||||
|
import org.mariotaku.twidere.R
|
||||||
|
import org.mariotaku.twidere.adapter.iface.IGapSupportedAdapter
|
||||||
|
import org.mariotaku.twidere.adapter.iface.IItemCountsAdapter
|
||||||
|
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
|
||||||
|
import org.mariotaku.twidere.adapter.iface.IStatusesAdapter
|
||||||
|
import org.mariotaku.twidere.constant.*
|
||||||
|
import org.mariotaku.twidere.extension.model.originalId
|
||||||
|
import org.mariotaku.twidere.fragment.status.StatusFragment
|
||||||
|
import org.mariotaku.twidere.model.*
|
||||||
|
import org.mariotaku.twidere.util.StatusAdapterLinkClickHandler
|
||||||
|
import org.mariotaku.twidere.util.ThemeUtils
|
||||||
|
import org.mariotaku.twidere.util.TwidereLinkify
|
||||||
|
import org.mariotaku.twidere.view.holder.EmptyViewHolder
|
||||||
|
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder
|
||||||
|
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
|
||||||
|
import org.mariotaku.twidere.view.holder.status.DetailStatusViewHolder
|
||||||
|
|
||||||
|
class StatusDetailsAdapter(
|
||||||
|
val fragment: StatusFragment
|
||||||
|
) : LoadMoreSupportAdapter<RecyclerView.ViewHolder>(fragment.context, Glide.with(fragment)),
|
||||||
|
IStatusesAdapter<List<ParcelableStatus>>, IItemCountsAdapter {
|
||||||
|
|
||||||
|
override val twidereLinkify: TwidereLinkify
|
||||||
|
|
||||||
|
override var statusClickListener: IStatusViewHolder.StatusClickListener? = null
|
||||||
|
|
||||||
|
override val itemCounts = ItemCounts(ITEM_TYPES_SUM)
|
||||||
|
|
||||||
|
override val nameFirst = preferences[nameFirstKey]
|
||||||
|
override val mediaPreviewStyle = preferences[mediaPreviewStyleKey]
|
||||||
|
override val linkHighlightingStyle = preferences[linkHighlightOptionKey]
|
||||||
|
override val lightFont = preferences[lightFontKey]
|
||||||
|
override val mediaPreviewEnabled = preferences[mediaPreviewKey]
|
||||||
|
override val sensitiveContentEnabled = preferences[displaySensitiveContentsKey]
|
||||||
|
override val useStarsForLikes = preferences[iWantMyStarsBackKey]
|
||||||
|
|
||||||
|
private val inflater: LayoutInflater
|
||||||
|
private val cardBackgroundColor: Int
|
||||||
|
private val showCardActions = !preferences[hideCardActionsKey]
|
||||||
|
private var recyclerView: RecyclerView? = null
|
||||||
|
private var detailMediaExpanded: Boolean = false
|
||||||
|
|
||||||
|
var status: ParcelableStatus? = null
|
||||||
|
internal set
|
||||||
|
var translationResult: TranslationResult? = null
|
||||||
|
internal set(translation) {
|
||||||
|
if (translation == null || status?.originalId != translation.id) {
|
||||||
|
field = null
|
||||||
|
} else {
|
||||||
|
field = translation
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
var statusActivity: StatusFragment.StatusActivity? = null
|
||||||
|
internal set(value) {
|
||||||
|
val status = status ?: return
|
||||||
|
if (value != null && !value.isStatus(status)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
field = value
|
||||||
|
val statusIndex = getIndexStart(ITEM_IDX_STATUS)
|
||||||
|
notifyItemChanged(statusIndex, value)
|
||||||
|
}
|
||||||
|
var statusAccount: AccountDetails? = null
|
||||||
|
internal set
|
||||||
|
|
||||||
|
private var data: List<ParcelableStatus>? = null
|
||||||
|
private var replyError: CharSequence? = null
|
||||||
|
private var conversationError: CharSequence? = null
|
||||||
|
private var replyStart: Int = 0
|
||||||
|
private var showingActionCardPosition = RecyclerView.NO_POSITION
|
||||||
|
private val showingFullTextStates = SparseBooleanArray()
|
||||||
|
|
||||||
|
init {
|
||||||
|
setHasStableIds(true)
|
||||||
|
val context = fragment.activity
|
||||||
|
// There's always a space at the end of the list
|
||||||
|
itemCounts[ITEM_IDX_SPACE] = 1
|
||||||
|
itemCounts[ITEM_IDX_STATUS] = 1
|
||||||
|
itemCounts[ITEM_IDX_CONVERSATION_LOAD_MORE] = 1
|
||||||
|
itemCounts[ITEM_IDX_REPLY_LOAD_MORE] = 1
|
||||||
|
inflater = LayoutInflater.from(context)
|
||||||
|
cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
|
||||||
|
preferences[themeBackgroundOptionKey], preferences[themeBackgroundAlphaKey])
|
||||||
|
val listener = StatusAdapterLinkClickHandler<List<ParcelableStatus>>(context, preferences)
|
||||||
|
listener.setAdapter(this)
|
||||||
|
twidereLinkify = TwidereLinkify(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatus(position: Int, raw: Boolean): ParcelableStatus {
|
||||||
|
when (getItemCountIndex(position, raw)) {
|
||||||
|
ITEM_IDX_CONVERSATION -> {
|
||||||
|
var idx = position - getIndexStart(ITEM_IDX_CONVERSATION)
|
||||||
|
if (data!![idx].is_filtered) idx++
|
||||||
|
return data!![idx]
|
||||||
|
}
|
||||||
|
ITEM_IDX_REPLY -> {
|
||||||
|
var idx = position - getIndexStart(ITEM_IDX_CONVERSATION) -
|
||||||
|
getTypeCount(ITEM_IDX_CONVERSATION) - getTypeCount(ITEM_IDX_STATUS) +
|
||||||
|
replyStart
|
||||||
|
if (data!![idx].is_filtered) idx++
|
||||||
|
return data!![idx]
|
||||||
|
}
|
||||||
|
ITEM_IDX_STATUS -> {
|
||||||
|
return status!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IndexOutOfBoundsException("index: $position")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIndexStart(index: Int): Int {
|
||||||
|
if (index == 0) return 0
|
||||||
|
return itemCounts.getItemStartPosition(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatusId(position: Int, raw: Boolean): String {
|
||||||
|
return getStatus(position, raw).id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatusTimestamp(position: Int, raw: Boolean): Long {
|
||||||
|
return getStatus(position, raw).timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatusPositionKey(position: Int, raw: Boolean): Long {
|
||||||
|
val status = getStatus(position, raw)
|
||||||
|
return if (status.position_key > 0) status.timestamp else getStatusTimestamp(position, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAccountKey(position: Int, raw: Boolean) = getStatus(position, raw).account_key
|
||||||
|
|
||||||
|
override fun findStatusById(accountKey: UserKey, statusId: String): ParcelableStatus? {
|
||||||
|
if (status != null && accountKey == status!!.account_key && TextUtils.equals(statusId, status!!.id)) {
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
return data?.firstOrNull { accountKey == it.account_key && TextUtils.equals(it.id, statusId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStatusCount(raw: Boolean): Int {
|
||||||
|
return getTypeCount(ITEM_IDX_CONVERSATION) + getTypeCount(ITEM_IDX_STATUS) + getTypeCount(ITEM_IDX_REPLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isCardActionsShown(position: Int): Boolean {
|
||||||
|
if (position == RecyclerView.NO_POSITION) return showCardActions
|
||||||
|
return showCardActions || showingActionCardPosition == position
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showCardActions(position: Int) {
|
||||||
|
if (showingActionCardPosition != RecyclerView.NO_POSITION) {
|
||||||
|
notifyItemChanged(showingActionCardPosition)
|
||||||
|
}
|
||||||
|
showingActionCardPosition = position
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isFullTextVisible(position: Int): Boolean {
|
||||||
|
return showingFullTextStates.get(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFullTextVisible(position: Int, visible: Boolean) {
|
||||||
|
showingFullTextStates.put(position, visible)
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setData(data: List<ParcelableStatus>?): Boolean {
|
||||||
|
val status = this.status ?: return false
|
||||||
|
val changed = this.data != data
|
||||||
|
this.data = data
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
setTypeCount(ITEM_IDX_CONVERSATION, 0)
|
||||||
|
setTypeCount(ITEM_IDX_REPLY, 0)
|
||||||
|
replyStart = -1
|
||||||
|
} else {
|
||||||
|
var sortId = status.sort_id
|
||||||
|
|
||||||
|
if (status.is_retweet) {
|
||||||
|
sortId = data.find {
|
||||||
|
it.id == status.retweet_id
|
||||||
|
}?.sort_id ?: status.retweet_timestamp
|
||||||
|
}
|
||||||
|
var conversationCount = 0
|
||||||
|
var replyCount = 0
|
||||||
|
var replyStart = -1
|
||||||
|
data.forEachIndexed { i, item ->
|
||||||
|
if (item.sort_id < sortId) {
|
||||||
|
if (!item.is_filtered) {
|
||||||
|
conversationCount++
|
||||||
|
}
|
||||||
|
} else if (status.id == item.id) {
|
||||||
|
this.status = item
|
||||||
|
} else if (item.sort_id > sortId) {
|
||||||
|
if (replyStart < 0) {
|
||||||
|
replyStart = i
|
||||||
|
}
|
||||||
|
if (!item.is_filtered) {
|
||||||
|
replyCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTypeCount(ITEM_IDX_CONVERSATION, conversationCount)
|
||||||
|
setTypeCount(ITEM_IDX_REPLY, replyCount)
|
||||||
|
this.replyStart = replyStart
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
updateItemDecoration()
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
override val showAccountsColor: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
var isDetailMediaExpanded: Boolean
|
||||||
|
get() {
|
||||||
|
if (detailMediaExpanded) return true
|
||||||
|
if (mediaPreviewEnabled) {
|
||||||
|
val status = this.status
|
||||||
|
return status != null && (sensitiveContentEnabled || !status.is_possibly_sensitive)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set(expanded) {
|
||||||
|
detailMediaExpanded = expanded
|
||||||
|
notifyDataSetChanged()
|
||||||
|
updateItemDecoration()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isGapItem(position: Int): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override val gapClickListener: IGapSupportedAdapter.GapClickListener?
|
||||||
|
get() = statusClickListener
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder? {
|
||||||
|
when (viewType) {
|
||||||
|
VIEW_TYPE_DETAIL_STATUS -> {
|
||||||
|
val view = inflater.inflate(R.layout.header_status, parent, false)
|
||||||
|
view.setBackgroundColor(cardBackgroundColor)
|
||||||
|
return DetailStatusViewHolder(this, view)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_LIST_STATUS -> {
|
||||||
|
return ListParcelableStatusesAdapter.createStatusViewHolder(this, inflater, parent)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_CONVERSATION_LOAD_INDICATOR, VIEW_TYPE_REPLIES_LOAD_INDICATOR -> {
|
||||||
|
val view = inflater.inflate(R.layout.list_item_load_indicator, parent,
|
||||||
|
false)
|
||||||
|
return LoadIndicatorViewHolder(view)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_SPACE -> {
|
||||||
|
return EmptyViewHolder(Space(context))
|
||||||
|
}
|
||||||
|
VIEW_TYPE_REPLY_ERROR -> {
|
||||||
|
val view = inflater.inflate(R.layout.adapter_item_status_error, parent,
|
||||||
|
false)
|
||||||
|
return StatusErrorItemViewHolder(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
|
||||||
|
var handled = false
|
||||||
|
when (holder.itemViewType) {
|
||||||
|
VIEW_TYPE_DETAIL_STATUS -> {
|
||||||
|
holder as DetailStatusViewHolder
|
||||||
|
payloads.forEach { it ->
|
||||||
|
when (it) {
|
||||||
|
is StatusFragment.StatusActivity -> {
|
||||||
|
holder.updateStatusActivity(it)
|
||||||
|
}
|
||||||
|
is ParcelableStatus -> {
|
||||||
|
holder.displayStatus(statusAccount, status, statusActivity,
|
||||||
|
translationResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handled) return
|
||||||
|
super.onBindViewHolder(holder, position, payloads)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder.itemViewType) {
|
||||||
|
VIEW_TYPE_DETAIL_STATUS -> {
|
||||||
|
val status = getStatus(position)
|
||||||
|
val detailHolder = holder as DetailStatusViewHolder
|
||||||
|
detailHolder.displayStatus(statusAccount, status, statusActivity, translationResult)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_LIST_STATUS -> {
|
||||||
|
val status = getStatus(position)
|
||||||
|
val statusHolder = holder as IStatusViewHolder
|
||||||
|
// Display 'in reply to' for first item
|
||||||
|
// useful to indicate whether first tweet has reply or not
|
||||||
|
// We only display that indicator for first conversation item
|
||||||
|
val itemType = getItemType(position)
|
||||||
|
val displayInReplyTo = itemType == ITEM_IDX_CONVERSATION && position - getItemTypeStart(position) == 0
|
||||||
|
statusHolder.display(status = status, displayInReplyTo = displayInReplyTo)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_REPLY_ERROR -> {
|
||||||
|
val errorHolder = holder as StatusErrorItemViewHolder
|
||||||
|
errorHolder.showError(replyError!!)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_CONVERSATION_ERROR -> {
|
||||||
|
val errorHolder = holder as StatusErrorItemViewHolder
|
||||||
|
errorHolder.showError(conversationError!!)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_CONVERSATION_LOAD_INDICATOR -> {
|
||||||
|
val indicatorHolder = holder as LoadIndicatorViewHolder
|
||||||
|
indicatorHolder.setLoadProgressVisible(isConversationsLoading)
|
||||||
|
}
|
||||||
|
VIEW_TYPE_REPLIES_LOAD_INDICATOR -> {
|
||||||
|
val indicatorHolder = holder as LoadIndicatorViewHolder
|
||||||
|
indicatorHolder.setLoadProgressVisible(isRepliesLoading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return getItemViewTypeByItemType(getItemType(position))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addGapLoadingId(id: ObjectId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeGapLoadingId(id: ObjectId) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItemViewTypeByItemType(type: Int): Int {
|
||||||
|
when (type) {
|
||||||
|
ITEM_IDX_CONVERSATION, ITEM_IDX_REPLY -> return VIEW_TYPE_LIST_STATUS
|
||||||
|
ITEM_IDX_CONVERSATION_LOAD_MORE -> return VIEW_TYPE_CONVERSATION_LOAD_INDICATOR
|
||||||
|
ITEM_IDX_REPLY_LOAD_MORE -> return VIEW_TYPE_REPLIES_LOAD_INDICATOR
|
||||||
|
ITEM_IDX_STATUS -> return VIEW_TYPE_DETAIL_STATUS
|
||||||
|
ITEM_IDX_SPACE -> return VIEW_TYPE_SPACE
|
||||||
|
ITEM_IDX_REPLY_ERROR -> return VIEW_TYPE_REPLY_ERROR
|
||||||
|
ITEM_IDX_CONVERSATION_ERROR -> return VIEW_TYPE_CONVERSATION_ERROR
|
||||||
|
}
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItemCountIndex(position: Int, raw: Boolean): Int {
|
||||||
|
return itemCounts.getItemCountIndex(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItemType(position: Int): Int {
|
||||||
|
var typeStart = 0
|
||||||
|
for (type in 0 until ITEM_TYPES_SUM) {
|
||||||
|
val typeCount = getTypeCount(type)
|
||||||
|
val typeEnd = typeStart + typeCount
|
||||||
|
if (position in typeStart until typeEnd) return type
|
||||||
|
typeStart = typeEnd
|
||||||
|
}
|
||||||
|
throw IllegalStateException("Unknown position " + position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItemTypeStart(position: Int): Int {
|
||||||
|
var typeStart = 0
|
||||||
|
for (type in 0 until ITEM_TYPES_SUM) {
|
||||||
|
val typeCount = getTypeCount(type)
|
||||||
|
val typeEnd = typeStart + typeCount
|
||||||
|
if (position in typeStart until typeEnd) return typeStart
|
||||||
|
typeStart = typeEnd
|
||||||
|
}
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
val countIndex = getItemCountIndex(position)
|
||||||
|
when (countIndex) {
|
||||||
|
ITEM_IDX_CONVERSATION, ITEM_IDX_STATUS, ITEM_IDX_REPLY -> {
|
||||||
|
val status = getStatus(position)
|
||||||
|
val hashCode = ParcelableStatus.calculateHashCode(status.account_key, status.id)
|
||||||
|
return (countIndex.toLong() shl 32) or hashCode.toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val countPos = (position - getItemStartPosition(countIndex)).toLong()
|
||||||
|
return (countIndex.toLong() shl 32) or countPos
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
if (status == null) return 0
|
||||||
|
return itemCounts.itemCount
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView?) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
this.recyclerView = recyclerView
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView?) {
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
this.recyclerView = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTypeCount(idx: Int, size: Int) {
|
||||||
|
itemCounts[idx] = size
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTypeCount(idx: Int): Int {
|
||||||
|
return itemCounts[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setReplyError(error: CharSequence?) {
|
||||||
|
replyError = error
|
||||||
|
setTypeCount(ITEM_IDX_REPLY_ERROR, if (error != null) 1 else 0)
|
||||||
|
updateItemDecoration()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setConversationError(error: CharSequence?) {
|
||||||
|
conversationError = error
|
||||||
|
setTypeCount(ITEM_IDX_CONVERSATION_ERROR, if (error != null) 1 else 0)
|
||||||
|
updateItemDecoration()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setStatus(status: ParcelableStatus, account: AccountDetails?): Boolean {
|
||||||
|
val oldStatus = this.status
|
||||||
|
val oldAccount = this.statusAccount
|
||||||
|
val changed = oldStatus != status && oldAccount != account
|
||||||
|
this.status = status
|
||||||
|
this.statusAccount = account
|
||||||
|
if (changed) {
|
||||||
|
notifyDataSetChanged()
|
||||||
|
updateItemDecoration()
|
||||||
|
} else {
|
||||||
|
val statusIndex = getIndexStart(ITEM_IDX_STATUS)
|
||||||
|
notifyItemChanged(statusIndex, status)
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateItemDecoration() {
|
||||||
|
if (recyclerView == null) return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFirstPositionOfItem(itemIdx: Int): Int {
|
||||||
|
var position = 0
|
||||||
|
for (i in 0 until ITEM_TYPES_SUM) {
|
||||||
|
if (itemIdx == i) return position
|
||||||
|
position += getTypeCount(i)
|
||||||
|
}
|
||||||
|
return RecyclerView.NO_POSITION
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getData(): List<ParcelableStatus>? {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
var isConversationsLoading: Boolean
|
||||||
|
get() = ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition
|
||||||
|
set(loading) {
|
||||||
|
if (loading) {
|
||||||
|
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.START
|
||||||
|
} else {
|
||||||
|
loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START.inv()
|
||||||
|
}
|
||||||
|
updateItemDecoration()
|
||||||
|
}
|
||||||
|
|
||||||
|
var isRepliesLoading: Boolean
|
||||||
|
get() = ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition
|
||||||
|
set(loading) {
|
||||||
|
if (loading) {
|
||||||
|
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.END
|
||||||
|
} else {
|
||||||
|
loadMoreIndicatorPosition = loadMoreIndicatorPosition and ILoadMoreSupportAdapter.END.inv()
|
||||||
|
}
|
||||||
|
updateItemDecoration()
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusErrorItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
private val textView = itemView.findViewById<TextView>(android.R.id.text1)
|
||||||
|
|
||||||
|
init {
|
||||||
|
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
textView.linksClickable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showError(text: CharSequence) {
|
||||||
|
textView.text = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val VIEW_TYPE_LIST_STATUS = 0
|
||||||
|
const val VIEW_TYPE_DETAIL_STATUS = 1
|
||||||
|
const val VIEW_TYPE_CONVERSATION_LOAD_INDICATOR = 2
|
||||||
|
const val VIEW_TYPE_REPLIES_LOAD_INDICATOR = 3
|
||||||
|
const val VIEW_TYPE_REPLY_ERROR = 4
|
||||||
|
const val VIEW_TYPE_CONVERSATION_ERROR = 5
|
||||||
|
const val VIEW_TYPE_SPACE = 6
|
||||||
|
const val VIEW_TYPE_EMPTY = 7
|
||||||
|
|
||||||
|
const val ITEM_IDX_CONVERSATION_LOAD_MORE = 0
|
||||||
|
const val ITEM_IDX_CONVERSATION_ERROR = 1
|
||||||
|
const val ITEM_IDX_CONVERSATION = 2
|
||||||
|
const val ITEM_IDX_STATUS = 3
|
||||||
|
const val ITEM_IDX_REPLY = 4
|
||||||
|
const val ITEM_IDX_REPLY_ERROR = 5
|
||||||
|
const val ITEM_IDX_REPLY_LOAD_MORE = 6
|
||||||
|
const val ITEM_IDX_SPACE = 7
|
||||||
|
const val ITEM_TYPES_SUM = 8
|
||||||
|
}
|
||||||
|
}
|
@ -87,6 +87,7 @@ val composeStatusVisibilityKey = KNullableStringKey("compose_status_visibility",
|
|||||||
val navbarStyleKey = KStringKey(KEY_NAVBAR_STYLE, NavbarStyle.DEFAULT)
|
val navbarStyleKey = KStringKey(KEY_NAVBAR_STYLE, NavbarStyle.DEFAULT)
|
||||||
val lastLaunchTimeKey = KLongKey("last_launch_time", -1)
|
val lastLaunchTimeKey = KLongKey("last_launch_time", -1)
|
||||||
val promotionsEnabledKey = KBooleanKey("promotions_enabled", false)
|
val promotionsEnabledKey = KBooleanKey("promotions_enabled", false)
|
||||||
|
val translationDestinationKey = KNullableStringKey(KEY_TRANSLATION_DESTINATION, null)
|
||||||
|
|
||||||
object cacheSizeLimitKey : KSimpleKey<Int>(KEY_CACHE_SIZE_LIMIT, 300) {
|
object cacheSizeLimitKey : KSimpleKey<Int>(KEY_CACHE_SIZE_LIMIT, 300) {
|
||||||
override fun read(preferences: SharedPreferences) = preferences.getInt(key, def).coerceIn(100,
|
override fun read(preferences: SharedPreferences) = preferences.getInt(key, def).coerceIn(100,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,960 @@
|
|||||||
|
/*
|
||||||
|
* Twidere - Twitter client for Android
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.mariotaku.twidere.fragment.status
|
||||||
|
|
||||||
|
import android.accounts.AccountManager
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.nfc.NdefMessage
|
||||||
|
import android.nfc.NdefRecord
|
||||||
|
import android.nfc.NfcAdapter.CreateNdefMessageCallback
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.LoaderManager.LoaderCallbacks
|
||||||
|
import android.support.v4.app.hasRunningLoadersSafe
|
||||||
|
import android.support.v4.content.FixedAsyncTaskLoader
|
||||||
|
import android.support.v4.content.Loader
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import android.support.v7.widget.FixedLinearLayoutManager
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.support.v7.widget.RecyclerView.ViewHolder
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.*
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.squareup.otto.Subscribe
|
||||||
|
import kotlinx.android.synthetic.main.fragment_status.*
|
||||||
|
import kotlinx.android.synthetic.main.layout_content_fragment_common.*
|
||||||
|
import nl.komponents.kovenant.combine.and
|
||||||
|
import nl.komponents.kovenant.task
|
||||||
|
import nl.komponents.kovenant.ui.alwaysUi
|
||||||
|
import nl.komponents.kovenant.ui.successUi
|
||||||
|
import org.mariotaku.abstask.library.TaskStarter
|
||||||
|
import org.mariotaku.kpreferences.get
|
||||||
|
import org.mariotaku.ktextension.*
|
||||||
|
import org.mariotaku.library.objectcursor.ObjectCursor
|
||||||
|
import org.mariotaku.microblog.library.MicroBlog
|
||||||
|
import org.mariotaku.microblog.library.MicroBlogException
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.Paging
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.TranslationResult
|
||||||
|
import org.mariotaku.sqliteqb.library.Expression
|
||||||
|
import org.mariotaku.twidere.Constants.*
|
||||||
|
import org.mariotaku.twidere.R
|
||||||
|
import org.mariotaku.twidere.activity.ColorPickerDialogActivity
|
||||||
|
import org.mariotaku.twidere.adapter.StatusDetailsAdapter
|
||||||
|
import org.mariotaku.twidere.adapter.decorator.ExtendedDividerItemDecoration
|
||||||
|
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
|
||||||
|
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition
|
||||||
|
import org.mariotaku.twidere.annotation.AccountType
|
||||||
|
import org.mariotaku.twidere.constant.KeyboardShortcutConstants.*
|
||||||
|
import org.mariotaku.twidere.constant.displaySensitiveContentsKey
|
||||||
|
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||||
|
import org.mariotaku.twidere.extension.*
|
||||||
|
import org.mariotaku.twidere.extension.model.api.key
|
||||||
|
import org.mariotaku.twidere.extension.model.api.toParcelable
|
||||||
|
import org.mariotaku.twidere.extension.model.getAccountType
|
||||||
|
import org.mariotaku.twidere.extension.model.media_type
|
||||||
|
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
||||||
|
import org.mariotaku.twidere.extension.model.originalId
|
||||||
|
import org.mariotaku.twidere.extension.view.calculateSpaceItemHeight
|
||||||
|
import org.mariotaku.twidere.fragment.AbsStatusesFragment
|
||||||
|
import org.mariotaku.twidere.fragment.AbsStatusesFragment.Companion.handleActionClick
|
||||||
|
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||||
|
import org.mariotaku.twidere.fragment.BaseFragment
|
||||||
|
import org.mariotaku.twidere.fragment.status.TranslationDestinationDialogFragment
|
||||||
|
import org.mariotaku.twidere.loader.ParcelableStatusLoader
|
||||||
|
import org.mariotaku.twidere.loader.statuses.ConversationLoader
|
||||||
|
import org.mariotaku.twidere.model.*
|
||||||
|
import org.mariotaku.twidere.model.analyzer.Share
|
||||||
|
import org.mariotaku.twidere.model.analyzer.StatusView
|
||||||
|
import org.mariotaku.twidere.model.event.FavoriteTaskEvent
|
||||||
|
import org.mariotaku.twidere.model.event.StatusListChangedEvent
|
||||||
|
import org.mariotaku.twidere.model.pagination.Pagination
|
||||||
|
import org.mariotaku.twidere.model.pagination.SinceMaxPagination
|
||||||
|
import org.mariotaku.twidere.model.util.AccountUtils
|
||||||
|
import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses
|
||||||
|
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||||
|
import org.mariotaku.twidere.task.AbsAccountRequestTask
|
||||||
|
import org.mariotaku.twidere.util.*
|
||||||
|
import org.mariotaku.twidere.util.ContentScrollHandler.ContentListSupport
|
||||||
|
import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback
|
||||||
|
import org.mariotaku.twidere.util.RecyclerViewScrollHandler.RecyclerViewCallback
|
||||||
|
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener
|
||||||
|
import org.mariotaku.twidere.view.ExtendedRecyclerView
|
||||||
|
import org.mariotaku.twidere.view.holder.GapViewHolder
|
||||||
|
import org.mariotaku.twidere.view.holder.StatusViewHolder
|
||||||
|
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder
|
||||||
|
import org.mariotaku.twidere.view.holder.iface.IStatusViewHolder.StatusClickListener
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays status details
|
||||||
|
* Created by mariotaku on 14/12/5.
|
||||||
|
*/
|
||||||
|
class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<ParcelableStatus>>,
|
||||||
|
OnMediaClickListener, StatusClickListener, KeyboardShortcutCallback,
|
||||||
|
ContentListSupport<StatusDetailsAdapter> {
|
||||||
|
private var mItemDecoration: ExtendedDividerItemDecoration? = null
|
||||||
|
|
||||||
|
override lateinit var adapter: StatusDetailsAdapter
|
||||||
|
|
||||||
|
private lateinit var layoutManager: LinearLayoutManager
|
||||||
|
private lateinit var navigationHelper: RecyclerViewNavigationHelper
|
||||||
|
private lateinit var scrollListener: RecyclerViewScrollHandler<StatusDetailsAdapter>
|
||||||
|
|
||||||
|
private var loadTranslationTask: LoadTranslationTask? = null
|
||||||
|
// Data fields
|
||||||
|
private var conversationLoaderInitialized: Boolean = false
|
||||||
|
|
||||||
|
private var activityLoaderInitialized: Boolean = false
|
||||||
|
private var hasMoreConversation = true
|
||||||
|
|
||||||
|
// Listeners
|
||||||
|
private val conversationsLoaderCallback = object : LoaderCallbacks<List<ParcelableStatus>> {
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle): Loader<List<ParcelableStatus>> {
|
||||||
|
val adapter = this@StatusFragment.adapter
|
||||||
|
adapter.isRepliesLoading = true
|
||||||
|
adapter.isConversationsLoading = true
|
||||||
|
adapter.updateItemDecoration()
|
||||||
|
val status: ParcelableStatus = args.getParcelable(EXTRA_STATUS)
|
||||||
|
val loadingMore = args.getBoolean(EXTRA_LOADING_MORE, false)
|
||||||
|
return ConversationLoader(activity, status, adapter.getData(), true, loadingMore).apply {
|
||||||
|
pagination = args.toPagination()
|
||||||
|
// Setting comparator to null lets statuses sort ascending
|
||||||
|
comparator = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<List<ParcelableStatus>>, data: List<ParcelableStatus>?) {
|
||||||
|
val adapter = this@StatusFragment.adapter
|
||||||
|
adapter.updateItemDecoration()
|
||||||
|
val conversationLoader = loader as ConversationLoader
|
||||||
|
var supportedPositions: Long = 0
|
||||||
|
if (data != null && !data.isEmpty()) {
|
||||||
|
val sinceSortId = (conversationLoader.pagination as? SinceMaxPagination)?.sinceSortId ?: -1
|
||||||
|
if (sinceSortId < data[data.size - 1].sort_id) {
|
||||||
|
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.END
|
||||||
|
}
|
||||||
|
if (data[0].in_reply_to_status_id != null) {
|
||||||
|
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.START
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.END
|
||||||
|
val status = status
|
||||||
|
if (status?.in_reply_to_status_id != null) {
|
||||||
|
supportedPositions = supportedPositions or ILoadMoreSupportAdapter.START
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.loadMoreSupportedPosition = supportedPositions
|
||||||
|
setConversation(data)
|
||||||
|
adapter.isConversationsLoading = false
|
||||||
|
adapter.isRepliesLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<List<ParcelableStatus>>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val statusActivityLoaderCallback = object : LoaderCallbacks<StatusActivity?> {
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle): Loader<StatusActivity?> {
|
||||||
|
val accountKey = args.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
|
||||||
|
val statusId = args.getString(EXTRA_STATUS_ID)
|
||||||
|
return StatusActivitySummaryLoader(activity, accountKey, statusId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<StatusActivity?>, data: StatusActivity?) {
|
||||||
|
adapter.updateItemDecoration()
|
||||||
|
adapter.statusActivity = data
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<StatusActivity?>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
val activity = activity ?: return
|
||||||
|
when (requestCode) {
|
||||||
|
REQUEST_SET_COLOR -> {
|
||||||
|
val status = adapter.status ?: return
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
if (data == null) return
|
||||||
|
val color = data.getIntExtra(EXTRA_COLOR, Color.TRANSPARENT)
|
||||||
|
userColorNameManager.setUserColor(status.user_key, color)
|
||||||
|
} else if (resultCode == ColorPickerDialogActivity.RESULT_CLEARED) {
|
||||||
|
userColorNameManager.clearUserColor(status.user_key)
|
||||||
|
}
|
||||||
|
val args = arguments
|
||||||
|
if (args.containsKey(EXTRA_STATUS)) {
|
||||||
|
args.putParcelable(EXTRA_STATUS, status)
|
||||||
|
}
|
||||||
|
loaderManager.restartLoader(LOADER_ID_DETAIL_STATUS, args, this)
|
||||||
|
}
|
||||||
|
REQUEST_SELECT_ACCOUNT -> {
|
||||||
|
val status = adapter.status ?: return
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
if (data == null || !data.hasExtra(EXTRA_ID)) return
|
||||||
|
val accountKey = data.getParcelableExtra<UserKey>(EXTRA_ACCOUNT_KEY)
|
||||||
|
IntentUtils.openStatus(activity, accountKey, status.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AbsStatusesFragment.REQUEST_FAVORITE_SELECT_ACCOUNT,
|
||||||
|
AbsStatusesFragment.REQUEST_RETWEET_SELECT_ACCOUNT -> {
|
||||||
|
AbsStatusesFragment.handleActionActivityResult(this, requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_status, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
Utils.setNdefPushMessageCallback(activity, CreateNdefMessageCallback {
|
||||||
|
val status = status ?: return@CreateNdefMessageCallback null
|
||||||
|
NdefMessage(arrayOf(NdefRecord.createUri(LinkCreator.getStatusWebLink(status))))
|
||||||
|
})
|
||||||
|
adapter = StatusDetailsAdapter(this)
|
||||||
|
layoutManager = StatusListLinearLayoutManager(context, recyclerView)
|
||||||
|
mItemDecoration = StatusDividerItemDecoration(context, adapter, layoutManager.orientation)
|
||||||
|
recyclerView.addItemDecoration(mItemDecoration)
|
||||||
|
layoutManager.recycleChildrenOnDetach = true
|
||||||
|
recyclerView.layoutManager = layoutManager
|
||||||
|
recyclerView.clipToPadding = false
|
||||||
|
adapter.statusClickListener = this
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
registerForContextMenu(recyclerView)
|
||||||
|
|
||||||
|
scrollListener = RecyclerViewScrollHandler(this, RecyclerViewCallback(recyclerView))
|
||||||
|
scrollListener.touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
|
||||||
|
navigationHelper = RecyclerViewNavigationHelper(recyclerView, layoutManager,
|
||||||
|
adapter, null)
|
||||||
|
|
||||||
|
setState(STATE_LOADING)
|
||||||
|
|
||||||
|
loaderManager.initLoader(LOADER_ID_DETAIL_STATUS, arguments, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia, statusPosition: Int) {
|
||||||
|
val status = adapter.getStatus(statusPosition)
|
||||||
|
IntentUtils.openMedia(activity, status, current, preferences[newDocumentApiKey],
|
||||||
|
preferences[displaySensitiveContentsKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuotedMediaClick(holder: IStatusViewHolder, view: View, current: ParcelableMedia, statusPosition: Int) {
|
||||||
|
val status = adapter.getStatus(statusPosition)
|
||||||
|
val quotedMedia = status.quoted_media ?: return
|
||||||
|
IntentUtils.openMedia(activity, status.account_key, status.is_possibly_sensitive, status,
|
||||||
|
current, quotedMedia, preferences[newDocumentApiKey],
|
||||||
|
preferences[displaySensitiveContentsKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGapClick(holder: GapViewHolder, position: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemActionClick(holder: ViewHolder, id: Int, position: Int) {
|
||||||
|
val status = adapter.getStatus(position)
|
||||||
|
handleActionClick(this@StatusFragment, id, status, holder as StatusViewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onItemActionLongClick(holder: RecyclerView.ViewHolder, id: Int, position: Int): Boolean {
|
||||||
|
val status = adapter.getStatus(position)
|
||||||
|
return AbsStatusesFragment.handleActionLongClick(this, status, adapter.getItemId(position), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusClick(holder: IStatusViewHolder, position: Int) {
|
||||||
|
val status = adapter.getStatus(position)
|
||||||
|
IntentUtils.openStatus(activity, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuotedStatusClick(holder: IStatusViewHolder, position: Int) {
|
||||||
|
val status = adapter.getStatus(position)
|
||||||
|
val quotedId = status.quoted_id ?: return
|
||||||
|
IntentUtils.openStatus(activity, status.account_key, quotedId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusLongClick(holder: IStatusViewHolder, position: Int): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemMenuClick(holder: ViewHolder, menuView: View, position: Int) {
|
||||||
|
if (activity == null) return
|
||||||
|
val view = layoutManager.findViewByPosition(position) ?: return
|
||||||
|
recyclerView.showContextMenuForChild(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserProfileClick(holder: IStatusViewHolder, position: Int) {
|
||||||
|
val status = adapter.getStatus(position)
|
||||||
|
IntentUtils.openUserProfile(activity, status.account_key, status.user_key,
|
||||||
|
status.user_screen_name, status.extras?.user_statusnet_profile_url,
|
||||||
|
preferences[newDocumentApiKey], null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaClick(view: View, current: ParcelableMedia, accountKey: UserKey?, id: Long) {
|
||||||
|
val status = adapter.status ?: return
|
||||||
|
if ((view.parent as View).id == R.id.quotedMediaPreview && status.quoted_media != null) {
|
||||||
|
IntentUtils.openMediaDirectly(activity, accountKey, status.quoted_media!!, current,
|
||||||
|
newDocument = preferences[newDocumentApiKey], status = status)
|
||||||
|
} else if (status.media != null) {
|
||||||
|
IntentUtils.openMediaDirectly(activity, accountKey, status.media!!, current,
|
||||||
|
newDocument = preferences[newDocumentApiKey], status = status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler,
|
||||||
|
keyCode: Int, event: KeyEvent,
|
||||||
|
metaState: Int): Boolean {
|
||||||
|
if (!KeyboardShortcutsHandler.isValidForHotkey(keyCode, event)) return false
|
||||||
|
val focusedChild = RecyclerViewUtils.findRecyclerViewChild(recyclerView, layoutManager.focusedChild)
|
||||||
|
val position: Int
|
||||||
|
if (focusedChild != null && focusedChild.parent === recyclerView) {
|
||||||
|
position = recyclerView.getChildLayoutPosition(focusedChild)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (position == -1) return false
|
||||||
|
val status = adapter.getStatus(position)
|
||||||
|
val action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState) ?: return false
|
||||||
|
return AbsStatusesFragment.handleKeyboardShortcutAction(this, action, status, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean {
|
||||||
|
val action = handler.getKeyAction(CONTEXT_TAG_STATUS, keyCode, event, metaState) ?: return false
|
||||||
|
when (action) {
|
||||||
|
ACTION_STATUS_REPLY, ACTION_STATUS_RETWEET, ACTION_STATUS_FAVORITE -> return true
|
||||||
|
}
|
||||||
|
return navigationHelper.isKeyboardShortcutHandled(handler, keyCode, event, metaState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler,
|
||||||
|
keyCode: Int, repeatCount: Int,
|
||||||
|
event: KeyEvent, metaState: Int): Boolean {
|
||||||
|
return navigationHelper.handleKeyboardShortcutRepeat(handler, keyCode,
|
||||||
|
repeatCount, event, metaState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateLoader(id: Int, args: Bundle): Loader<SingleResponse<ParcelableStatus>> {
|
||||||
|
val fragmentArgs = arguments
|
||||||
|
val accountKey = fragmentArgs.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
|
||||||
|
val statusId = fragmentArgs.getString(EXTRA_STATUS_ID)
|
||||||
|
return ParcelableStatusLoader(activity, false, fragmentArgs, accountKey, statusId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onLoadFinished(loader: Loader<SingleResponse<ParcelableStatus>>,
|
||||||
|
data: SingleResponse<ParcelableStatus>) {
|
||||||
|
val activity = activity ?: return
|
||||||
|
val status = data.data
|
||||||
|
if (status != null) {
|
||||||
|
val readPosition = saveReadPosition()
|
||||||
|
val dataExtra = data.extras
|
||||||
|
val details: AccountDetails? = dataExtra.getParcelable(EXTRA_ACCOUNT)
|
||||||
|
if (adapter.setStatus(status, details)) {
|
||||||
|
val args = arguments
|
||||||
|
if (args.containsKey(EXTRA_STATUS)) {
|
||||||
|
args.putParcelable(EXTRA_STATUS, status)
|
||||||
|
}
|
||||||
|
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.BOTH
|
||||||
|
adapter.setData(null)
|
||||||
|
loadConversation(status, null, null)
|
||||||
|
loadActivity(status)
|
||||||
|
|
||||||
|
val position = adapter.getFirstPositionOfItem(StatusDetailsAdapter.ITEM_IDX_STATUS)
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
layoutManager.scrollToPositionWithOffset(position, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyzer.log(StatusView(details?.type, status.media_type).apply {
|
||||||
|
this.type = StatusView.getStatusType(status)
|
||||||
|
this.source = status.source?.let(HtmlEscapeHelper::toPlainText)
|
||||||
|
})
|
||||||
|
} else if (readPosition != null) {
|
||||||
|
restoreReadPosition(readPosition)
|
||||||
|
}
|
||||||
|
setState(STATE_LOADED)
|
||||||
|
} else {
|
||||||
|
adapter.loadMoreSupportedPosition = ILoadMoreSupportAdapter.NONE
|
||||||
|
setState(STATE_ERROR)
|
||||||
|
val errorInfo = StatusCodeMessageUtils.getErrorInfo(context, data.exception!!)
|
||||||
|
errorText.spannable = errorInfo.message
|
||||||
|
errorIcon.setImageResource(errorInfo.icon)
|
||||||
|
}
|
||||||
|
activity.invalidateOptionsMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoaderReset(loader: Loader<SingleResponse<ParcelableStatus>>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override val refreshing: Boolean
|
||||||
|
get() = loaderManager.hasRunningLoadersSafe()
|
||||||
|
|
||||||
|
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
|
||||||
|
if (!hasMoreConversation) return
|
||||||
|
if (ILoadMoreSupportAdapter.START in position) {
|
||||||
|
val start = adapter.getIndexStart(StatusDetailsAdapter.ITEM_IDX_CONVERSATION)
|
||||||
|
val first = adapter.getStatus(start, true)
|
||||||
|
if (first.in_reply_to_status_id == null) return
|
||||||
|
loadConversation(status, null, first.id)
|
||||||
|
} else if (ILoadMoreSupportAdapter.END in position) {
|
||||||
|
val start = adapter.getIndexStart(StatusDetailsAdapter.ITEM_IDX_CONVERSATION)
|
||||||
|
val last = adapter.getStatus(start + adapter.getStatusCount(true) - 1, true)
|
||||||
|
loadConversation(status, last.id, null)
|
||||||
|
}
|
||||||
|
adapter.loadMoreIndicatorPosition = position
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setControlVisible(visible: Boolean) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onApplySystemWindowInsets(insets: Rect) {
|
||||||
|
recyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val reachingEnd: Boolean
|
||||||
|
get() {
|
||||||
|
val lm = layoutManager
|
||||||
|
var itemPos = lm.findLastCompletelyVisibleItemPosition()
|
||||||
|
if (itemPos == RecyclerView.NO_POSITION) {
|
||||||
|
// No completely visible item, find visible item instead
|
||||||
|
itemPos = lm.findLastVisibleItemPosition()
|
||||||
|
}
|
||||||
|
return itemPos >= lm.itemCount - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override val reachingStart: Boolean
|
||||||
|
get() {
|
||||||
|
val lm = layoutManager
|
||||||
|
var itemPos = lm.findFirstCompletelyVisibleItemPosition()
|
||||||
|
if (itemPos == RecyclerView.NO_POSITION) {
|
||||||
|
// No completely visible item, find visible item instead
|
||||||
|
itemPos = lm.findFirstVisibleItemPosition()
|
||||||
|
}
|
||||||
|
return itemPos <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private val status: ParcelableStatus?
|
||||||
|
get() = adapter.status
|
||||||
|
|
||||||
|
private fun loadConversation(status: ParcelableStatus?, sinceId: String?, maxId: String?) {
|
||||||
|
if (status == null || activity == null) return
|
||||||
|
val args = Bundle {
|
||||||
|
this[EXTRA_ACCOUNT_KEY] = status.account_key
|
||||||
|
this[EXTRA_STATUS_ID] = status.originalId
|
||||||
|
this[EXTRA_SINCE_ID] = sinceId
|
||||||
|
this[EXTRA_MAX_ID] = maxId
|
||||||
|
this[EXTRA_STATUS] = status
|
||||||
|
}
|
||||||
|
if (conversationLoaderInitialized) {
|
||||||
|
loaderManager.restartLoader(LOADER_ID_STATUS_CONVERSATIONS, args, conversationsLoaderCallback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loaderManager.initLoader(LOADER_ID_STATUS_CONVERSATIONS, args, conversationsLoaderCallback)
|
||||||
|
conversationLoaderInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun loadActivity(status: ParcelableStatus?) {
|
||||||
|
if (status == null || host == null || isDetached) return
|
||||||
|
val args = Bundle {
|
||||||
|
this[EXTRA_ACCOUNT_KEY] = status.account_key
|
||||||
|
this[EXTRA_STATUS_ID] = status.originalId
|
||||||
|
}
|
||||||
|
if (activityLoaderInitialized) {
|
||||||
|
loaderManager.restartLoader(LOADER_ID_STATUS_ACTIVITY, args, statusActivityLoaderCallback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loaderManager.initLoader(LOADER_ID_STATUS_ACTIVITY, args, statusActivityLoaderCallback)
|
||||||
|
activityLoaderInitialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun loadTranslation(status: ParcelableStatus?) {
|
||||||
|
if (status == null) return
|
||||||
|
if (loadTranslationTask?.isFinished == true) return
|
||||||
|
loadTranslationTask = run {
|
||||||
|
val task = LoadTranslationTask(this, status)
|
||||||
|
TaskStarter.execute(task)
|
||||||
|
return@run task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setConversation(data: List<ParcelableStatus>?) {
|
||||||
|
val readPosition = saveReadPosition()
|
||||||
|
val changed = adapter.setData(data)
|
||||||
|
hasMoreConversation = data != null && changed
|
||||||
|
restoreReadPosition(readPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scrollToCurrent() {
|
||||||
|
if (adapter.status != null) {
|
||||||
|
val position = adapter.getFirstPositionOfItem(StatusDetailsAdapter.ITEM_IDX_STATUS)
|
||||||
|
recyclerView.smoothScrollToPosition(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displayTranslation(translation: TranslationResult) {
|
||||||
|
adapter.translationResult = translation
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveReadPosition(): ReadPosition? {
|
||||||
|
val lm = layoutManager
|
||||||
|
val adapter = this.adapter
|
||||||
|
val position = lm.findFirstVisibleItemPosition()
|
||||||
|
if (position == RecyclerView.NO_POSITION) return null
|
||||||
|
val itemType = adapter.getItemType(position)
|
||||||
|
var itemId = adapter.getItemId(position)
|
||||||
|
val positionView: View?
|
||||||
|
if (itemType == StatusDetailsAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE) {
|
||||||
|
// Should be next item
|
||||||
|
positionView = lm.findViewByPosition(position + 1)
|
||||||
|
itemId = adapter.getItemId(position + 1)
|
||||||
|
} else {
|
||||||
|
positionView = lm.findViewByPosition(position)
|
||||||
|
}
|
||||||
|
return ReadPosition(itemId, positionView?.top ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreReadPosition(position: ReadPosition?) {
|
||||||
|
val adapter = this.adapter
|
||||||
|
if (position == null) return
|
||||||
|
val adapterPosition = adapter.findPositionByItemId(position.statusId)
|
||||||
|
if (adapterPosition < 0) return
|
||||||
|
layoutManager.scrollToPositionWithOffset(adapterPosition, position.offsetTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setState(state: Int) {
|
||||||
|
statusContent.visibility = if (state == STATE_LOADED) View.VISIBLE else View.GONE
|
||||||
|
progressContainer.visibility = if (state == STATE_LOADING) View.VISIBLE else View.GONE
|
||||||
|
errorContainer.visibility = if (state == STATE_ERROR) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
bus.register(this)
|
||||||
|
recyclerView.addOnScrollListener(scrollListener)
|
||||||
|
recyclerView.setOnTouchListener(scrollListener.touchListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
recyclerView.setOnTouchListener(null)
|
||||||
|
recyclerView.removeOnScrollListener(scrollListener)
|
||||||
|
bus.unregister(this)
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||||
|
if (!userVisibleHint) return
|
||||||
|
val contextMenuInfo = menuInfo as? ExtendedRecyclerView.ContextMenuInfo ?: return
|
||||||
|
val status = adapter.getStatus(contextMenuInfo.position)
|
||||||
|
val inflater = MenuInflater(context)
|
||||||
|
inflater.inflate(R.menu.action_status, menu)
|
||||||
|
MenuUtils.setupForStatus(context, menu, preferences, twitterWrapper, userColorNameManager,
|
||||||
|
status)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (!userVisibleHint) return false
|
||||||
|
val contextMenuInfo = item.menuInfo as? ExtendedRecyclerView.ContextMenuInfo ?: return false
|
||||||
|
val status = adapter.getStatus(contextMenuInfo.position)
|
||||||
|
if (item.itemId == R.id.share) {
|
||||||
|
val shareIntent = Utils.createStatusShareIntent(activity, status)
|
||||||
|
val chooser = Intent.createChooser(shareIntent, getString(R.string.share_status))
|
||||||
|
|
||||||
|
startActivity(chooser)
|
||||||
|
|
||||||
|
val am = AccountManager.get(context)
|
||||||
|
val accountType = AccountUtils.findByAccountKey(am, status.account_key)?.getAccountType(am)
|
||||||
|
|
||||||
|
Analyzer.log(Share.status(accountType, status))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return MenuUtils.handleStatusClick(activity, this, fragmentManager,
|
||||||
|
preferences, userColorNameManager, twitterWrapper, status, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun notifyStatusListChanged(event: StatusListChangedEvent) {
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun notifyFavoriteTask(event: FavoriteTaskEvent) {
|
||||||
|
if (!event.isSucceeded) return
|
||||||
|
val status = adapter.findStatusById(event.accountKey, event.statusId)
|
||||||
|
if (status != null) {
|
||||||
|
when (event.action) {
|
||||||
|
FavoriteTaskEvent.Action.CREATE -> {
|
||||||
|
status.is_favorite = true
|
||||||
|
}
|
||||||
|
FavoriteTaskEvent.Action.DESTROY -> {
|
||||||
|
status.is_favorite = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun onUserClick(user: ParcelableUser) {
|
||||||
|
IntentUtils.openUserProfile(context, user, true, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun openTranslationDestinationChooser() {
|
||||||
|
val account = adapter.statusAccount ?: return
|
||||||
|
val weakThis = WeakReference(this)
|
||||||
|
(showProgressDialog("get_language_settings") and task {
|
||||||
|
val fragment = weakThis.get() ?: throw InterruptedException()
|
||||||
|
val microBlog = account.newMicroBlogInstance(fragment.context, MicroBlog::class.java)
|
||||||
|
return@task Pair(microBlog.accountSettings.language,
|
||||||
|
microBlog.languages.map { TranslationDestinationDialogFragment.DisplayLanguage(it.name, it.code) })
|
||||||
|
}).successUi { (_, settings) ->
|
||||||
|
val (accountLanguage, languages) = settings
|
||||||
|
val fragment = weakThis.get() ?: return@successUi
|
||||||
|
val df = TranslationDestinationDialogFragment.create(languages, accountLanguage)
|
||||||
|
df.show(fragment.childFragmentManager, "translation_destination_settings")
|
||||||
|
}.alwaysUi {
|
||||||
|
val fragment = weakThis.get() ?: return@alwaysUi
|
||||||
|
fragment.dismissProgressDialog("get_language_settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadSensitiveImageConfirmDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
|
||||||
|
|
||||||
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
|
when (which) {
|
||||||
|
DialogInterface.BUTTON_POSITIVE -> {
|
||||||
|
val f = parentFragment
|
||||||
|
if (f is StatusFragment) {
|
||||||
|
val adapter = f.adapter
|
||||||
|
adapter.isDetailMediaExpanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val context = activity
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
builder.setTitle(android.R.string.dialog_alert_title)
|
||||||
|
builder.setMessage(R.string.sensitive_content_warning)
|
||||||
|
builder.setPositiveButton(android.R.string.ok, this)
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
val dialog = builder.create()
|
||||||
|
dialog.onShow { it.applyTheme() }
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LoadTranslationTask(fragment: StatusFragment, val status: ParcelableStatus) :
|
||||||
|
AbsAccountRequestTask<Any?, TranslationResult, Any?>(fragment.context, status.account_key) {
|
||||||
|
|
||||||
|
private val weakFragment = WeakReference(fragment)
|
||||||
|
|
||||||
|
override fun onExecute(account: AccountDetails, params: Any?): TranslationResult {
|
||||||
|
val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java)
|
||||||
|
val prefDest = preferences.getString(KEY_TRANSLATION_DESTINATION, null)
|
||||||
|
val dest: String
|
||||||
|
if (TextUtils.isEmpty(prefDest)) {
|
||||||
|
dest = twitter.accountSettings.language
|
||||||
|
val editor = preferences.edit()
|
||||||
|
editor.putString(KEY_TRANSLATION_DESTINATION, dest)
|
||||||
|
editor.apply()
|
||||||
|
} else {
|
||||||
|
dest = prefDest
|
||||||
|
}
|
||||||
|
return twitter.showTranslation(status.originalId, dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSucceed(callback: Any?, result: TranslationResult) {
|
||||||
|
val fragment = weakFragment.get() ?: return
|
||||||
|
fragment.displayTranslation(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onException(callback: Any?, exception: MicroBlogException) {
|
||||||
|
Toast.makeText(context, exception.getErrorMessage(context), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StatusActivitySummaryLoader(
|
||||||
|
context: Context,
|
||||||
|
private val accountKey: UserKey,
|
||||||
|
private val statusId: String
|
||||||
|
) : FixedAsyncTaskLoader<StatusActivity>(context) {
|
||||||
|
|
||||||
|
override fun loadInBackground(): StatusActivity? {
|
||||||
|
val context = context
|
||||||
|
val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: return null
|
||||||
|
if (AccountType.TWITTER != details.type) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return null
|
||||||
|
val paging = Paging()
|
||||||
|
paging.setCount(10)
|
||||||
|
val activitySummary = StatusActivity(statusId, emptyList())
|
||||||
|
try {
|
||||||
|
activitySummary.retweeters = twitter.getRetweets(statusId, paging)
|
||||||
|
.filterNot { DataStoreUtils.isFilteringUser(context, it.user.key) }
|
||||||
|
.distinctBy { it.user.id }
|
||||||
|
.map { it.user.toParcelable(details) }
|
||||||
|
val countValues = ContentValues()
|
||||||
|
val status = twitter.showStatus(statusId)
|
||||||
|
activitySummary.favoriteCount = status.favoriteCount
|
||||||
|
activitySummary.retweetCount = status.retweetCount
|
||||||
|
activitySummary.replyCount = status.replyCount
|
||||||
|
|
||||||
|
countValues.put(Statuses.REPLY_COUNT, activitySummary.replyCount)
|
||||||
|
countValues.put(Statuses.FAVORITE_COUNT, activitySummary.favoriteCount)
|
||||||
|
countValues.put(Statuses.RETWEET_COUNT, activitySummary.retweetCount)
|
||||||
|
|
||||||
|
val cr = context.contentResolver
|
||||||
|
val statusWhere = Expression.and(
|
||||||
|
Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
||||||
|
Expression.or(
|
||||||
|
Expression.equalsArgs(Statuses.ID),
|
||||||
|
Expression.equalsArgs(Statuses.RETWEET_ID)))
|
||||||
|
val statusWhereArgs = arrayOf(accountKey.toString(), statusId, statusId)
|
||||||
|
cr.update(Statuses.CONTENT_URI, countValues, statusWhere.sql, statusWhereArgs)
|
||||||
|
cr.updateStatusInfo(DataStoreUtils.STATUSES_ACTIVITIES_URIS, Statuses.COLUMNS,
|
||||||
|
accountKey, statusId, ParcelableStatus::class.java) { item ->
|
||||||
|
item.favorite_count = activitySummary.favoriteCount
|
||||||
|
item.reply_count = activitySummary.replyCount
|
||||||
|
item.retweet_count = activitySummary.retweetCount
|
||||||
|
return@updateStatusInfo item
|
||||||
|
}
|
||||||
|
val pStatus = status.toParcelable(details)
|
||||||
|
cr.insert(CachedStatuses.CONTENT_URI, ObjectCursor
|
||||||
|
.valuesCreatorFrom(ParcelableStatus::class.java).create(pStatus))
|
||||||
|
|
||||||
|
return activitySummary
|
||||||
|
} catch (e: MicroBlogException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartLoading() {
|
||||||
|
forceLoad()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StatusActivity(
|
||||||
|
var statusId: String,
|
||||||
|
var retweeters: List<ParcelableUser>,
|
||||||
|
var favoriteCount: Long = 0,
|
||||||
|
var replyCount: Long = -1,
|
||||||
|
var retweetCount: Long = 0
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun isStatus(status: ParcelableStatus): Boolean {
|
||||||
|
return statusId == status.retweet_id ?: status.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ReadPosition(var statusId: Long, var offsetTop: Int)
|
||||||
|
|
||||||
|
private class StatusListLinearLayoutManager(context: Context, private val recyclerView: RecyclerView) : FixedLinearLayoutManager(context) {
|
||||||
|
private var spaceHeight: Int = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
orientation = LinearLayoutManager.VERTICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDecoratedMeasuredHeight(child: View): Int {
|
||||||
|
if (getItemViewType(child) == StatusDetailsAdapter.VIEW_TYPE_SPACE) {
|
||||||
|
val height = calculateSpaceItemHeight(child, StatusDetailsAdapter.VIEW_TYPE_SPACE,
|
||||||
|
StatusDetailsAdapter.VIEW_TYPE_DETAIL_STATUS)
|
||||||
|
if (height >= 0) {
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.getDecoratedMeasuredHeight(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setOrientation(orientation: Int) {
|
||||||
|
if (orientation != LinearLayoutManager.VERTICAL)
|
||||||
|
throw IllegalArgumentException("Only VERTICAL orientation supported")
|
||||||
|
super.setOrientation(orientation)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun computeVerticalScrollExtent(state: RecyclerView.State?): Int {
|
||||||
|
val firstPosition = findFirstVisibleItemPosition()
|
||||||
|
val lastPosition = Math.min(validScrollItemCount - 1, findLastVisibleItemPosition())
|
||||||
|
if (firstPosition < 0 || lastPosition < 0) return 0
|
||||||
|
val childCount = lastPosition - firstPosition + 1
|
||||||
|
if (childCount > 0) {
|
||||||
|
if (isSmoothScrollbarEnabled) {
|
||||||
|
var extent = childCount * 100
|
||||||
|
var view = findViewByPosition(firstPosition) ?: return 0
|
||||||
|
val top = view.top
|
||||||
|
var height = view.height
|
||||||
|
if (height > 0) {
|
||||||
|
extent += top * 100 / height
|
||||||
|
}
|
||||||
|
|
||||||
|
view = findViewByPosition(lastPosition) ?: return 0
|
||||||
|
val bottom = view.bottom
|
||||||
|
height = view.height
|
||||||
|
if (height > 0) {
|
||||||
|
extent -= (bottom - getHeight()) * 100 / height
|
||||||
|
}
|
||||||
|
return extent
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun computeVerticalScrollOffset(state: RecyclerView.State?): Int {
|
||||||
|
val firstPosition = findFirstVisibleItemPosition()
|
||||||
|
val lastPosition = Math.min(validScrollItemCount - 1, findLastVisibleItemPosition())
|
||||||
|
if (firstPosition < 0 || lastPosition < 0) return 0
|
||||||
|
val childCount = lastPosition - firstPosition + 1
|
||||||
|
val skippedCount = skippedScrollItemCount
|
||||||
|
if (firstPosition >= skippedCount && childCount > 0) {
|
||||||
|
if (isSmoothScrollbarEnabled) {
|
||||||
|
val view = findViewByPosition(firstPosition) ?: return 0
|
||||||
|
val top = view.top
|
||||||
|
val height = view.height
|
||||||
|
if (height > 0) {
|
||||||
|
return Math.max((firstPosition - skippedCount) * 100 - top * 100 / height, 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val index: Int
|
||||||
|
val count = validScrollItemCount
|
||||||
|
if (firstPosition == 0) {
|
||||||
|
index = 0
|
||||||
|
} else if (firstPosition + childCount == count) {
|
||||||
|
index = count
|
||||||
|
} else {
|
||||||
|
index = firstPosition + childCount / 2
|
||||||
|
}
|
||||||
|
return (firstPosition + childCount * (index / count.toFloat())).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun computeVerticalScrollRange(state: RecyclerView.State?): Int {
|
||||||
|
val result: Int
|
||||||
|
if (isSmoothScrollbarEnabled) {
|
||||||
|
result = Math.max(validScrollItemCount * 100, 0)
|
||||||
|
} else {
|
||||||
|
result = validScrollItemCount
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private val skippedScrollItemCount: Int
|
||||||
|
get() {
|
||||||
|
val adapter = recyclerView.adapter as StatusDetailsAdapter
|
||||||
|
var skipped = 0
|
||||||
|
if (!adapter.isConversationsLoading) {
|
||||||
|
skipped += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE)
|
||||||
|
}
|
||||||
|
return skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
private val validScrollItemCount: Int
|
||||||
|
get() {
|
||||||
|
val adapter = recyclerView.adapter as StatusDetailsAdapter
|
||||||
|
var count = 0
|
||||||
|
if (adapter.isConversationsLoading) {
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_CONVERSATION_LOAD_MORE)
|
||||||
|
}
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_CONVERSATION_ERROR)
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_CONVERSATION)
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_STATUS)
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_REPLY)
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_REPLY_ERROR)
|
||||||
|
if (adapter.isRepliesLoading) {
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_REPLY_LOAD_MORE)
|
||||||
|
}
|
||||||
|
val spaceHeight = calculateSpaceHeight()
|
||||||
|
if (spaceHeight > 0) {
|
||||||
|
count += adapter.getTypeCount(StatusDetailsAdapter.ITEM_IDX_SPACE)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateSpaceHeight(): Int {
|
||||||
|
val space = findViewByPosition(itemCount - 1) ?: return spaceHeight
|
||||||
|
spaceHeight = getDecoratedMeasuredHeight(space)
|
||||||
|
return spaceHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StatusDividerItemDecoration(
|
||||||
|
context: Context,
|
||||||
|
private val statusAdapter: StatusDetailsAdapter,
|
||||||
|
orientation: Int
|
||||||
|
) : ExtendedDividerItemDecoration(context, orientation) {
|
||||||
|
|
||||||
|
override fun isDividerEnabled(childPos: Int): Boolean {
|
||||||
|
if (childPos >= statusAdapter.itemCount || childPos < 0) return false
|
||||||
|
val itemType = statusAdapter.getItemType(childPos)
|
||||||
|
when (itemType) {
|
||||||
|
StatusDetailsAdapter.ITEM_IDX_REPLY_LOAD_MORE, StatusDetailsAdapter.ITEM_IDX_REPLY_ERROR,
|
||||||
|
StatusDetailsAdapter.ITEM_IDX_SPACE -> return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
private val LOADER_ID_DETAIL_STATUS = 1
|
||||||
|
private val LOADER_ID_STATUS_CONVERSATIONS = 2
|
||||||
|
private val LOADER_ID_STATUS_ACTIVITY = 3
|
||||||
|
private val STATE_LOADED = 1
|
||||||
|
private val STATE_LOADING = 2
|
||||||
|
private val STATE_ERROR = 3
|
||||||
|
|
||||||
|
fun Bundle.toPagination(): Pagination {
|
||||||
|
val maxId = getString(EXTRA_MAX_ID)
|
||||||
|
val sinceId = getString(EXTRA_SINCE_ID)
|
||||||
|
val maxSortId = getLong(EXTRA_MAX_SORT_ID)
|
||||||
|
val sinceSortId = getLong(EXTRA_SINCE_SORT_ID)
|
||||||
|
return SinceMaxPagination().apply {
|
||||||
|
this.maxId = maxId
|
||||||
|
this.sinceId = sinceId
|
||||||
|
this.maxSortId = maxSortId
|
||||||
|
this.sinceSortId = sinceSortId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Twidere - Twitter client for Android
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.mariotaku.twidere.fragment.status
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import org.mariotaku.kpreferences.get
|
||||||
|
import org.mariotaku.kpreferences.set
|
||||||
|
import org.mariotaku.ktextension.Bundle
|
||||||
|
import org.mariotaku.ktextension.getTypedArray
|
||||||
|
import org.mariotaku.ktextension.mapToArray
|
||||||
|
import org.mariotaku.ktextension.set
|
||||||
|
import org.mariotaku.twidere.constant.translationDestinationKey
|
||||||
|
import org.mariotaku.twidere.extension.applyTheme
|
||||||
|
import org.mariotaku.twidere.extension.onShow
|
||||||
|
import org.mariotaku.twidere.fragment.BaseDialogFragment
|
||||||
|
import java.text.Collator
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class TranslationDestinationDialogFragment : BaseDialogFragment() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
val languages = arguments.getTypedArray<DisplayLanguage>(EXTRA_LANGUAGES).sortedArrayWith(LanguageComparator())
|
||||||
|
val selectedLanguage = preferences[translationDestinationKey] ?: arguments.getString(EXTRA_SELECTED_LANGUAGE)
|
||||||
|
val selectedIndex = languages.indexOfFirst { selectedLanguage == it.code }
|
||||||
|
builder.setSingleChoiceItems(languages.mapToArray { it.name }, selectedIndex) { _, which ->
|
||||||
|
preferences[translationDestinationKey] = languages[which].code
|
||||||
|
}
|
||||||
|
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
val dialog = builder.create()
|
||||||
|
dialog.onShow {
|
||||||
|
it.applyTheme()
|
||||||
|
it.listView?.isFastScrollEnabled = true
|
||||||
|
}
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DisplayLanguage(val name: String, val code: String) : Parcelable {
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString())
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(name)
|
||||||
|
parcel.writeString(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<DisplayLanguage> {
|
||||||
|
|
||||||
|
override fun createFromParcel(parcel: Parcel): DisplayLanguage {
|
||||||
|
return DisplayLanguage(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<DisplayLanguage?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class LanguageComparator : Comparator<DisplayLanguage> {
|
||||||
|
|
||||||
|
private val collator = Collator.getInstance(Locale.getDefault())
|
||||||
|
|
||||||
|
override fun compare(object1: DisplayLanguage, object2: DisplayLanguage): Int {
|
||||||
|
return collator.compare(object1.name, object2.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_SELECTED_LANGUAGE = "selected_language"
|
||||||
|
const val EXTRA_LANGUAGES = "languages"
|
||||||
|
|
||||||
|
fun create(languages: List<DisplayLanguage>, selectedLanguage: String?): TranslationDestinationDialogFragment {
|
||||||
|
val df = TranslationDestinationDialogFragment()
|
||||||
|
df.arguments = Bundle {
|
||||||
|
this[EXTRA_LANGUAGES] = languages.toTypedArray()
|
||||||
|
this[EXTRA_SELECTED_LANGUAGE] = selectedLanguage
|
||||||
|
}
|
||||||
|
return df
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,760 @@
|
|||||||
|
/*
|
||||||
|
* Twidere - Twitter client for Android
|
||||||
|
*
|
||||||
|
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.mariotaku.twidere.view.holder.status
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.support.annotation.UiThread
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v4.view.MenuItemCompat
|
||||||
|
import android.support.v4.view.ViewCompat
|
||||||
|
import android.support.v7.widget.ActionMenuView
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.text.style.ForegroundColorSpan
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import kotlinx.android.synthetic.main.adapter_item_status_count_label.view.*
|
||||||
|
import kotlinx.android.synthetic.main.header_status.view.*
|
||||||
|
import org.mariotaku.kpreferences.get
|
||||||
|
import org.mariotaku.ktextension.applyFontFamily
|
||||||
|
import org.mariotaku.ktextension.hideIfEmpty
|
||||||
|
import org.mariotaku.ktextension.spannable
|
||||||
|
import org.mariotaku.microblog.library.twitter.model.TranslationResult
|
||||||
|
import org.mariotaku.twidere.Constants
|
||||||
|
import org.mariotaku.twidere.R
|
||||||
|
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter
|
||||||
|
import org.mariotaku.twidere.adapter.StatusDetailsAdapter
|
||||||
|
import org.mariotaku.twidere.annotation.ProfileImageSize
|
||||||
|
import org.mariotaku.twidere.constant.displaySensitiveContentsKey
|
||||||
|
import org.mariotaku.twidere.constant.newDocumentApiKey
|
||||||
|
import org.mariotaku.twidere.extension.loadProfileImage
|
||||||
|
import org.mariotaku.twidere.extension.model.*
|
||||||
|
import org.mariotaku.twidere.fragment.status.StatusFragment
|
||||||
|
import org.mariotaku.twidere.menu.FavoriteItemProvider
|
||||||
|
import org.mariotaku.twidere.model.*
|
||||||
|
import org.mariotaku.twidere.model.util.ParcelableLocationUtils
|
||||||
|
import org.mariotaku.twidere.model.util.ParcelableMediaUtils
|
||||||
|
import org.mariotaku.twidere.util.*
|
||||||
|
import org.mariotaku.twidere.util.twitter.card.TwitterCardViewFactory
|
||||||
|
import org.mariotaku.twidere.view.ProfileImageView
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class DetailStatusViewHolder(
|
||||||
|
private val adapter: StatusDetailsAdapter,
|
||||||
|
itemView: View
|
||||||
|
) : RecyclerView.ViewHolder(itemView), View.OnClickListener, ActionMenuView.OnMenuItemClickListener {
|
||||||
|
|
||||||
|
private val linkClickHandler: StatusLinkClickHandler
|
||||||
|
private val linkify: TwidereLinkify
|
||||||
|
|
||||||
|
private val profileTypeView = itemView.profileType
|
||||||
|
private val nameView = itemView.name
|
||||||
|
private val summaryView = itemView.summary
|
||||||
|
private val textView = itemView.text
|
||||||
|
private val locationView = itemView.locationView
|
||||||
|
private val retweetedByView = itemView.retweetedBy
|
||||||
|
private val translateResultView = itemView.translateResult
|
||||||
|
private val translateChangeLanguageView = itemView.translateChangeLanguage
|
||||||
|
private val translateContainer = itemView.translateContainer
|
||||||
|
private val translateLabelView = itemView.translateLabel
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.linkClickHandler = DetailStatusLinkClickHandler(adapter.context,
|
||||||
|
adapter.multiSelectManager, adapter, adapter.preferences)
|
||||||
|
this.linkify = TwidereLinkify(linkClickHandler)
|
||||||
|
|
||||||
|
initViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
fun displayStatus(account: AccountDetails?, status: ParcelableStatus?,
|
||||||
|
statusActivity: StatusFragment.StatusActivity?, translation: TranslationResult?) {
|
||||||
|
if (account == null || status == null) return
|
||||||
|
val fragment = adapter.fragment
|
||||||
|
val context = adapter.context
|
||||||
|
val formatter = adapter.bidiFormatter
|
||||||
|
val twitter = adapter.twitterWrapper
|
||||||
|
val nameFirst = adapter.nameFirst
|
||||||
|
val colorNameManager = adapter.userColorNameManager
|
||||||
|
|
||||||
|
linkClickHandler.status = status
|
||||||
|
|
||||||
|
if (status.retweet_id != null) {
|
||||||
|
val retweetedBy = colorNameManager.getDisplayName(status.retweeted_by_user_key!!,
|
||||||
|
status.retweeted_by_user_name!!, status.retweeted_by_user_acct!!, nameFirst)
|
||||||
|
retweetedByView.spannable = context.getString(R.string.name_retweeted, retweetedBy)
|
||||||
|
retweetedByView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
retweetedByView.spannable = null
|
||||||
|
retweetedByView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.profileContainer.drawEnd(status.account_color)
|
||||||
|
|
||||||
|
val layoutPosition = layoutPosition
|
||||||
|
val skipLinksInText = status.extras?.support_entities == true
|
||||||
|
|
||||||
|
if (status.is_quote) {
|
||||||
|
|
||||||
|
itemView.quotedView.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val quoteContentAvailable = status.quoted_text_plain != null && status.quoted_text_unescaped != null
|
||||||
|
|
||||||
|
if (quoteContentAvailable) {
|
||||||
|
itemView.quotedName.visibility = View.VISIBLE
|
||||||
|
itemView.quotedText.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
itemView.quotedName.name = colorNameManager.getUserNickname(status.quoted_user_key!!,
|
||||||
|
status.quoted_user_name)
|
||||||
|
itemView.quotedName.screenName = "@${status.quoted_user_acct}"
|
||||||
|
itemView.quotedName.updateText(formatter)
|
||||||
|
|
||||||
|
|
||||||
|
val quotedDisplayEnd = status.extras?.quoted_display_text_range?.getOrNull(1) ?: -1
|
||||||
|
val quotedText = SpannableStringBuilder.valueOf(status.quoted_text_unescaped)
|
||||||
|
status.quoted_spans?.applyTo(quotedText)
|
||||||
|
linkify.applyAllLinks(quotedText, status.account_key, layoutPosition.toLong(),
|
||||||
|
status.is_possibly_sensitive, skipLinksInText)
|
||||||
|
if (quotedDisplayEnd != -1 && quotedDisplayEnd <= quotedText.length) {
|
||||||
|
itemView.quotedText.spannable = quotedText.subSequence(0, quotedDisplayEnd)
|
||||||
|
} else {
|
||||||
|
itemView.quotedText.spannable = quotedText
|
||||||
|
}
|
||||||
|
itemView.quotedText.hideIfEmpty()
|
||||||
|
|
||||||
|
val quotedUserColor = colorNameManager.getUserColor(status.quoted_user_key!!)
|
||||||
|
if (quotedUserColor != 0) {
|
||||||
|
itemView.quotedView.drawStart(quotedUserColor)
|
||||||
|
} else {
|
||||||
|
itemView.quotedView.drawStart(ThemeUtils.getColorFromAttribute(context,
|
||||||
|
R.attr.quoteIndicatorBackgroundColor))
|
||||||
|
}
|
||||||
|
|
||||||
|
val quotedMedia = status.quoted_media
|
||||||
|
|
||||||
|
if (quotedMedia?.isEmpty() != false) {
|
||||||
|
itemView.quotedMediaLabel.visibility = View.GONE
|
||||||
|
itemView.quotedMediaPreview.visibility = View.GONE
|
||||||
|
} else if (adapter.isDetailMediaExpanded) {
|
||||||
|
itemView.quotedMediaLabel.visibility = View.GONE
|
||||||
|
itemView.quotedMediaPreview.visibility = View.VISIBLE
|
||||||
|
itemView.quotedMediaPreview.displayMedia(adapter.requestManager,
|
||||||
|
media = quotedMedia, accountKey = status.account_key,
|
||||||
|
mediaClickListener = adapter.fragment)
|
||||||
|
} else {
|
||||||
|
itemView.quotedMediaLabel.visibility = View.VISIBLE
|
||||||
|
itemView.quotedMediaPreview.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemView.quotedName.visibility = View.GONE
|
||||||
|
itemView.quotedText.visibility = View.VISIBLE
|
||||||
|
itemView.quotedMediaLabel.visibility = View.GONE
|
||||||
|
itemView.quotedMediaPreview.visibility = View.GONE
|
||||||
|
|
||||||
|
// Not available
|
||||||
|
val string = SpannableString.valueOf(context.getString(R.string.label_status_not_available))
|
||||||
|
string.setSpan(ForegroundColorSpan(ThemeUtils.getColorFromAttribute(context,
|
||||||
|
android.R.attr.textColorTertiary, textView.currentTextColor)), 0,
|
||||||
|
string.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
itemView.quotedText.spannable = string
|
||||||
|
|
||||||
|
itemView.quotedView.drawStart(ThemeUtils.getColorFromAttribute(context,
|
||||||
|
R.attr.quoteIndicatorBackgroundColor))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
itemView.quotedView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.profileContainer.drawStart(colorNameManager.getUserColor(status.user_key))
|
||||||
|
|
||||||
|
val timestamp: Long
|
||||||
|
|
||||||
|
if (status.is_retweet) {
|
||||||
|
timestamp = status.retweet_timestamp
|
||||||
|
} else {
|
||||||
|
timestamp = status.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
nameView.name = colorNameManager.getUserNickname(status.user_key, status.user_name)
|
||||||
|
nameView.screenName = "@${status.user_acct}"
|
||||||
|
nameView.updateText(formatter)
|
||||||
|
|
||||||
|
adapter.requestManager.loadProfileImage(context, status, adapter.profileImageStyle,
|
||||||
|
itemView.profileImage.cornerRadius, itemView.profileImage.cornerRadiusRatio,
|
||||||
|
size = ProfileImageSize.ORIGINAL).into(itemView.profileImage)
|
||||||
|
|
||||||
|
val typeIconRes = Utils.getUserTypeIconRes(status.user_is_verified, status.user_is_protected)
|
||||||
|
val typeDescriptionRes = Utils.getUserTypeDescriptionRes(status.user_is_verified, status.user_is_protected)
|
||||||
|
|
||||||
|
|
||||||
|
if (typeIconRes != 0 && typeDescriptionRes != 0) {
|
||||||
|
profileTypeView.setImageResource(typeIconRes)
|
||||||
|
profileTypeView.contentDescription = context.getString(typeDescriptionRes)
|
||||||
|
profileTypeView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
profileTypeView.setImageDrawable(null)
|
||||||
|
profileTypeView.contentDescription = null
|
||||||
|
profileTypeView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeString = Utils.formatToLongTimeString(context, timestamp)?.takeIf(String::isNotEmpty)
|
||||||
|
val source = status.source?.takeIf(String::isNotEmpty)
|
||||||
|
itemView.timeSource.spannable = when {
|
||||||
|
timeString != null && source != null -> {
|
||||||
|
HtmlSpanBuilder.fromHtml(context.getString(R.string.status_format_time_source,
|
||||||
|
timeString, source))
|
||||||
|
}
|
||||||
|
source != null -> HtmlSpanBuilder.fromHtml(source)
|
||||||
|
timeString != null -> timeString
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
itemView.timeSource.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
|
||||||
|
val displayEnd = status.extras?.display_text_range?.getOrNull(1) ?: -1
|
||||||
|
val text = SpannableStringBuilder.valueOf(status.text_unescaped).apply {
|
||||||
|
status.spans?.applyTo(this)
|
||||||
|
linkify.applyAllLinks(this, status.account_key, layoutPosition.toLong(),
|
||||||
|
status.is_possibly_sensitive, skipLinksInText)
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryView.spannable = status.extras?.summary_text
|
||||||
|
summaryView.hideIfEmpty()
|
||||||
|
|
||||||
|
if (displayEnd != -1 && displayEnd <= text.length) {
|
||||||
|
textView.spannable = text.subSequence(0, displayEnd)
|
||||||
|
} else {
|
||||||
|
textView.spannable = text
|
||||||
|
}
|
||||||
|
textView.hideIfEmpty()
|
||||||
|
|
||||||
|
val location: ParcelableLocation? = status.location
|
||||||
|
val placeFullName: String? = status.place_full_name
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(placeFullName)) {
|
||||||
|
locationView.visibility = View.VISIBLE
|
||||||
|
locationView.spannable = placeFullName
|
||||||
|
locationView.isClickable = ParcelableLocationUtils.isValidLocation(location)
|
||||||
|
} else if (ParcelableLocationUtils.isValidLocation(location)) {
|
||||||
|
locationView.visibility = View.VISIBLE
|
||||||
|
locationView.setText(R.string.action_view_map)
|
||||||
|
locationView.isClickable = true
|
||||||
|
} else {
|
||||||
|
locationView.visibility = View.GONE
|
||||||
|
locationView.spannable = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val interactUsersAdapter = itemView.countsUsers.adapter as CountsUsersAdapter
|
||||||
|
if (statusActivity != null) {
|
||||||
|
updateStatusActivity(statusActivity)
|
||||||
|
} else {
|
||||||
|
interactUsersAdapter.setUsers(null)
|
||||||
|
interactUsersAdapter.setCounts(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interactUsersAdapter.itemCount > 0) {
|
||||||
|
itemView.countsUsers.visibility = View.VISIBLE
|
||||||
|
itemView.countsUsersHeightHolder.visibility = View.INVISIBLE
|
||||||
|
} else {
|
||||||
|
itemView.countsUsers.visibility = View.GONE
|
||||||
|
itemView.countsUsersHeightHolder.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
val media = status.media
|
||||||
|
|
||||||
|
if (media?.isEmpty() != false) {
|
||||||
|
itemView.mediaPreviewContainer.visibility = View.GONE
|
||||||
|
itemView.mediaPreview.visibility = View.GONE
|
||||||
|
itemView.mediaPreviewLoad.visibility = View.GONE
|
||||||
|
itemView.mediaPreview.displayMedia()
|
||||||
|
} else if (adapter.isDetailMediaExpanded) {
|
||||||
|
itemView.mediaPreviewContainer.visibility = View.VISIBLE
|
||||||
|
itemView.mediaPreview.visibility = View.VISIBLE
|
||||||
|
itemView.mediaPreviewLoad.visibility = View.GONE
|
||||||
|
itemView.mediaPreview.displayMedia(adapter.requestManager, media = media,
|
||||||
|
accountKey = status.account_key, mediaClickListener = adapter.fragment)
|
||||||
|
} else {
|
||||||
|
itemView.mediaPreviewContainer.visibility = View.VISIBLE
|
||||||
|
itemView.mediaPreview.visibility = View.GONE
|
||||||
|
itemView.mediaPreviewLoad.visibility = View.VISIBLE
|
||||||
|
itemView.mediaPreview.displayMedia()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TwitterCardUtils.isCardSupported(status)) {
|
||||||
|
val size = TwitterCardUtils.getCardSize(status.card!!)
|
||||||
|
|
||||||
|
if (size != null) {
|
||||||
|
itemView.twitterCard.setCardSize(size.x, size.y)
|
||||||
|
} else {
|
||||||
|
itemView.twitterCard.setCardSize(0, 0)
|
||||||
|
}
|
||||||
|
val vc = TwitterCardViewFactory.from(status)
|
||||||
|
itemView.twitterCard.viewController = vc
|
||||||
|
if (vc != null) {
|
||||||
|
itemView.twitterCard.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
itemView.twitterCard.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
itemView.twitterCard.viewController = null
|
||||||
|
itemView.twitterCard.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuUtils.setupForStatus(context, itemView.menuBar.menu, fragment.preferences, twitter,
|
||||||
|
colorNameManager, status, adapter.statusAccount!!)
|
||||||
|
|
||||||
|
|
||||||
|
val lang = status.lang
|
||||||
|
if (CheckUtils.isValidLocale(lang) && account.isOfficial(context)) {
|
||||||
|
val locale = Locale(lang)
|
||||||
|
translateContainer.visibility = View.VISIBLE
|
||||||
|
if (translation != null) {
|
||||||
|
translateLabelView.text = context.getString(R.string.label_translation)
|
||||||
|
translateResultView.visibility = View.VISIBLE
|
||||||
|
translateChangeLanguageView.visibility = View.VISIBLE
|
||||||
|
translateResultView.text = translation.text
|
||||||
|
} else {
|
||||||
|
translateLabelView.text = context.getString(R.string.label_translate_from_language,
|
||||||
|
locale.displayLanguage)
|
||||||
|
translateResultView.visibility = View.GONE
|
||||||
|
translateChangeLanguageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
translateLabelView.setText(R.string.unknown_language)
|
||||||
|
translateContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
textView.setTextIsSelectable(true)
|
||||||
|
translateResultView.setTextIsSelectable(true)
|
||||||
|
|
||||||
|
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
itemView.quotedText.movementMethod = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
val status = adapter.getStatus(layoutPosition)
|
||||||
|
val fragment = adapter.fragment
|
||||||
|
val preferences = fragment.preferences
|
||||||
|
when (v) {
|
||||||
|
itemView.mediaPreviewLoad -> {
|
||||||
|
if (adapter.sensitiveContentEnabled || !status.is_possibly_sensitive) {
|
||||||
|
adapter.isDetailMediaExpanded = true
|
||||||
|
} else {
|
||||||
|
val f = StatusFragment.LoadSensitiveImageConfirmDialogFragment()
|
||||||
|
f.show(fragment.childFragmentManager, "load_sensitive_image_confirm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemView.profileContainer -> {
|
||||||
|
val activity = fragment.activity
|
||||||
|
IntentUtils.openUserProfile(activity, status.account_key, status.user_key,
|
||||||
|
status.user_screen_name, status.extras?.user_statusnet_profile_url,
|
||||||
|
preferences[newDocumentApiKey], null)
|
||||||
|
}
|
||||||
|
retweetedByView -> {
|
||||||
|
if (status.retweet_id != null) {
|
||||||
|
IntentUtils.openUserProfile(adapter.context, status.account_key,
|
||||||
|
status.retweeted_by_user_key, status.retweeted_by_user_screen_name,
|
||||||
|
null, preferences[newDocumentApiKey], null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locationView -> {
|
||||||
|
val location = status.location
|
||||||
|
if (!ParcelableLocationUtils.isValidLocation(location)) return
|
||||||
|
IntentUtils.openMap(adapter.context, location.latitude, location.longitude)
|
||||||
|
}
|
||||||
|
itemView.quotedView -> {
|
||||||
|
val quotedId = status.quoted_id ?: return
|
||||||
|
IntentUtils.openStatus(adapter.context, status.account_key, quotedId)
|
||||||
|
}
|
||||||
|
translateLabelView -> {
|
||||||
|
fragment.loadTranslation(adapter.status)
|
||||||
|
}
|
||||||
|
translateChangeLanguageView -> {
|
||||||
|
fragment.openTranslationDestinationChooser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||||
|
val layoutPosition = layoutPosition
|
||||||
|
if (layoutPosition < 0) return false
|
||||||
|
val fragment = adapter.fragment
|
||||||
|
val status = adapter.getStatus(layoutPosition)
|
||||||
|
val preferences = fragment.preferences
|
||||||
|
val twitter = fragment.twitterWrapper
|
||||||
|
val manager = fragment.userColorNameManager
|
||||||
|
val activity = fragment.activity
|
||||||
|
return MenuUtils.handleStatusClick(activity, fragment, fragment.childFragmentManager,
|
||||||
|
preferences, manager, twitter, status, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun updateStatusActivity(activity: StatusFragment.StatusActivity) {
|
||||||
|
val adapter = itemView.countsUsers.adapter as CountsUsersAdapter
|
||||||
|
adapter.setUsers(activity.retweeters)
|
||||||
|
adapter.setCounts(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initViews() {
|
||||||
|
itemView.menuBar.setOnMenuItemClickListener(this)
|
||||||
|
val fragment = adapter.fragment
|
||||||
|
val activity = fragment.activity
|
||||||
|
val inflater = activity.menuInflater
|
||||||
|
val menu = itemView.menuBar.menu
|
||||||
|
inflater.inflate(R.menu.menu_detail_status, menu)
|
||||||
|
val favoriteItem = menu.findItem(R.id.favorite)
|
||||||
|
val provider = MenuItemCompat.getActionProvider(favoriteItem)
|
||||||
|
if (provider is FavoriteItemProvider) {
|
||||||
|
val defaultColor = ThemeUtils.getActionIconColor(activity)
|
||||||
|
provider.setDefaultColor(defaultColor)
|
||||||
|
val favoriteHighlight = ContextCompat.getColor(activity, R.color.highlight_favorite)
|
||||||
|
val likeHighlight = ContextCompat.getColor(activity, R.color.highlight_like)
|
||||||
|
val useStar = adapter.useStarsForLikes
|
||||||
|
provider.setActivatedColor(if (useStar) favoriteHighlight else likeHighlight)
|
||||||
|
provider.setIcon(if (useStar) R.drawable.ic_action_star else R.drawable.ic_action_heart)
|
||||||
|
provider.setUseStar(useStar)
|
||||||
|
provider.init(itemView.menuBar, favoriteItem)
|
||||||
|
}
|
||||||
|
ThemeUtils.wrapMenuIcon(itemView.menuBar, excludeGroups = Constants.MENU_GROUP_STATUS_SHARE)
|
||||||
|
itemView.mediaPreviewLoad.setOnClickListener(this)
|
||||||
|
itemView.profileContainer.setOnClickListener(this)
|
||||||
|
retweetedByView.setOnClickListener(this)
|
||||||
|
locationView.setOnClickListener(this)
|
||||||
|
itemView.quotedView.setOnClickListener(this)
|
||||||
|
translateLabelView.setOnClickListener(this)
|
||||||
|
translateChangeLanguageView.setOnClickListener(this)
|
||||||
|
|
||||||
|
val textSize = adapter.textSize
|
||||||
|
|
||||||
|
nameView.setPrimaryTextSize(textSize * 1.25f)
|
||||||
|
nameView.setSecondaryTextSize(textSize * 0.85f)
|
||||||
|
summaryView.textSize = textSize * 1.25f
|
||||||
|
textView.textSize = textSize * 1.25f
|
||||||
|
|
||||||
|
itemView.quotedName.setPrimaryTextSize(textSize * 1.25f)
|
||||||
|
itemView.quotedName.setSecondaryTextSize(textSize * 0.85f)
|
||||||
|
itemView.quotedText.textSize = textSize * 1.25f
|
||||||
|
|
||||||
|
locationView.textSize = textSize * 0.85f
|
||||||
|
itemView.timeSource.textSize = textSize * 0.85f
|
||||||
|
translateLabelView.textSize = textSize * 0.85f
|
||||||
|
translateResultView.textSize = textSize * 1.05f
|
||||||
|
|
||||||
|
itemView.countsUsersHeightHolder.count.textSize = textSize * 1.25f
|
||||||
|
itemView.countsUsersHeightHolder.label.textSize = textSize * 0.85f
|
||||||
|
|
||||||
|
nameView.nameFirst = adapter.nameFirst
|
||||||
|
itemView.quotedName.nameFirst = adapter.nameFirst
|
||||||
|
|
||||||
|
itemView.mediaPreview.style = adapter.mediaPreviewStyle
|
||||||
|
itemView.quotedMediaPreview.style = adapter.mediaPreviewStyle
|
||||||
|
|
||||||
|
itemView.text.customSelectionActionModeCallback = StatusActionModeCallback(itemView.text, activity)
|
||||||
|
itemView.profileImage.style = adapter.profileImageStyle
|
||||||
|
|
||||||
|
val layoutManager = LinearLayoutManager(adapter.context)
|
||||||
|
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
|
||||||
|
itemView.countsUsers.layoutManager = layoutManager
|
||||||
|
|
||||||
|
val countsUsersAdapter = CountsUsersAdapter(fragment, adapter)
|
||||||
|
itemView.countsUsers.adapter = countsUsersAdapter
|
||||||
|
val resources = activity.resources
|
||||||
|
itemView.countsUsers.addItemDecoration(SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.element_spacing_normal)))
|
||||||
|
|
||||||
|
// Apply font families
|
||||||
|
nameView.applyFontFamily(adapter.lightFont)
|
||||||
|
summaryView.applyFontFamily(adapter.lightFont)
|
||||||
|
textView.applyFontFamily(adapter.lightFont)
|
||||||
|
itemView.quotedName.applyFontFamily(adapter.lightFont)
|
||||||
|
itemView.quotedText.applyFontFamily(adapter.lightFont)
|
||||||
|
itemView.locationView.applyFontFamily(adapter.lightFont)
|
||||||
|
translateLabelView.applyFontFamily(adapter.lightFont)
|
||||||
|
translateResultView.applyFontFamily(adapter.lightFont)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class CountsUsersAdapter(
|
||||||
|
private val fragment: StatusFragment,
|
||||||
|
private val statusAdapter: StatusDetailsAdapter
|
||||||
|
) : BaseRecyclerViewAdapter<RecyclerView.ViewHolder>(statusAdapter.context, Glide.with(fragment)) {
|
||||||
|
|
||||||
|
private val inflater = LayoutInflater.from(statusAdapter.context)
|
||||||
|
|
||||||
|
private var counts: List<LabeledCount>? = null
|
||||||
|
private var users: List<ParcelableUser>? = null
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder.itemViewType) {
|
||||||
|
ITEM_VIEW_TYPE_USER -> {
|
||||||
|
(holder as ProfileImageViewHolder).displayUser(getUser(position)!!)
|
||||||
|
}
|
||||||
|
ITEM_VIEW_TYPE_COUNT -> {
|
||||||
|
(holder as CountViewHolder).displayCount(getCount(position)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCount(position: Int): LabeledCount? {
|
||||||
|
if (counts == null) return null
|
||||||
|
if (position < countItemsCount) {
|
||||||
|
return counts!![position]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return countItemsCount + usersCount
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
val countItemsCount = countItemsCount
|
||||||
|
if (position < countItemsCount) {
|
||||||
|
return ITEM_VIEW_TYPE_COUNT
|
||||||
|
}
|
||||||
|
return ITEM_VIEW_TYPE_USER
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
when (viewType) {
|
||||||
|
ITEM_VIEW_TYPE_USER -> return ProfileImageViewHolder(this, inflater.inflate(R.layout.adapter_item_status_interact_user, parent, false))
|
||||||
|
ITEM_VIEW_TYPE_COUNT -> return CountViewHolder(this, inflater.inflate(R.layout.adapter_item_status_count_label, parent, false))
|
||||||
|
}
|
||||||
|
throw UnsupportedOperationException("Unsupported viewType " + viewType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUsers(users: List<ParcelableUser>?) {
|
||||||
|
this.users = users
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun setCounts(activity: StatusFragment.StatusActivity?) {
|
||||||
|
if (activity != null) {
|
||||||
|
val counts = ArrayList<LabeledCount>()
|
||||||
|
val replyCount = activity.replyCount
|
||||||
|
if (replyCount > 0) {
|
||||||
|
counts.add(LabeledCount(KEY_REPLY_COUNT, replyCount))
|
||||||
|
}
|
||||||
|
val retweetCount = activity.retweetCount
|
||||||
|
if (retweetCount > 0) {
|
||||||
|
counts.add(LabeledCount(KEY_RETWEET_COUNT, retweetCount))
|
||||||
|
}
|
||||||
|
val favoriteCount = activity.favoriteCount
|
||||||
|
if (favoriteCount > 0) {
|
||||||
|
counts.add(LabeledCount(KEY_FAVORITE_COUNT, favoriteCount))
|
||||||
|
}
|
||||||
|
this.counts = counts
|
||||||
|
} else {
|
||||||
|
counts = null
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCounts(status: ParcelableStatus?) {
|
||||||
|
if (status != null) {
|
||||||
|
val counts = ArrayList<LabeledCount>()
|
||||||
|
if (status.reply_count > 0) {
|
||||||
|
counts.add(LabeledCount(KEY_REPLY_COUNT, status.reply_count))
|
||||||
|
}
|
||||||
|
if (status.retweet_count > 0) {
|
||||||
|
counts.add(LabeledCount(KEY_RETWEET_COUNT, status.retweet_count))
|
||||||
|
}
|
||||||
|
if (status.favorite_count > 0) {
|
||||||
|
counts.add(LabeledCount(KEY_FAVORITE_COUNT, status.favorite_count))
|
||||||
|
}
|
||||||
|
this.counts = counts
|
||||||
|
} else {
|
||||||
|
counts = null
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
val countItemsCount: Int
|
||||||
|
get() {
|
||||||
|
if (counts == null) return 0
|
||||||
|
return counts!!.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private val usersCount: Int
|
||||||
|
get() {
|
||||||
|
if (users == null) return 0
|
||||||
|
return users!!.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyItemClick(position: Int) {
|
||||||
|
when (getItemViewType(position)) {
|
||||||
|
ITEM_VIEW_TYPE_COUNT -> {
|
||||||
|
val count = getCount(position)
|
||||||
|
val status = statusAdapter.status
|
||||||
|
if (count == null || status == null) return
|
||||||
|
when (count.type) {
|
||||||
|
KEY_RETWEET_COUNT -> {
|
||||||
|
IntentUtils.openStatusRetweeters(context, status.account_key,
|
||||||
|
status.originalId)
|
||||||
|
}
|
||||||
|
KEY_FAVORITE_COUNT -> {
|
||||||
|
IntentUtils.openStatusFavoriters(context, status.account_key,
|
||||||
|
status.originalId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ITEM_VIEW_TYPE_USER -> {
|
||||||
|
fragment.onUserClick(getUser(position)!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUser(position: Int): ParcelableUser? {
|
||||||
|
val countItemsCount = countItemsCount
|
||||||
|
if (users == null || position < countItemsCount) return null
|
||||||
|
return users!![position - countItemsCount]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class ProfileImageViewHolder(private val adapter: CountsUsersAdapter, itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||||
|
private val profileImageView = itemView.findViewById<ProfileImageView>(R.id.profileImage)
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun displayUser(item: ParcelableUser) {
|
||||||
|
val context = adapter.context
|
||||||
|
val requestManager = adapter.requestManager
|
||||||
|
requestManager.loadProfileImage(context, item, adapter.profileImageStyle,
|
||||||
|
profileImageView.cornerRadius, profileImageView.cornerRadiusRatio,
|
||||||
|
adapter.profileImageSize).into(profileImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
adapter.notifyItemClick(layoutPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class CountViewHolder(
|
||||||
|
private val adapter: CountsUsersAdapter,
|
||||||
|
itemView: View
|
||||||
|
) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener(this)
|
||||||
|
val textSize = adapter.textSize
|
||||||
|
itemView.count.textSize = textSize * 1.25f
|
||||||
|
itemView.label.textSize = textSize * 0.85f
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
adapter.notifyItemClick(layoutPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun displayCount(count: LabeledCount) {
|
||||||
|
val label: String
|
||||||
|
when (count.type) {
|
||||||
|
KEY_REPLY_COUNT -> {
|
||||||
|
label = adapter.context.getString(R.string.replies)
|
||||||
|
}
|
||||||
|
KEY_RETWEET_COUNT -> {
|
||||||
|
label = adapter.context.getString(R.string.count_label_retweets)
|
||||||
|
}
|
||||||
|
KEY_FAVORITE_COUNT -> {
|
||||||
|
label = adapter.context.getString(R.string.title_favorites)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw UnsupportedOperationException("Unsupported type " + count.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemView.count.text = Utils.getLocalizedNumber(Locale.getDefault(), count.count)
|
||||||
|
itemView.label.text = label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LabeledCount(var type: Int, var count: Long)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ITEM_VIEW_TYPE_USER = 1
|
||||||
|
private val ITEM_VIEW_TYPE_COUNT = 2
|
||||||
|
|
||||||
|
private val KEY_REPLY_COUNT = 1
|
||||||
|
private val KEY_RETWEET_COUNT = 2
|
||||||
|
private val KEY_FAVORITE_COUNT = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DetailStatusLinkClickHandler(
|
||||||
|
context: Context,
|
||||||
|
manager: MultiSelectManager,
|
||||||
|
private val adapter: StatusDetailsAdapter,
|
||||||
|
preferences: SharedPreferences
|
||||||
|
) : StatusLinkClickHandler(context, manager, preferences) {
|
||||||
|
|
||||||
|
override fun onLinkClick(link: String, orig: String?, accountKey: UserKey?,
|
||||||
|
extraId: Long, type: Int, sensitive: Boolean, start: Int, end: Int): Boolean {
|
||||||
|
val position = extraId.toInt()
|
||||||
|
val current = getCurrentMedia(link, position)
|
||||||
|
if (current != null && !current.open_browser) {
|
||||||
|
expandOrOpenMedia(current)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onLinkClick(link, orig, accountKey, extraId, type, sensitive, start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expandOrOpenMedia(current: ParcelableMedia) {
|
||||||
|
if (adapter.isDetailMediaExpanded) {
|
||||||
|
IntentUtils.openMedia(adapter.context, adapter.status!!, current,
|
||||||
|
preferences[newDocumentApiKey], preferences[displaySensitiveContentsKey])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adapter.isDetailMediaExpanded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isMedia(link: String, extraId: Long): Boolean {
|
||||||
|
val current = getCurrentMedia(link, extraId.toInt())
|
||||||
|
return current != null && !current.open_browser
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrentMedia(link: String, extraId: Int): ParcelableMedia? {
|
||||||
|
val status = adapter.getStatus(extraId)
|
||||||
|
val media = ParcelableMediaUtils.getAllMedia(status)
|
||||||
|
return findByLink(media, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
|
||||||
|
if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
|
||||||
|
outRect.set(spacing, 0, 0, 0)
|
||||||
|
} else {
|
||||||
|
outRect.set(0, 0, spacing, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -183,18 +183,23 @@
|
|||||||
tools:text="@string/sample_status_text"
|
tools:text="@string/sample_status_text"
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
android:id="@+id/translateContainer"
|
android:id="@+id/translateContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/text"
|
android:layout_below="@+id/text"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:visibility="gone">
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<org.mariotaku.twidere.view.ActionIconThemedTextView
|
<org.mariotaku.twidere.view.ActionIconThemedTextView
|
||||||
android:id="@+id/translateLabel"
|
android:id="@+id/translateLabel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_toLeftOf="@+id/translateChangeLanguage"
|
||||||
|
android:layout_toStartOf="@+id/translateChangeLanguage"
|
||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
android:drawableLeft="@drawable/ic_indicator_web"
|
android:drawableLeft="@drawable/ic_indicator_web"
|
||||||
android:drawablePadding="4dp"
|
android:drawablePadding="4dp"
|
||||||
@ -210,11 +215,33 @@
|
|||||||
android:id="@+id/translateResult"
|
android:id="@+id/translateResult"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/translateLabel"
|
||||||
android:padding="@dimen/element_spacing_normal"
|
android:padding="@dimen/element_spacing_normal"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:text="@string/sample_status_text"/>
|
tools:text="@string/sample_status_text"/>
|
||||||
</LinearLayout>
|
|
||||||
|
<org.mariotaku.twidere.view.FixedTextView
|
||||||
|
android:id="@+id/translateChangeLanguage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingBottom="@dimen/element_spacing_normal"
|
||||||
|
android:paddingEnd="@dimen/element_spacing_large"
|
||||||
|
android:paddingLeft="@dimen/element_spacing_normal"
|
||||||
|
android:paddingRight="@dimen/element_spacing_large"
|
||||||
|
android:paddingStart="@dimen/element_spacing_normal"
|
||||||
|
android:paddingTop="@dimen/element_spacing_normal"
|
||||||
|
android:text="@string/action_change_language"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/mediaPreviewContainer"
|
android:id="@+id/mediaPreviewContainer"
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
<!-- [verb] Action for cancelling (deleting) a retweet-->
|
<!-- [verb] Action for cancelling (deleting) a retweet-->
|
||||||
<string name="action_cancel_retweet">Cancel retweet</string>
|
<string name="action_cancel_retweet">Cancel retweet</string>
|
||||||
<string name="action_center">Center</string>
|
<string name="action_center">Center</string>
|
||||||
|
<string name="action_change_language">Change language</string>
|
||||||
<string name="action_clear">Clear</string>
|
<string name="action_clear">Clear</string>
|
||||||
<string name="action_clear_messages">Clear messages</string>
|
<string name="action_clear_messages">Clear messages</string>
|
||||||
<string name="action_comment">Comment</string>
|
<string name="action_comment">Comment</string>
|
||||||
@ -687,7 +688,7 @@
|
|||||||
<string name="message_toast_no_account_permission">Account permission is required</string>
|
<string name="message_toast_no_account_permission">Account permission is required</string>
|
||||||
<string name="message_toast_no_account_selected">No account selected</string>
|
<string name="message_toast_no_account_selected">No account selected</string>
|
||||||
<string name="message_toast_no_user_selected">No user selected</string>
|
<string name="message_toast_no_user_selected">No user selected</string>
|
||||||
<string name="message_toast_notification_enabled_hint">Only available when streaming is on, and not 100% reliable.</string>
|
<string name="message_toast_notification_enabled_hint">Only available when streaming is on, and not 100% reliable.</string>
|
||||||
<string name="message_toast_press_again_to_close">Press again to close</string>
|
<string name="message_toast_press_again_to_close">Press again to close</string>
|
||||||
<string name="message_toast_profile_background_image_updated">Profile background image updated</string>
|
<string name="message_toast_profile_background_image_updated">Profile background image updated</string>
|
||||||
<string name="message_toast_profile_banner_image_updated">Profile banner image updated</string>
|
<string name="message_toast_profile_banner_image_updated">Profile banner image updated</string>
|
||||||
@ -1121,7 +1122,7 @@
|
|||||||
<!-- [noun] Accessibility label for retweet icon -->
|
<!-- [noun] Accessibility label for retweet icon -->
|
||||||
<string name="status_type_retweet">Retweet</string>
|
<string name="status_type_retweet">Retweet</string>
|
||||||
|
|
||||||
<string name="stream_summary_notification_users">Users turned "Notifications" on, not 100% reliable.</string>
|
<string name="stream_summary_notification_users">Users turned "Notifications" on, not 100% reliable.</string>
|
||||||
<string name="stream_type_home">Home</string>
|
<string name="stream_type_home">Home</string>
|
||||||
<string name="stream_type_interactions">Interactions</string>
|
<string name="stream_type_interactions">Interactions</string>
|
||||||
<string name="stream_type_messages">Messages</string>
|
<string name="stream_type_messages">Messages</string>
|
||||||
|
@ -47,9 +47,6 @@
|
|||||||
android:summary="@string/preference_summary_trends_location"
|
android:summary="@string/preference_summary_trends_location"
|
||||||
android:title="@string/preference_title_trends_location"/>
|
android:title="@string/preference_title_trends_location"/>
|
||||||
|
|
||||||
<org.mariotaku.twidere.preference.TranslationDestinationPreference
|
|
||||||
android:key="translation_destination"
|
|
||||||
android:title="@string/translation_destination"/>
|
|
||||||
<org.mariotaku.twidere.preference.ComponentStatePreference
|
<org.mariotaku.twidere.preference.ComponentStatePreference
|
||||||
android:name="org.mariotaku.twidere.activity.WebLinkHandlerActivity"
|
android:name="org.mariotaku.twidere.activity.WebLinkHandlerActivity"
|
||||||
android:key="twitter_link_handler"
|
android:key="twitter_link_handler"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user