Compare commits

...

17 Commits

Author SHA1 Message Date
Karol Kosek 2ad4d2bf4f Translated using Weblate (Polish)
Currently translated at 79.3% (69 of 87 strings)

Translation: Husky/Husky translations
Translate-URL: https://l10n.mentality.rip/projects/husky/husky-translations/pl/
2020-10-09 18:12:14 +02:00
Alibek Omarov e13a02e219 ChatActivity: fix crash, fix sticker button enabling conditions 2020-10-07 01:42:58 +03:00
Alibek Omarov 1805a99a6c ChatActivity: set sendbutton inactive when text or media are empty, fix media menu, fix media button stay inactive after removing media 2020-10-06 15:27:14 +03:00
Alibek Omarov 866e725d9c ComposeTokenizer: try to fix test 3 2020-10-05 23:31:17 +03:00
Alibek Omarov 7295ace378 ChatMessagesAdapter: fix non-clickable URLs 2020-10-05 23:16:46 +03:00
Alibek Omarov b5ffbfe000 ComposeTokenizer: try to fix test 2 2020-10-05 23:08:31 +03:00
Alibek Omarov e394eb6a5c ComposeTokenizer: try to fix test 2020-10-05 22:03:15 +03:00
Alibek Omarov ed2053b00f Account: add optional ap_id field 2020-10-05 21:15:30 +03:00
Alibek Omarov 5f198cde13 BottomSheetActivity: fix account URL search leading to wrong accounts or threads 2020-10-05 20:43:04 +03:00
Alibek Omarov 629a336e7e CommonComposeViewModel: filter all unrelated autocomplete suggestions 2020-10-05 19:51:56 +03:00
Alibek Omarov 3fff54c1f4 ComposeTokenizer: do not search stop after second @ 2020-10-05 19:51:17 +03:00
Alibek Omarov c5cedbe85f SpanUtils: allow extended username format 2020-10-05 19:50:31 +03:00
Alibek Omarov fabf921569 ChatActivity: fix #91 2020-10-05 17:59:40 +03:00
Alibek Omarov 9bdd4e49ca ComposeActivity: fix ignoring opened preview when back pressed 2020-10-05 17:49:57 +03:00
Alibek Omarov 7435d38d7b ChatsFragment: fix list not rendering sometimes 2020-10-05 17:43:56 +03:00
Alibek Omarov daabaf2bdb chats: implement attachment onClick 2020-10-05 17:25:49 +03:00
Alibek Omarov db18d55619 SFragment: open browser if attachment type is unknown 2020-10-05 17:24:49 +03:00
14 changed files with 219 additions and 71 deletions

View File

@ -34,7 +34,7 @@
<string name="notification_favourite_format">%s dodał Twój post do ulubionych</string> <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_hide_reblogs">Ukryj powtórzenia</string>
<string name="action_show_reblogs">Pokaż 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="action_open_reblogged_by">Pokaż powtórzenia</string>
<string name="notification_boost_name">Powtórzenia</string> <string name="notification_boost_name">Powtórzenia</string>
<string name="action_open_reblogger">Otwórz konto osoby powtarzającej</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="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="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_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> </resources>

View File

@ -83,12 +83,25 @@ abstract class BottomSheetActivity : BaseActivity() {
onEndSearch(url) 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()) { if (statuses.isNotEmpty()) {
viewThread(statuses[0].id, statuses[0].url) viewThread(statuses[0].id, statuses[0].url)
return@subscribe return@subscribe
} else if (accounts.isNotEmpty()) {
viewAccount(accounts[0].id)
return@subscribe
} }
performUrlFallbackAction(url, lookupFallbackBehavior) performUrlFallbackAction(url, lookupFallbackBehavior)

View File

@ -79,7 +79,16 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
return intent 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) val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra(EXTRA_AVATAR_URL, url) intent.putExtra(EXTRA_AVATAR_URL, url)
return intent return intent
@ -171,6 +180,11 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
override fun onPrepareOptionsMenu(menu: Menu?): Boolean { override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
menu?.findItem(R.id.action_share_media)?.isEnabled = !isCreating 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 return true
} }

View File

@ -17,6 +17,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.ThemeUtils import com.keylesspalace.tusky.util.ThemeUtils
import com.keylesspalace.tusky.util.TimestampUtils import com.keylesspalace.tusky.util.TimestampUtils
import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.emojify
import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.view.MediaPreviewImageView import com.keylesspalace.tusky.view.MediaPreviewImageView
import com.keylesspalace.tusky.viewdata.ChatMessageViewData import com.keylesspalace.tusky.viewdata.ChatMessageViewData
import java.text.SimpleDateFormat 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?) { fun setupWithChatMessage(msg: ChatMessageViewData.Concrete, chatActionListener: ChatActionListener, statusDisplayOptions: StatusDisplayOptions, payload: Any?) {
if(payload == null) { if(payload == null) {
if(msg.content != null) if(msg.content != null) {
content.text = msg.content.emojify(msg.emojis, content) val text = msg.content.emojify(msg.emojis, content)
LinkHelper.setClickableText(content, text, null, chatActionListener)
}
setAttachment(msg.attachment, chatActionListener) setAttachment(msg.attachment, chatActionListener)
setCreatedAt(msg.createdAt) setCreatedAt(msg.createdAt)

View File

@ -267,7 +267,7 @@ public class ComposeAutoCompleteAdapter extends BaseAdapter
} }
public final static class AccountResult extends AutocompleteResult { public final static class AccountResult extends AutocompleteResult {
private final Account account; public final Account account;
public AccountResult(Account account) { public AccountResult(Account account) {
this.account = account; this.account = account;

View File

@ -12,6 +12,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.view.KeyEvent
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageButton import android.widget.ImageButton
@ -20,10 +21,6 @@ import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting 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.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Chat import com.keylesspalace.tusky.entity.Chat
@ -35,9 +32,11 @@ import com.keylesspalace.tusky.repository.ChatRepository
import com.keylesspalace.tusky.viewdata.ChatMessageViewData import com.keylesspalace.tusky.viewdata.ChatMessageViewData
import androidx.arch.core.util.Function import androidx.arch.core.util.Function
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.inputmethod.InputConnectionCompat import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.inputmethod.InputContentInfoCompat import androidx.core.view.inputmethod.InputContentInfoCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -49,11 +48,13 @@ import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.*
import com.keylesspalace.tusky.adapter.* import com.keylesspalace.tusky.adapter.*
import com.keylesspalace.tusky.appstore.* import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.common.* import com.keylesspalace.tusky.components.common.*
import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog 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.Placeholder
import com.keylesspalace.tusky.repository.TimelineRequestMode import com.keylesspalace.tusky.repository.TimelineRequestMode
import com.keylesspalace.tusky.service.MessageToSend 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.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.sizeDp
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.android.lifecycle.autoDispose import com.uber.autodispose.android.lifecycle.autoDispose
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
@ -208,7 +208,7 @@ class ChatActivity: BottomSheetActivity(),
subscribeToUpdates() subscribeToUpdates()
val preferences = PreferenceManager.getDefaultSharedPreferences(this) 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) viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
setupHeader() setupHeader()
@ -227,21 +227,23 @@ class ChatActivity: BottomSheetActivity(),
.subscribe { event: Event? -> .subscribe { event: Event? ->
when(event) { when(event) {
is ChatMessageDeliveredEvent -> { is ChatMessageDeliveredEvent -> {
onRefresh() if(event.chatMsg.chatId == chatId) {
enableButton(sendButton, true, true) onRefresh()
enableButton(attachmentButton, true, true) enableButton(attachmentButton, true, true)
enableButton(stickerButton, haveStickers, haveStickers) enableButton(stickerButton, haveStickers, haveStickers)
editText.text.clear()
viewModel.media.value = listOf()
}
sending = false
enableSendButton()
}
}
is ChatMessageReceivedEvent -> { is ChatMessageReceivedEvent -> {
onRefresh() if(event.chatMsg.chatId == chatId) {
onRefresh()
}
} }
} }
} }
tryCache() tryCache()
} }
@ -287,11 +289,14 @@ class ChatActivity: BottomSheetActivity(),
viewModel.updateDescription(it.localId, newDescription) viewModel.updateDescription(it.localId, newDescription)
} }
} }
removeId -> viewModel.removeMediaFromQueue(it) removeId -> {
viewModel.removeMediaFromQueue(it)
}
} }
} }
true true
} }
popup.show()
} }
imageAttachment.setOnClickListener(onMediaPick) imageAttachment.setOnClickListener(onMediaPick)
@ -314,6 +319,7 @@ class ChatActivity: BottomSheetActivity(),
highlightSpans(editText.text, mentionColour) highlightSpans(editText.text, mentionColour)
editText.afterTextChanged { editable -> editText.afterTextChanged { editable ->
highlightSpans(editable, mentionColour) highlightSpans(editable, mentionColour)
enableSendButton()
} }
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093 // 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> { override fun search(token: String): List<ComposeAutoCompleteAdapter.AutocompleteResult> {
return viewModel.searchAutocompleteSuggestions(token) return viewModel.searchAutocompleteSuggestions(token)
} }
@ -354,11 +371,6 @@ class ChatActivity: BottomSheetActivity(),
viewModel.instanceParams.observe { instanceData -> viewModel.instanceParams.observe { instanceData ->
maximumTootCharacters = instanceData.chatLimit maximumTootCharacters = instanceData.chatLimit
} }
viewModel.haveStickers.observe { haveStickers ->
if (haveStickers) {
stickerButton.visibility = View.VISIBLE
}
}
viewModel.instanceStickers.observe { stickers -> viewModel.instanceStickers.observe { stickers ->
if(stickers.isNotEmpty()) { if(stickers.isNotEmpty()) {
haveStickers = true haveStickers = true
@ -369,7 +381,13 @@ class ChatActivity: BottomSheetActivity(),
} }
viewModel.emoji.observe { setEmojiList(it) } viewModel.emoji.observe { setEmojiList(it) }
viewModel.media.observe { 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] val media = it[0]
when(media.type) { when(media.type) {
@ -461,22 +479,7 @@ class ChatActivity: BottomSheetActivity(),
emojiBehavior = BottomSheetBehavior.from(emojiView) emojiBehavior = BottomSheetBehavior.from(emojiView)
stickerBehavior = BottomSheetBehavior.from(stickerKeyboard) stickerBehavior = BottomSheetBehavior.from(stickerKeyboard)
sendButton.setOnClickListener { sendButton.setOnClickListener { onSendClicked() }
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)
}
attachmentButton.setOnClickListener { openPickDialog() } attachmentButton.setOnClickListener { openPickDialog() }
emojiButton.setOnClickListener { showEmojis() } emojiButton.setOnClickListener { showEmojis() }
@ -502,6 +505,26 @@ class ChatActivity: BottomSheetActivity(),
actionPhotoPick.setOnClickListener { onMediaPick() } 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() { private fun openPickDialog() {
if (addMediaBehavior.state == BottomSheetBehavior.STATE_HIDDEN || addMediaBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) { if (addMediaBehavior.state == BottomSheetBehavior.STATE_HIDDEN || addMediaBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
addMediaBehavior.state = BottomSheetBehavior.STATE_EXPANDED addMediaBehavior.state = BottomSheetBehavior.STATE_EXPANDED
@ -652,8 +675,6 @@ class ChatActivity: BottomSheetActivity(),
} }
} }
displayTransientError(errorId) displayTransientError(errorId)
} else {
enableButton(attachmentButton, false, false)
} }
} }
} }
@ -862,13 +883,13 @@ class ChatActivity: BottomSheetActivity(),
FetchEnd.TOP -> { FetchEnd.TOP -> {
updateMessages(msgs, fullFetch) 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()) .observeOn(AndroidSchedulers.mainThread())
.autoDispose(this, Lifecycle.Event.ON_DESTROY) .autoDispose(this, Lifecycle.Event.ON_DESTROY)
.subscribe({ .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) Log.d(TAG, "Failed to mark messages as read", it)
}) })
@ -980,14 +1001,34 @@ 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() { override fun onBackPressed() {
// Acting like a teen: deliberately ignoring parent. // Acting like a teen: deliberately ignoring parent.
if (addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED || if (addMediaBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED || emojiBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { stickerBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return return
} }
@ -1039,6 +1080,28 @@ class ChatActivity: BottomSheetActivity(),
startActivity(intent) 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 { companion object {
private const val MEDIA_PICK_RESULT = 1 private const val MEDIA_PICK_RESULT = 1
private const val MEDIA_TAKE_PHOTO_RESULT = 2 private const val MEDIA_TAKE_PHOTO_RESULT = 2

View File

@ -252,9 +252,13 @@ open class CommonComposeViewModel(
when (token[0]) { when (token[0]) {
'@' -> { '@' -> {
return try { return try {
api.searchAccounts(query = token.substring(1), limit = 10) val acct = token.substring(1)
api.searchAccounts(query = acct, resolve = true, limit = 10)
.blockingGet() .blockingGet()
.map { ComposeAutoCompleteAdapter.AccountResult(it) } .map { ComposeAutoCompleteAdapter.AccountResult(it) }
.filter {
it.account.username.startsWith(acct, ignoreCase = true)
}
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, String.format("Autocomplete search for %s failed.", token), e) Log.e(TAG, String.format("Autocomplete search for %s failed.", token), e)
emptyList() emptyList()

View File

@ -148,7 +148,7 @@ class ComposeActivity : BaseActivity(),
// do not do anything when not logged in, activity will be finished in super.onCreate() anyway // do not do anything when not logged in, activity will be finished in super.onCreate() anyway
val activeAccount = accountManager.activeAccount ?: return 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) viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
setupAvatar(preferences, activeAccount) setupAvatar(preferences, activeAccount)
val mediaAdapter = MediaPreviewAdapter( val mediaAdapter = MediaPreviewAdapter(
@ -1161,7 +1161,8 @@ class ComposeActivity : BaseActivity(),
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED || addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED || emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
scheduleBehavior.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 composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN

View File

@ -98,6 +98,7 @@ data class StringField (
) )
data class PleromaAccount( data class PleromaAccount(
@SerializedName("ap_id") val apId: String? = null,
@SerializedName("accepts_chat_messages") val acceptsChatMessages: Boolean? = null, @SerializedName("accepts_chat_messages") val acceptsChatMessages: Boolean? = null,
@SerializedName("is_moderator") val isModerator: Boolean? = null, @SerializedName("is_moderator") val isModerator: Boolean? = null,
@SerializedName("is_admin") val isAdmin: Boolean? = null @SerializedName("is_admin") val isAdmin: Boolean? = null

View File

@ -13,6 +13,7 @@ import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.* import androidx.recyclerview.widget.*
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import at.connyduck.sparkbutton.helpers.Utils
import com.keylesspalace.tusky.BottomSheetActivity import com.keylesspalace.tusky.BottomSheetActivity
import com.keylesspalace.tusky.PostLookupFallbackBehavior import com.keylesspalace.tusky.PostLookupFallbackBehavior
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
@ -95,8 +96,13 @@ class ChatsFragment : BaseFragment(), Injectable, RefreshableFragment, Reselecta
if (isAdded) { if (isAdded) {
Log.d(TAG, "onInserted"); Log.d(TAG, "onInserted");
adapter.notifyItemRangeInserted(position, count) adapter.notifyItemRangeInserted(position, count)
if (position == 0 && context != null) { // scroll up when new items at the top are loaded while being in the first position
recyclerView.scrollToPosition(0) // 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);
} }
} }
} }

View File

@ -426,9 +426,7 @@ public abstract class SFragment extends BaseFragment implements Injectable {
break; break;
} }
case UNKNOWN: { case UNKNOWN: {
/* Intentionally do nothing. This case is here is to handle when new attachment onViewUrl(active.getUrl());
* 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. */
break; break;
} }
} }

View File

@ -18,23 +18,55 @@ package com.keylesspalace.tusky.util
import android.text.SpannableString import android.text.SpannableString
import android.text.Spanned import android.text.Spanned
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import android.widget.MultiAutoCompleteTextView import android.widget.MultiAutoCompleteTextView
class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { 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 { override fun findTokenStart(text: CharSequence, cursor: Int): Int {
if (cursor == 0) { if (cursor == 0) {
return cursor return cursor
} }
var i = cursor var i = cursor
var character = text[i - 1] var character = text[i - 1]
while (i > 0 && character != '@' && character != '#' && character != ':') {
// See SpanUtils.MENTION_REGEX while(i > 0 && !(character == '@' || character == '#' || character == ':')) {
if (!Character.isLetterOrDigit(character) && character != '_') { if(!isMentionOrHashtagAllowedCharacter(character)) {
return cursor return cursor
} }
i-- i--
character = if (i == 0) ' ' else text[i - 1] 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 if (i < 1
|| (character != '@' && character != '#' && character != ':') || (character != '@' && character != '#' && character != ':')
|| i > 1 && !Character.isWhitespace(text[i - 2])) { || i > 1 && !Character.isWhitespace(text[i - 2])) {

View File

@ -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"> * @see <a href="https://github.com/tootsuite/mastodon/blob/master/app/models/account.rb">
* Account#MENTION_RE</a> * 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 HTTP_URL_REGEX = "(?:(^|\\b)http://[^\\s]+)"
private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)" private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)"

View File

@ -8,8 +8,8 @@ import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
data class AttachmentViewData( data class AttachmentViewData(
val attachment: Attachment, val attachment: Attachment,
val statusId: String, val statusId: String?,
val statusUrl: String val statusUrl: String?
) : Parcelable { ) : Parcelable {
companion object { companion object {
@JvmStatic @JvmStatic
@ -27,4 +27,4 @@ data class AttachmentViewData(
} }
} }
} }