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="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>
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])) {
|
||||||
|
|
|
@ -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]+)"
|
||||||
|
|
|
@ -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(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue