Compare commits
17 Commits
4eb56b713c
...
2ad4d2bf4f
Author | SHA1 | Date |
---|---|---|
Karol Kosek | 2ad4d2bf4f | |
Alibek Omarov | e13a02e219 | |
Alibek Omarov | 1805a99a6c | |
Alibek Omarov | 866e725d9c | |
Alibek Omarov | 7295ace378 | |
Alibek Omarov | b5ffbfe000 | |
Alibek Omarov | e394eb6a5c | |
Alibek Omarov | ed2053b00f | |
Alibek Omarov | 5f198cde13 | |
Alibek Omarov | 629a336e7e | |
Alibek Omarov | 3fff54c1f4 | |
Alibek Omarov | c5cedbe85f | |
Alibek Omarov | fabf921569 | |
Alibek Omarov | 9bdd4e49ca | |
Alibek Omarov | 7435d38d7b | |
Alibek Omarov | daabaf2bdb | |
Alibek Omarov | db18d55619 |
|
@ -34,7 +34,7 @@
|
|||
<string name="notification_favourite_format">%s dodał Twój post do ulubionych</string>
|
||||
<string name="action_hide_reblogs">Ukryj powtórzenia</string>
|
||||
<string name="action_show_reblogs">Pokaż powtórzenia</string>
|
||||
<string name="action_unreblog">Usuń powtórzenia</string>
|
||||
<string name="action_unreblog">Usuń powtórzenie</string>
|
||||
<string name="action_open_reblogged_by">Pokaż powtórzenia</string>
|
||||
<string name="notification_boost_name">Powtórzenia</string>
|
||||
<string name="action_open_reblogger">Otwórz konto osoby powtarzającej</string>
|
||||
|
@ -61,4 +61,17 @@
|
|||
<string name="error_sender_account_gone">Wysyłanie postu nie powiodło się.</string>
|
||||
<string name="pref_title_alway_open_spoiler">Zawsze rozwijaj posty z ostrzeżeniami o zawartości</string>
|
||||
<string name="action_access_scheduled_toot">Zaplanowane posty</string>
|
||||
<string name="action_mark_as_read">Oznacz jako przeczytane</string>
|
||||
<string name="action_open_in_external_app">Otwórz w zewnętrznej aplikacji</string>
|
||||
<string name="notification_chat_message_format">%s wysłał(-a) Tobie wiadomość</string>
|
||||
<string name="pref_title_privacy">Prywatność</string>
|
||||
<string name="pref_summary_live_notifications">Może nieco zwiększyć zużycie energii</string>
|
||||
<string name="attachment_type_image">Zdjęcie</string>
|
||||
<string name="attachment_type_video">Wideo</string>
|
||||
<string name="attachment_type_audio">Audio</string>
|
||||
<string name="attachment_type_unknown">Załącznik</string>
|
||||
<string name="link">Odnośnik</string>
|
||||
<string name="status_replied_to_format">Odpowiedź dla %s</string>
|
||||
<string name="chat_our_last_message"><b>Ty</b></string>
|
||||
<string name="pref_title_other">Inne</string>
|
||||
</resources>
|
|
@ -83,12 +83,25 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
|
||||
onEndSearch(url)
|
||||
|
||||
if (accounts.isNotEmpty()) {
|
||||
|
||||
// HACKHACK: Pleroma, remove when search will work normally
|
||||
if (accounts[0].pleroma != null) {
|
||||
val account = accounts.firstOrNull { it.pleroma?.apId == url || it.url == url }
|
||||
|
||||
if (account != null) {
|
||||
viewAccount(account.id)
|
||||
return@subscribe
|
||||
}
|
||||
} else {
|
||||
viewAccount(accounts[0].id)
|
||||
return@subscribe
|
||||
}
|
||||
}
|
||||
|
||||
if (statuses.isNotEmpty()) {
|
||||
viewThread(statuses[0].id, statuses[0].url)
|
||||
return@subscribe
|
||||
} else if (accounts.isNotEmpty()) {
|
||||
viewAccount(accounts[0].id)
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
performUrlFallbackAction(url, lookupFallbackBehavior)
|
||||
|
|
|
@ -79,7 +79,16 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
return intent
|
||||
}
|
||||
|
||||
fun newAvatarIntent(context: Context, url: String): Intent {
|
||||
@JvmStatic
|
||||
fun newIntent(context: Context?, attachment: Attachment): Intent {
|
||||
val intent = Intent(context, ViewMediaActivity::class.java)
|
||||
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS,
|
||||
arrayListOf(AttachmentViewData(attachment, null, null)))
|
||||
intent.putExtra(EXTRA_ATTACHMENT_INDEX, 0)
|
||||
return intent
|
||||
}
|
||||
|
||||
fun newAvatarIntent(context: Context?, url: String): Intent {
|
||||
val intent = Intent(context, ViewMediaActivity::class.java)
|
||||
intent.putExtra(EXTRA_AVATAR_URL, url)
|
||||
return intent
|
||||
|
@ -171,6 +180,11 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
|
||||
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
||||
menu?.findItem(R.id.action_share_media)?.isEnabled = !isCreating
|
||||
|
||||
if(attachments != null) {
|
||||
val isStatus = attachments!!.any { it.statusId != null && it.statusUrl != null }
|
||||
menu?.findItem(R.id.action_open_status)?.isVisible = isStatus
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions
|
|||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.TimestampUtils
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.LinkHelper
|
||||
import com.keylesspalace.tusky.view.MediaPreviewImageView
|
||||
import com.keylesspalace.tusky.viewdata.ChatMessageViewData
|
||||
import java.text.SimpleDateFormat
|
||||
|
@ -40,8 +41,10 @@ class ChatMessagesViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
|||
|
||||
fun setupWithChatMessage(msg: ChatMessageViewData.Concrete, chatActionListener: ChatActionListener, statusDisplayOptions: StatusDisplayOptions, payload: Any?) {
|
||||
if(payload == null) {
|
||||
if(msg.content != null)
|
||||
content.text = msg.content.emojify(msg.emojis, content)
|
||||
if(msg.content != null) {
|
||||
val text = msg.content.emojify(msg.emojis, content)
|
||||
LinkHelper.setClickableText(content, text, null, chatActionListener)
|
||||
}
|
||||
|
||||
setAttachment(msg.attachment, chatActionListener)
|
||||
setCreatedAt(msg.createdAt)
|
||||
|
|
|
@ -267,7 +267,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
|
|||
}
|
||||
|
||||
public final static class AccountResult extends AutocompleteResult {
|
||||
private final Account account;
|
||||
public final Account account;
|
||||
|
||||
public AccountResult(Account account) {
|
||||
this.account = account;
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
|
@ -20,10 +21,6 @@ import android.widget.Toast
|
|||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.BuildConfig
|
||||
import com.keylesspalace.tusky.ViewTagActivity
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import com.keylesspalace.tusky.entity.Chat
|
||||
|
@ -35,9 +32,11 @@ import com.keylesspalace.tusky.repository.ChatRepository
|
|||
import com.keylesspalace.tusky.viewdata.ChatMessageViewData
|
||||
import androidx.arch.core.util.Function
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -49,11 +48,13 @@ import com.bumptech.glide.request.target.CustomTarget
|
|||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.*
|
||||
import com.keylesspalace.tusky.adapter.*
|
||||
import com.keylesspalace.tusky.appstore.*
|
||||
import com.keylesspalace.tusky.components.common.*
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.repository.Placeholder
|
||||
import com.keylesspalace.tusky.repository.TimelineRequestMode
|
||||
import com.keylesspalace.tusky.service.MessageToSend
|
||||
|
@ -65,7 +66,6 @@ import com.mikepenz.iconics.IconicsDrawable
|
|||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
||||
import com.uber.autodispose.android.lifecycle.autoDispose
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
@ -208,7 +208,7 @@ class ChatActivity: BottomSheetActivity(),
|
|||
subscribeToUpdates()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
viewModel.tryFetchStickers = preferences.getBoolean("stickers", false)
|
||||
viewModel.tryFetchStickers = preferences.getBoolean(PrefKeys.STICKERS, false)
|
||||
viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
|
||||
|
||||
setupHeader()
|
||||
|
@ -227,20 +227,22 @@ class ChatActivity: BottomSheetActivity(),
|
|||
.subscribe { event: Event? ->
|
||||
when(event) {
|
||||
is ChatMessageDeliveredEvent -> {
|
||||
if(event.chatMsg.chatId == chatId) {
|
||||
onRefresh()
|
||||
enableButton(sendButton, true, true)
|
||||
enableButton(attachmentButton, true, true)
|
||||
enableButton(stickerButton, haveStickers, haveStickers)
|
||||
editText.text.clear()
|
||||
viewModel.media.value = listOf()
|
||||
}
|
||||
|
||||
sending = false
|
||||
enableSendButton()
|
||||
}
|
||||
}
|
||||
is ChatMessageReceivedEvent -> {
|
||||
if(event.chatMsg.chatId == chatId) {
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tryCache()
|
||||
}
|
||||
|
@ -287,11 +289,14 @@ class ChatActivity: BottomSheetActivity(),
|
|||
viewModel.updateDescription(it.localId, newDescription)
|
||||
}
|
||||
}
|
||||
removeId -> viewModel.removeMediaFromQueue(it)
|
||||
removeId -> {
|
||||
viewModel.removeMediaFromQueue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
imageAttachment.setOnClickListener(onMediaPick)
|
||||
|
@ -314,6 +319,7 @@ class ChatActivity: BottomSheetActivity(),
|
|||
highlightSpans(editText.text, mentionColour)
|
||||
editText.afterTextChanged { editable ->
|
||||
highlightSpans(editable, mentionColour)
|
||||
enableSendButton()
|
||||
}
|
||||
|
||||
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093
|
||||
|
@ -323,6 +329,17 @@ class ChatActivity: BottomSheetActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
private var sending = false
|
||||
private fun enableSendButton() {
|
||||
if(sending)
|
||||
return
|
||||
|
||||
val haveMedia = viewModel.media.value?.isNotEmpty() ?: false
|
||||
val haveText = editText.text.isNotEmpty()
|
||||
|
||||
enableButton(sendButton, haveMedia || haveText, haveMedia || haveText)
|
||||
}
|
||||
|
||||
override fun search(token: String): List<ComposeAutoCompleteAdapter.AutocompleteResult> {
|
||||
return viewModel.searchAutocompleteSuggestions(token)
|
||||
}
|
||||
|
@ -354,11 +371,6 @@ class ChatActivity: BottomSheetActivity(),
|
|||
viewModel.instanceParams.observe { instanceData ->
|
||||
maximumTootCharacters = instanceData.chatLimit
|
||||
}
|
||||
viewModel.haveStickers.observe { haveStickers ->
|
||||
if (haveStickers) {
|
||||
stickerButton.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
viewModel.instanceStickers.observe { stickers ->
|
||||
if(stickers.isNotEmpty()) {
|
||||
haveStickers = true
|
||||
|
@ -369,7 +381,13 @@ class ChatActivity: BottomSheetActivity(),
|
|||
}
|
||||
viewModel.emoji.observe { setEmojiList(it) }
|
||||
viewModel.media.observe {
|
||||
if(it.isNotEmpty()) {
|
||||
val notHaveMedia = it.isEmpty()
|
||||
|
||||
enableSendButton()
|
||||
enableButton(attachmentButton, notHaveMedia, notHaveMedia)
|
||||
enableButton(stickerButton, haveStickers && notHaveMedia, haveStickers && notHaveMedia)
|
||||
|
||||
if(!notHaveMedia) {
|
||||
val media = it[0]
|
||||
|
||||
when(media.type) {
|
||||
|
@ -461,22 +479,7 @@ class ChatActivity: BottomSheetActivity(),
|
|||
emojiBehavior = BottomSheetBehavior.from(emojiView)
|
||||
stickerBehavior = BottomSheetBehavior.from(stickerKeyboard)
|
||||
|
||||
sendButton.setOnClickListener {
|
||||
val media = viewModel.getSingleMedia()
|
||||
|
||||
serviceClient.sendChatMessage( MessageToSend(
|
||||
editText.text.toString(),
|
||||
media?.id,
|
||||
media?.uri?.toString(),
|
||||
accountManager.activeAccount!!.id,
|
||||
this.chatId,
|
||||
0
|
||||
))
|
||||
|
||||
enableButton(sendButton, false, false)
|
||||
enableButton(attachmentButton, false, false)
|
||||
enableButton(stickerButton, false, false)
|
||||
}
|
||||
sendButton.setOnClickListener { onSendClicked() }
|
||||
|
||||
attachmentButton.setOnClickListener { openPickDialog() }
|
||||
emojiButton.setOnClickListener { showEmojis() }
|
||||
|
@ -502,6 +505,26 @@ class ChatActivity: BottomSheetActivity(),
|
|||
actionPhotoPick.setOnClickListener { onMediaPick() }
|
||||
}
|
||||
|
||||
private fun onSendClicked() {
|
||||
val media = viewModel.getSingleMedia()
|
||||
|
||||
serviceClient.sendChatMessage(MessageToSend(
|
||||
editText.text.toString(),
|
||||
media?.id,
|
||||
media?.uri?.toString(),
|
||||
accountManager.activeAccount!!.id,
|
||||
this.chatId,
|
||||
0
|
||||
))
|
||||
|
||||
sending = true
|
||||
editText.text.clear()
|
||||
viewModel.media.value = listOf()
|
||||
enableButton(sendButton, false, false)
|
||||
enableButton(attachmentButton, false, false)
|
||||
enableButton(stickerButton, false, false)
|
||||
}
|
||||
|
||||
private fun openPickDialog() {
|
||||
if (addMediaBehavior.state == BottomSheetBehavior.STATE_HIDDEN || addMediaBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
addMediaBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
@ -652,8 +675,6 @@ class ChatActivity: BottomSheetActivity(),
|
|||
}
|
||||
}
|
||||
displayTransientError(errorId)
|
||||
} else {
|
||||
enableButton(attachmentButton, false, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -862,13 +883,13 @@ class ChatActivity: BottomSheetActivity(),
|
|||
FetchEnd.TOP -> {
|
||||
updateMessages(msgs, fullFetch)
|
||||
|
||||
val pos = msgs.indexOfFirst { it.isRight() }
|
||||
val last = msgs.indexOfFirst { it.isRight() }
|
||||
|
||||
mastodonApi.markChatAsRead(chatId, msgs[pos].asRight().id)
|
||||
mastodonApi.markChatAsRead(chatId, msgs[last].asRight().id)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
||||
.subscribe({
|
||||
Log.d(TAG, "Marked new messages as read up to ${msgs[pos].asRight().id}")
|
||||
Log.d(TAG, "Marked new messages as read up to ${msgs[last].asRight().id}")
|
||||
}, {
|
||||
Log.d(TAG, "Failed to mark messages as read", it)
|
||||
})
|
||||
|
@ -980,11 +1001,31 @@ class ChatActivity: BottomSheetActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
Log.d(TAG, event.toString())
|
||||
if(event.action == KeyEvent.ACTION_DOWN) {
|
||||
if (event.isCtrlPressed) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
// send message by pressing CTRL + ENTER
|
||||
onSendClicked()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed() {
|
||||
// Acting like a teen: deliberately ignoring parent.
|
||||
if (addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
if (addMediaBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
|
||||
emojiBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
|
||||
stickerBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
|
||||
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
@ -1039,6 +1080,28 @@ class ChatActivity: BottomSheetActivity(),
|
|||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onViewMedia(position: Int, view: View?) {
|
||||
val attachment = msgs[position].asRight().attachment!!
|
||||
|
||||
when(attachment.type) {
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.AUDIO, Attachment.Type.IMAGE -> {
|
||||
val intent = ViewMediaActivity.newIntent(this, attachment)
|
||||
if(view != null) {
|
||||
val url = attachment.url
|
||||
ViewCompat.setTransitionName(view, url)
|
||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, view, url)
|
||||
|
||||
startActivity(intent, options.toBundle())
|
||||
} else {
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
Attachment.Type.UNKNOWN -> {
|
||||
viewUrl(attachment.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MEDIA_PICK_RESULT = 1
|
||||
private const val MEDIA_TAKE_PHOTO_RESULT = 2
|
||||
|
|
|
@ -252,9 +252,13 @@ open class CommonComposeViewModel(
|
|||
when (token[0]) {
|
||||
'@' -> {
|
||||
return try {
|
||||
api.searchAccounts(query = token.substring(1), limit = 10)
|
||||
val acct = token.substring(1)
|
||||
api.searchAccounts(query = acct, resolve = true, limit = 10)
|
||||
.blockingGet()
|
||||
.map { ComposeAutoCompleteAdapter.AccountResult(it) }
|
||||
.filter {
|
||||
it.account.username.startsWith(acct, ignoreCase = true)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, String.format("Autocomplete search for %s failed.", token), e)
|
||||
emptyList()
|
||||
|
|
|
@ -148,7 +148,7 @@ class ComposeActivity : BaseActivity(),
|
|||
// do not do anything when not logged in, activity will be finished in super.onCreate() anyway
|
||||
val activeAccount = accountManager.activeAccount ?: return
|
||||
|
||||
viewModel.tryFetchStickers = preferences.getBoolean("stickers", false)
|
||||
viewModel.tryFetchStickers = preferences.getBoolean(PrefKeys.STICKERS, false)
|
||||
viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
|
||||
setupAvatar(preferences, activeAccount)
|
||||
val mediaAdapter = MediaPreviewAdapter(
|
||||
|
@ -1161,7 +1161,8 @@ class ComposeActivity : BaseActivity(),
|
|||
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||
previewBehavior.state == BottomSheetBehavior.STATE_HIDDEN) {
|
||||
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
|
|
@ -98,6 +98,7 @@ data class StringField (
|
|||
)
|
||||
|
||||
data class PleromaAccount(
|
||||
@SerializedName("ap_id") val apId: String? = null,
|
||||
@SerializedName("accepts_chat_messages") val acceptsChatMessages: Boolean? = null,
|
||||
@SerializedName("is_moderator") val isModerator: Boolean? = null,
|
||||
@SerializedName("is_admin") val isAdmin: Boolean? = null
|
||||
|
|
|
@ -13,6 +13,7 @@ import androidx.lifecycle.Lifecycle
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||
import at.connyduck.sparkbutton.helpers.Utils
|
||||
import com.keylesspalace.tusky.BottomSheetActivity
|
||||
import com.keylesspalace.tusky.PostLookupFallbackBehavior
|
||||
import com.keylesspalace.tusky.R
|
||||
|
@ -95,8 +96,13 @@ class ChatsFragment : BaseFragment(), Injectable, RefreshableFragment, Reselecta
|
|||
if (isAdded) {
|
||||
Log.d(TAG, "onInserted");
|
||||
adapter.notifyItemRangeInserted(position, count)
|
||||
if (position == 0 && context != null) {
|
||||
recyclerView.scrollToPosition(0)
|
||||
// scroll up when new items at the top are loaded while being in the first position
|
||||
// https://github.com/tuskyapp/Tusky/pull/1905#issuecomment-677819724
|
||||
if (position == 0 && context != null && adapter.itemCount != count) {
|
||||
if (isSwipeToRefreshEnabled)
|
||||
recyclerView.scrollBy(0, Utils.dpToPx(context!!, -30));
|
||||
else
|
||||
recyclerView.scrollToPosition(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -426,9 +426,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
|
|||
break;
|
||||
}
|
||||
case UNKNOWN: {
|
||||
/* Intentionally do nothing. This case is here is to handle when new attachment
|
||||
* types are added to the API before code is added here to handle them. So, the
|
||||
* best fallback is to just show the preview and ignore requests to view them. */
|
||||
onViewUrl(active.getUrl());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,23 +18,55 @@ package com.keylesspalace.tusky.util
|
|||
import android.text.SpannableString
|
||||
import android.text.Spanned
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.widget.MultiAutoCompleteTextView
|
||||
|
||||
class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer {
|
||||
|
||||
private fun isMentionOrHashtagAllowedCharacter(character: Char) : Boolean {
|
||||
return Character.isLetterOrDigit(character) || character == '_' // simple usernames
|
||||
|| character == '-' // extended usernames
|
||||
|| character == '.' // domain dot
|
||||
}
|
||||
|
||||
override fun findTokenStart(text: CharSequence, cursor: Int): Int {
|
||||
if (cursor == 0) {
|
||||
return cursor
|
||||
}
|
||||
var i = cursor
|
||||
var character = text[i - 1]
|
||||
while (i > 0 && character != '@' && character != '#' && character != ':') {
|
||||
// See SpanUtils.MENTION_REGEX
|
||||
if (!Character.isLetterOrDigit(character) && character != '_') {
|
||||
|
||||
while(i > 0 && !(character == '@' || character == '#' || character == ':')) {
|
||||
if(!isMentionOrHashtagAllowedCharacter(character)) {
|
||||
return cursor
|
||||
}
|
||||
|
||||
i--
|
||||
character = if (i == 0) ' ' else text[i - 1]
|
||||
}
|
||||
|
||||
// caught domain name, try search username
|
||||
// don't ask me about this code
|
||||
if(i > 3 && character == '@') {
|
||||
var j = i - 1
|
||||
var character2 = text[i - 2]
|
||||
|
||||
while(j > 0 && character2 != '@') {
|
||||
if(!isMentionOrHashtagAllowedCharacter(character2)) {
|
||||
break
|
||||
}
|
||||
|
||||
j--
|
||||
character2 = if (j == 0) ' ' else text[j - 1]
|
||||
}
|
||||
if(character2 == '@') {
|
||||
i = j
|
||||
character = character2
|
||||
}
|
||||
}
|
||||
|
||||
// Log.d("Tokenizer", "Stopped search at ${character} ${text.substring(i)}")
|
||||
|
||||
if (i < 1
|
||||
|| (character != '@' && character != '#' && character != ':')
|
||||
|| i > 1 && !Character.isWhitespace(text[i - 2])) {
|
||||
|
|
|
@ -18,7 +18,7 @@ private const val TAG_REGEX = "(?:^|[^/)A-Za-z0-9_])#([\\w_]*[\\p{Alpha}_][\\w_]
|
|||
* @see <a href="https://github.com/tootsuite/mastodon/blob/master/app/models/account.rb">
|
||||
* Account#MENTION_RE</a>
|
||||
*/
|
||||
private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z0-9_]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)"
|
||||
private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z\\d_-]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)"
|
||||
|
||||
private const val HTTP_URL_REGEX = "(?:(^|\\b)http://[^\\s]+)"
|
||||
private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)"
|
||||
|
|
|
@ -8,8 +8,8 @@ import kotlinx.android.parcel.Parcelize
|
|||
@Parcelize
|
||||
data class AttachmentViewData(
|
||||
val attachment: Attachment,
|
||||
val statusId: String,
|
||||
val statusUrl: String
|
||||
val statusId: String?,
|
||||
val statusUrl: String?
|
||||
) : Parcelable {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
|
Loading…
Reference in New Issue