Compare commits

..

No commits in common. "2ad4d2bf4f45f41170ceaf64a8cac756d37aa371" and "4eb56b713c1d04e24536eaeab336023f18ca33f8" have entirely different histories.

14 changed files with 71 additions and 219 deletions

View File

@ -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órzenie</string>
<string name="action_unreblog">Usuń powtórzenia</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,17 +61,4 @@
<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>

View File

@ -83,25 +83,12 @@ 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)

View File

@ -79,16 +79,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
return 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 {
fun newAvatarIntent(context: Context, url: String): Intent {
val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra(EXTRA_AVATAR_URL, url)
return intent
@ -180,11 +171,6 @@ 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
}

View File

@ -17,7 +17,6 @@ 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
@ -41,10 +40,8 @@ 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) {
val text = msg.content.emojify(msg.emojis, content)
LinkHelper.setClickableText(content, text, null, chatActionListener)
}
if(msg.content != null)
content.text = msg.content.emojify(msg.emojis, content)
setAttachment(msg.attachment, chatActionListener)
setCreatedAt(msg.createdAt)

View File

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

View File

@ -12,7 +12,6 @@ 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
@ -21,6 +20,10 @@ 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
@ -32,11 +35,9 @@ 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
@ -48,13 +49,11 @@ 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
@ -66,6 +65,7 @@ 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(PrefKeys.STICKERS, false)
viewModel.tryFetchStickers = preferences.getBoolean("stickers", false)
viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
setupHeader()
@ -227,23 +227,21 @@ class ChatActivity: BottomSheetActivity(),
.subscribe { event: Event? ->
when(event) {
is ChatMessageDeliveredEvent -> {
if(event.chatMsg.chatId == chatId) {
onRefresh()
enableButton(attachmentButton, true, true)
enableButton(stickerButton, haveStickers, haveStickers)
sending = false
enableSendButton()
}
onRefresh()
enableButton(sendButton, true, true)
enableButton(attachmentButton, true, true)
enableButton(stickerButton, haveStickers, haveStickers)
editText.text.clear()
viewModel.media.value = listOf()
}
is ChatMessageReceivedEvent -> {
if(event.chatMsg.chatId == chatId) {
onRefresh()
}
onRefresh()
}
}
}
tryCache()
}
@ -289,14 +287,11 @@ class ChatActivity: BottomSheetActivity(),
viewModel.updateDescription(it.localId, newDescription)
}
}
removeId -> {
viewModel.removeMediaFromQueue(it)
}
removeId -> viewModel.removeMediaFromQueue(it)
}
}
true
}
popup.show()
}
imageAttachment.setOnClickListener(onMediaPick)
@ -319,7 +314,6 @@ 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
@ -329,17 +323,6 @@ 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)
}
@ -371,6 +354,11 @@ 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
@ -381,13 +369,7 @@ class ChatActivity: BottomSheetActivity(),
}
viewModel.emoji.observe { setEmojiList(it) }
viewModel.media.observe {
val notHaveMedia = it.isEmpty()
enableSendButton()
enableButton(attachmentButton, notHaveMedia, notHaveMedia)
enableButton(stickerButton, haveStickers && notHaveMedia, haveStickers && notHaveMedia)
if(!notHaveMedia) {
if(it.isNotEmpty()) {
val media = it[0]
when(media.type) {
@ -479,7 +461,22 @@ class ChatActivity: BottomSheetActivity(),
emojiBehavior = BottomSheetBehavior.from(emojiView)
stickerBehavior = BottomSheetBehavior.from(stickerKeyboard)
sendButton.setOnClickListener { onSendClicked() }
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)
}
attachmentButton.setOnClickListener { openPickDialog() }
emojiButton.setOnClickListener { showEmojis() }
@ -505,26 +502,6 @@ 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
@ -675,6 +652,8 @@ class ChatActivity: BottomSheetActivity(),
}
}
displayTransientError(errorId)
} else {
enableButton(attachmentButton, false, false)
}
}
}
@ -883,13 +862,13 @@ class ChatActivity: BottomSheetActivity(),
FetchEnd.TOP -> {
updateMessages(msgs, fullFetch)
val last = msgs.indexOfFirst { it.isRight() }
val pos = msgs.indexOfFirst { it.isRight() }
mastodonApi.markChatAsRead(chatId, msgs[last].asRight().id)
mastodonApi.markChatAsRead(chatId, msgs[pos].asRight().id)
.observeOn(AndroidSchedulers.mainThread())
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
.subscribe({
Log.d(TAG, "Marked new messages as read up to ${msgs[last].asRight().id}")
Log.d(TAG, "Marked new messages as read up to ${msgs[pos].asRight().id}")
}, {
Log.d(TAG, "Failed to mark messages as read", it)
})
@ -1001,34 +980,14 @@ 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_HIDDEN ||
emojiBehavior.state != BottomSheetBehavior.STATE_HIDDEN ||
stickerBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
if (addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
@ -1080,28 +1039,6 @@ 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

View File

@ -252,13 +252,9 @@ open class CommonComposeViewModel(
when (token[0]) {
'@' -> {
return try {
val acct = token.substring(1)
api.searchAccounts(query = acct, resolve = true, limit = 10)
api.searchAccounts(query = token.substring(1), 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()

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
val activeAccount = accountManager.activeAccount ?: return
viewModel.tryFetchStickers = preferences.getBoolean(PrefKeys.STICKERS, false)
viewModel.tryFetchStickers = preferences.getBoolean("stickers", false)
viewModel.anonymizeNames = preferences.getBoolean(PrefKeys.ANONYMIZE_FILENAMES, false)
setupAvatar(preferences, activeAccount)
val mediaAdapter = MediaPreviewAdapter(
@ -1161,8 +1161,7 @@ class ComposeActivity : BaseActivity(),
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
previewBehavior.state == BottomSheetBehavior.STATE_HIDDEN) {
stickerBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN

View File

@ -98,7 +98,6 @@ 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

View File

@ -13,7 +13,6 @@ 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
@ -96,13 +95,8 @@ class ChatsFragment : BaseFragment(), Injectable, RefreshableFragment, Reselecta
if (isAdded) {
Log.d(TAG, "onInserted");
adapter.notifyItemRangeInserted(position, count)
// 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);
if (position == 0 && context != null) {
recyclerView.scrollToPosition(0)
}
}
}

View File

@ -426,7 +426,9 @@ public abstract class SFragment extends BaseFragment implements Injectable {
break;
}
case UNKNOWN: {
onViewUrl(active.getUrl());
/* 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. */
break;
}
}

View File

@ -18,55 +18,23 @@ 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 == ':')) {
if(!isMentionOrHashtagAllowedCharacter(character)) {
while (i > 0 && character != '@' && character != '#' && character != ':') {
// See SpanUtils.MENTION_REGEX
if (!Character.isLetterOrDigit(character) && 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])) {

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">
* Account#MENTION_RE</a>
*/
private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z\\d_-]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)"
private const val MENTION_REGEX = "(?:^|[^/[:word:]])@([a-z0-9_]+(?:@[a-z0-9\\.\\-]+[a-z0-9]+)?)"
private const val HTTP_URL_REGEX = "(?:(^|\\b)http://[^\\s]+)"
private const val HTTPS_URL_REGEX = "(?:(^|\\b)https://[^\\s]+)"

View File

@ -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
@ -27,4 +27,4 @@ data class AttachmentViewData(
}
}
}
}