Compare commits

...

13 Commits

Author SHA1 Message Date
Hararan 64d7e263fe Translated using Weblate (Thai)
Currently translated at 100.0% (62 of 62 strings)

Translation: Husky/Husky translations
Translate-URL: https://l10n.mentality.rip/projects/husky/husky-translations/th/
2020-06-25 18:11:35 +02:00
Alibek Omarov d16c7f08ab ComposeActivityTest: fix test 2020-06-25 19:04:25 +03:00
Alibek Omarov 3c1b82056c entity: Instance: fix optional pleroma field type 2020-06-25 18:57:47 +03:00
Alibek Omarov 3e9c568e76 ViewMediaActivity: add a button to open media file in external application just in case built-in fails 2020-06-25 18:33:35 +03:00
Alibek Omarov c062e9c180 SearchStatusesFragment: fix reply to 2020-06-25 17:58:44 +03:00
Alibek Omarov 78352944c1 StatusFragment: delete delete and redraft 2020-06-25 17:42:17 +03:00
Alibek Omarov 6ff14af290 ViewThreadActivity: move Open in web menu option to status threedots menu 2020-06-25 17:38:08 +03:00
Alibek Omarov 9f4f805141 fastlane: fix compose screenshot 2020-06-25 17:14:28 +03:00
Alibek Omarov d68260ba4e readme: update 2020-06-25 16:50:17 +03:00
Alibek Omarov b495462b33 Merge remote-tracking branch 'weblate/develop' into develop 2020-06-25 16:47:08 +03:00
Konrad Pozniak 9680bbf82b correctly update the menu when muting domains (#1848) 2020-06-25 16:46:51 +03:00
Ivan Kupalov 762be59036 Improve image viewer (#1843)
This commit does 3 things:
1. Replaces PhotoView (which is abandonware) with modern TouchImageView
2. Fixes an issue with panning images. Gesture was not intercepted
properly and pager was taking control instead of image being moved.
3. Adds feedback to dismissing of images with vertical gesture.
2020-06-25 16:42:56 +03:00
Alibek Omarov b3259fcfd9 ComposeActivity: finalize preview feature, fix preview bar appearing on media change, add proper icon, fix minor bugs 2020-06-25 16:40:18 +03:00
29 changed files with 250 additions and 107 deletions

View File

@ -1,7 +1,8 @@
# Husky # Husky
[![Build Status](https://api.travis-ci.org/FWGS/Husky.svg?branch=develop)](https://travis-ci.org/FWGS/Husky)\ [![Build Status](https://api.travis-ci.org/FWGS/Husky.svg?branch=develop)](https://travis-ci.org/FWGS/Husky)\
[![Download F-Droid](https://img.shields.io/badge/download-fdroid-blue)](https://f-droid.org/repository/browse/?fdid=su.xash.husky)\
[![Download F-Droid](https://img.shields.io/badge/download-fdroid-blue)](https://f-droid.org/repository/browse/?fdid=su.xash.husky) [![Download Google Play](https://img.shields.io/badge/download-googleplay-blue)](https://play.google.com/store/apps/details?id=su.xash.husky) [![Download Testing](https://img.shields.io/badge/downloads-testing-green)](https://github.com/FWGS/Husky/releases/tag/continuous) [![Download Google Play](https://img.shields.io/badge/download-googleplay-blue)](https://play.google.com/store/apps/details?id=su.xash.husky)\
[![Download Testing](https://img.shields.io/badge/downloads-testing-green)](https://github.com/FWGS/Husky/releases/tag/continuous)
![icon](https://git.mentality.rip/FWGS/Husky/raw/branch/develop/assets/splash.xcf) ![icon](https://git.mentality.rip/FWGS/Husky/raw/branch/develop/assets/splash.xcf)
@ -20,12 +21,14 @@ Tusky is quote, unquote, `... a beautiful Android client for [Mastodon](https://
- Support for seen notifications to less annoy you - Support for seen notifications to less annoy you
- "Reply to" feature that allows to jump to replied status, useful for hellthreading ;) - "Reply to" feature that allows to jump to replied status, useful for hellthreading ;)
- Bigger emojis! - Bigger emojis!
- "Preview" feature on Pleroma
### Support ### Support
If you have any bug reports, feature requests or questions please open an issue or send us a post at [Husky@enigmatic.observer](https://enigmatic.observer/users/Husky)! If you have any bug reports, feature requests or questions please open an issue or send us a post at [Husky@enigmatic.observer](https://enigmatic.observer/users/Husky)!
For translating Tusky into your language, visit https://weblate.tusky.app/. For translating Husky, translate app/src/main/res/values/husky.xml file and send it to profile above or send a pull request. For translating Tusky into your language, visit https://weblate.tusky.app/.\
For translating Husky, visit https://l10n.mentality.rip.
### Head of development ### Head of development

View File

@ -178,7 +178,7 @@ dependencies {
implementation "com.github.connyduck:sparkbutton:4.0.0" implementation "com.github.connyduck:sparkbutton:4.0.0"
implementation "com.github.chrisbanes:PhotoView:2.3.0" implementation 'com.github.MikeOrtiz:TouchImageView:3.0.1'
implementation "com.mikepenz:materialdrawer:$materialdrawerVersion" implementation "com.mikepenz:materialdrawer:$materialdrawerVersion"
implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion" implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pref_title_default_formatting">ไวยากรณ์การจัดรูปแบบเริ่มต้น (ถ้ารองรับโดย Instance)</string> <string name="pref_title_default_formatting">ค่าปริยายของไวยากรณ์การจัดรูปแบบ (ถ้ารองรับโดย Instance)</string>
<string name="pref_title_enable_big_emojis">เปิดใช้งานเอโมจิที่กำหนดเองขนาดใหญ่</string> <string name="pref_title_enable_big_emojis">เปิดใช้งานเอโมจิที่กำหนดเองขนาดใหญ่</string>
<string name="action_send">โพสต์</string> <string name="action_send">โพสต์</string>
<string name="action_send_public">โพสต์!</string> <string name="action_send_public">โพสต์!</string>
@ -15,7 +15,7 @@
<string name="action_hide_reblogs">ซ่อนรีพีต</string> <string name="action_hide_reblogs">ซ่อนรีพีต</string>
<string name="unreblog_private">ลบรีพีต</string> <string name="unreblog_private">ลบรีพีต</string>
<string name="description_status_reblogged">รีพีตแล้ว</string> <string name="description_status_reblogged">รีพีตแล้ว</string>
<string name="pref_title_hide_muted_users">ซ่อนผู้ใช้ที่ถูกทำให้เป็นใบ้ไว้</string> <string name="pref_title_hide_muted_users">ซ่อนผู้ใช้ที่ปิดเสียงไว้</string>
<string name="action_toggle_visibility">การมองเห็นโพสต์</string> <string name="action_toggle_visibility">การมองเห็นโพสต์</string>
<string name="action_schedule_toot">โพสต์แบบกำหนดเวลา</string> <string name="action_schedule_toot">โพสต์แบบกำหนดเวลา</string>
<string name="notification_boost_name">รีพีต</string> <string name="notification_boost_name">รีพีต</string>
@ -30,7 +30,7 @@
<string name="send_toot_notification_title">กำลังส่งโพสต์…</string> <string name="send_toot_notification_title">กำลังส่งโพสต์…</string>
<string name="send_toot_notification_error_title">เกิดข้อผิดพลาดในการส่งโพสต์</string> <string name="send_toot_notification_error_title">เกิดข้อผิดพลาดในการส่งโพสต์</string>
<string name="status_share_content">แบ่งปันเนื้อหาของโพสต์</string> <string name="status_share_content">แบ่งปันเนื้อหาของโพสต์</string>
<string name="status_boosted_format">%s ถูกรีพีต</string> <string name="status_boosted_format">%s ได้รีพีต</string>
<string name="notification_emoji_name">การโต้ตอบเอโมจิ</string> <string name="notification_emoji_name">การโต้ตอบเอโมจิ</string>
<string name="send_status_link_to">แบ่งปัน URL โพสต์ไป…</string> <string name="send_status_link_to">แบ่งปัน URL โพสต์ไป…</string>
<string name="title_view_thread">โพสต์</string> <string name="title_view_thread">โพสต์</string>

View File

@ -83,6 +83,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
private var followState: FollowState = FollowState.NOT_FOLLOWING private var followState: FollowState = FollowState.NOT_FOLLOWING
private var blocking: Boolean = false private var blocking: Boolean = false
private var muting: Boolean = false private var muting: Boolean = false
private var blockingDomain: Boolean = false
private var showingReblogs: Boolean = false private var showingReblogs: Boolean = false
private var subscribing: Boolean = false private var subscribing: Boolean = false
private var loadedAccount: Account? = null private var loadedAccount: Account? = null
@ -543,6 +544,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
} }
blocking = relation.blocking blocking = relation.blocking
muting = relation.muting muting = relation.muting
blockingDomain = relation.blockingDomain
showingReblogs = relation.showingReblogs showingReblogs = relation.showingReblogs
accountFollowsYouTextView.visible(relation.followedBy) accountFollowsYouTextView.visible(relation.followedBy)
@ -664,10 +666,14 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
if (domain.isEmpty()) { if (domain.isEmpty()) {
// If we can't get the domain, there's no way we can mute it anyway... // If we can't get the domain, there's no way we can mute it anyway...
menu.removeItem(R.id.action_mute_domain) menu.removeItem(R.id.action_mute_domain)
} else {
if (blockingDomain) {
muteDomain.title = getString(R.string.action_unmute_domain, domain)
} else { } else {
muteDomain.title = getString(R.string.action_mute_domain, domain) muteDomain.title = getString(R.string.action_mute_domain, domain)
} }
} }
}
if (followState == FollowState.FOLLOWING) { if (followState == FollowState.FOLLOWING) {
val showReblogs = menu.findItem(R.id.action_show_reblogs) val showReblogs = menu.findItem(R.id.action_show_reblogs)
@ -710,13 +716,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
.show() .show()
} }
private fun showMuteDomainWarningDialog(instance: String) { private fun toggleBlockDomain(instance: String) {
if(blockingDomain) {
viewModel.unblockDomain(instance)
} else {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setMessage(getString(R.string.mute_domain_warning, instance)) .setMessage(getString(R.string.mute_domain_warning, instance))
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.muteDomain(instance) } .setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()
} }
}
private fun toggleBlock() { private fun toggleBlock() {
if (viewModel.relationshipData.value?.data?.blocking != true) { if (viewModel.relationshipData.value?.data?.blocking != true) {
@ -796,7 +806,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
return true return true
} }
R.id.action_mute_domain -> { R.id.action_mute_domain -> {
showMuteDomainWarningDialog(domain) toggleBlockDomain(domain)
return true return true
} }
R.id.action_show_reblogs -> { R.id.action_show_reblogs -> {

View File

@ -142,6 +142,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
toolbar.setNavigationOnClickListener { supportFinishAfterTransition() } toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
toolbar.setOnMenuItemClickListener { item: MenuItem -> toolbar.setOnMenuItemClickListener { item: MenuItem ->
when (item.itemId) { when (item.itemId) {
R.id.action_open_in_external_app -> openInExternalApp()
R.id.action_download -> requestDownloadMedia() R.id.action_download -> requestDownloadMedia()
R.id.action_open_status -> onOpenStatus() R.id.action_open_status -> onOpenStatus()
R.id.action_share_media -> shareMedia() R.id.action_share_media -> shareMedia()
@ -269,6 +270,19 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to))) startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to)))
} }
private fun openInExternalApp() {
val url = attachments!![viewPager.currentItem].attachment.url
val intent = Intent(Intent.ACTION_VIEW)
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
if(extension != null) {
intent.setDataAndType(Uri.parse(url), MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension))
} else {
intent.data = Uri.parse(url)
}
startActivity(intent)
}
private var isCreating: Boolean = false private var isCreating: Boolean = false

View File

@ -114,10 +114,6 @@ public class ViewThreadActivity extends BottomSheetActivity implements HasAndroi
onBackPressed(); onBackPressed();
return true; return true;
} }
case R.id.action_open_in_web: {
LinkHelper.openLink(getIntent().getStringExtra(URL_EXTRA), this);
return true;
}
case R.id.action_reveal: { case R.id.action_reveal: {
fragment.onRevealPressed(); fragment.onRevealPressed();
return true; return true;

View File

@ -54,6 +54,7 @@ import androidx.core.view.inputmethod.InputContentInfoCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -97,6 +98,8 @@ import kotlin.collections.ArrayList
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import me.thanel.markdownedit.MarkdownEdit import me.thanel.markdownedit.MarkdownEdit
import io.reactivex.android.schedulers.AndroidSchedulers
import com.uber.autodispose.android.lifecycle.autoDispose
class ComposeActivity : BaseActivity(), class ComposeActivity : BaseActivity(),
ComposeOptionsListener, ComposeOptionsListener,
@ -198,7 +201,9 @@ class ComposeActivity : BaseActivity(),
stickerKeyboard.isSticky = true stickerKeyboard.isSticky = true
eventHub.events.subscribe { event -> eventHub.events.observeOn(AndroidSchedulers.mainThread())
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
.subscribe { event: Event? ->
when(event) { when(event) {
is StatusPreviewEvent -> onStatusPreviewReady(event.status) is StatusPreviewEvent -> onStatusPreviewReady(event.status)
} }
@ -967,9 +972,8 @@ class ComposeActivity : BaseActivity(),
} }
private fun onSendClicked(preview: Boolean) { private fun onSendClicked(preview: Boolean) {
if(preview && previewBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { if(preview && previewBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
} }
if (verifyScheduledTime()) { if (verifyScheduledTime()) {
@ -988,8 +992,6 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN stickerBehavior.state = BottomSheetBehavior.STATE_HIDDEN
// Log.d("ComposeActivityPreview", "Preview: " + status.content)
} }
/** This is for the fancy keyboards which can insert images and stuff. */ /** This is for the fancy keyboards which can insert images and stuff. */
@ -1030,11 +1032,11 @@ class ComposeActivity : BaseActivity(),
this, getString(R.string.dialog_title_finishing_media_upload), this, getString(R.string.dialog_title_finishing_media_upload),
getString(R.string.dialog_message_uploading_media), true, true) getString(R.string.dialog_message_uploading_media), true, true)
viewModel.sendStatus(contentText, spoilerText, preview).observe(this, Observer { viewModel.sendStatus(contentText, spoilerText, preview).observeOnce(this) {
finishingUploadDialog?.dismiss() finishingUploadDialog?.dismiss()
if(!preview) if(!preview)
deleteDraftAndFinish() deleteDraftAndFinish()
}) }
} else { } else {
composeEditField.error = getString(R.string.error_compose_character_limit) composeEditField.error = getString(R.string.error_compose_character_limit)

View File

@ -33,9 +33,9 @@ import at.connyduck.sparkbutton.helpers.Utils
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import com.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.util.withLifecycleContext import com.keylesspalace.tusky.util.withLifecycleContext
import com.ortiz.touchview.TouchImageView
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94 // https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420 private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420
@ -50,9 +50,8 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
dialogLayout.setPadding(padding, padding, padding, padding) dialogLayout.setPadding(padding, padding, padding, padding)
dialogLayout.orientation = LinearLayout.VERTICAL dialogLayout.orientation = LinearLayout.VERTICAL
val imageView = PhotoView(this).apply { val imageView = TouchImageView(this).apply {
// If it seems a lot, try opening an image of A4 format or similar maxZoom = 6f
maximumScale = 6.0f
} }
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()

View File

@ -53,6 +53,7 @@ import com.keylesspalace.tusky.entity.Status.Mention
import com.keylesspalace.tusky.interfaces.AccountSelectionListener import com.keylesspalace.tusky.interfaces.AccountSelectionListener
import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.CardViewMode
import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.NetworkState import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.AttachmentViewData
@ -194,6 +195,10 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
} }
} }
private fun onShowReplyTo(replyToId: String) {
bottomSheetActivity?.viewThread(replyToId, null)
}
companion object { companion object {
fun newInstance() = SearchStatusesFragment() fun newInstance() = SearchStatusesFragment()
} }
@ -267,6 +272,10 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
} }
openAsItem.title = openAsTitle openAsItem.title = openAsTitle
if(status.inReplyToId == null) {
popup.menu.findItem(R.id.status_reply_to)?.isVisible = false
}
popup.setOnMenuItemClickListener { item -> popup.setOnMenuItemClickListener { item ->
when (item.itemId) { when (item.itemId) {
R.id.status_share_content -> { R.id.status_share_content -> {
@ -296,6 +305,14 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl)) clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl))
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true
} }
R.id.status_open_in_web -> {
LinkHelper.openLinkInBrowser(Uri.parse(statusUrl), context);
return@setOnMenuItemClickListener true
}
R.id.status_reply_to -> {
onShowReplyTo(status.inReplyToId!!)
return@setOnMenuItemClickListener true
}
R.id.status_open_as -> { R.id.status_open_as -> {
showOpenAsDialog(statusUrl!!, item.title) showOpenAsDialog(statusUrl!!, item.title)
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true
@ -334,10 +351,6 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
showConfirmDeleteDialog(id, position) showConfirmDeleteDialog(id, position)
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true
} }
R.id.status_delete_and_redraft -> {
showConfirmEditDialog(id, position, status)
return@setOnMenuItemClickListener true
}
R.id.pin -> { R.id.pin -> {
viewModel.pinAccount(status, !status.isPinned()) viewModel.pinAccount(status, !status.isPinned())
return@setOnMenuItemClickListener true return@setOnMenuItemClickListener true

View File

@ -31,7 +31,7 @@ data class Instance (
@SerializedName("max_toot_chars") val maxTootChars: Int?, @SerializedName("max_toot_chars") val maxTootChars: Int?,
@SerializedName("max_bio_chars") val maxBioChars: Int?, @SerializedName("max_bio_chars") val maxBioChars: Int?,
@SerializedName("poll_limits") val pollLimits: PollLimits?, @SerializedName("poll_limits") val pollLimits: PollLimits?,
val pleroma: InstancePleroma val pleroma: InstancePleroma?
) { ) {
override fun hashCode(): Int { override fun hashCode(): Int {
return uri.hashCode() return uri.hashCode()

View File

@ -25,5 +25,6 @@ data class Relationship (
val muting: Boolean, val muting: Boolean,
val requested: Boolean, val requested: Boolean,
@SerializedName("showing_reblogs") val showingReblogs: Boolean, @SerializedName("showing_reblogs") val showingReblogs: Boolean,
val subscribing: Boolean? = null // Pleroma extension val subscribing: Boolean? = null, // Pleroma extension
@SerializedName("domain_blocking") val blockingDomain: Boolean
) )

View File

@ -62,6 +62,7 @@ import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.EmojiReaction; import com.keylesspalace.tusky.entity.EmojiReaction;
import com.keylesspalace.tusky.network.MastodonApi; import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases; import com.keylesspalace.tusky.network.TimelineCases;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.viewdata.AttachmentViewData; import com.keylesspalace.tusky.viewdata.AttachmentViewData;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
@ -320,6 +321,10 @@ public abstract class SFragment extends BaseFragment implements Injectable {
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
return true; return true;
} }
case R.id.status_open_in_web: {
LinkHelper.openLinkInBrowser(Uri.parse(statusUrl), getContext());
return true;
}
case R.id.status_reply_to: { case R.id.status_reply_to: {
onShowReplyTo(status.getInReplyToId()); onShowReplyTo(status.getInReplyToId());
return true; return true;
@ -364,10 +369,6 @@ public abstract class SFragment extends BaseFragment implements Injectable {
showConfirmDeleteDialog(id, position); showConfirmDeleteDialog(id, position);
return true; return true;
} }
case R.id.status_delete_and_redraft: {
showConfirmEditDialog(id, position, status);
return true;
}
case R.id.pin: { case R.id.pin: {
timelineCases.pin(status, !status.isPinned()); timelineCases.pin(status, !status.isPinned());
return true; return true;

View File

@ -21,9 +21,7 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.*
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -31,7 +29,6 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.github.chrisbanes.photoview.PhotoViewAttacher
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
@ -48,11 +45,11 @@ class ViewImageFragment : ViewMediaFragment() {
fun onPhotoTap() fun onPhotoTap()
} }
private lateinit var attacher: PhotoViewAttacher
private lateinit var photoActionsListener: PhotoActionsListener private lateinit var photoActionsListener: PhotoActionsListener
private lateinit var toolbar: View private lateinit var toolbar: View
private var transition = BehaviorSubject.create<Unit>() private var transition = BehaviorSubject.create<Unit>()
private var shouldStartTransition = false private var shouldStartTransition = false
// Volatile: Image requests happen on background thread and we want to see updates to it // Volatile: Image requests happen on background thread and we want to see updates to it
// immediately on another thread. Atomic is an overkill for such thing. // immediately on another thread. Atomic is an overkill for such thing.
@Volatile @Volatile
@ -67,23 +64,6 @@ class ViewImageFragment : ViewMediaFragment() {
override fun setupMediaView(url: String, previewUrl: String?) { override fun setupMediaView(url: String, previewUrl: String?) {
descriptionView = mediaDescription descriptionView = mediaDescription
photoView.transitionName = url photoView.transitionName = url
attacher = PhotoViewAttacher(photoView).apply {
// Clicking outside the photo closes the viewer.
setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() }
setOnClickListener { onMediaTap() }
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
* mostly fills the screen so clicking outside is difficult. */
setOnSingleFlingListener { _, _, velocityX, velocityY ->
var result = false
if (abs(velocityY) > abs(velocityX)) {
photoActionsListener.onDismiss()
result = true
}
result
}
}
startedTransition = false startedTransition = false
loadImageFromNetwork(url, previewUrl, photoView) loadImageFromNetwork(url, previewUrl, photoView)
} }
@ -94,10 +74,66 @@ class ViewImageFragment : ViewMediaFragment() {
return inflater.inflate(R.layout.fragment_view_image, container, false) return inflater.inflate(R.layout.fragment_view_image, container, false)
} }
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val arguments = this.arguments!! val gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
onMediaTap()
return true
}
})
var lastY = 0f
photoView.setOnTouchListener { _, event ->
// This part is for scaling/translating on vertical move.
// We use raw coordinates to get the correct ones during scaling
var result = true
gestureDetector.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_DOWN) {
lastY = event.rawY
} else if (!photoView.isZoomed && event.action == MotionEvent.ACTION_MOVE) {
val diff = event.rawY - lastY
// This code is to prevent transformations during page scrolling
// If we are already translating or we reached the threshold, then transform.
if (photoView.translationY != 0f || abs(diff) > 40) {
photoView.translationY += (diff)
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
photoView.scaleY = scale
photoView.scaleX = scale
lastY = event.rawY
}
return@setOnTouchListener true
} else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
onGestureEnd()
} else if (event.pointerCount >= 2 || photoView.canScrollHorizontally(1) && photoView.canScrollHorizontally(-1)) {
// Starting from here is adapted code from TouchImageView to play nice with pager.
// Can scroll horizontally checks if there's still a part of the image.
// That can be scrolled until you reach the edge multi-touch event.
val parent = view.parent
result = when (event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
// Disallow RecyclerView to intercept touch events.
parent.requestDisallowInterceptTouchEvent(true)
// Disable touch on view
false
}
MotionEvent.ACTION_UP -> {
// Allow RecyclerView to intercept touch events.
parent.requestDisallowInterceptTouchEvent(false)
true
}
else -> true
}
}
result
}
val arguments = this.requireArguments()
val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT) val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT)
this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION) this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)
val url: String? val url: String?
@ -116,6 +152,14 @@ class ViewImageFragment : ViewMediaFragment() {
finalizeViewSetup(url, attachment?.previewUrl, description) finalizeViewSetup(url, attachment?.previewUrl, description)
} }
private fun onGestureEnd() {
if (abs(photoView.translationY) > 180) {
photoActionsListener.onDismiss()
} else {
photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
}
}
private fun onMediaTap() { private fun onMediaTap() {
photoActionsListener.onPhotoTap() photoActionsListener.onPhotoTap()
} }
@ -155,7 +199,6 @@ class ViewImageFragment : ViewMediaFragment() {
.load(previewUrl) .load(previewUrl)
.dontAnimate() .dontAnimate()
.onlyRetrieveFromCache(true) .onlyRetrieveFromCache(true)
.centerInside()
.addListener(ImageRequestListener(true, isThumnailRequest = true))) .addListener(ImageRequestListener(true, isThumnailRequest = true)))
else it else it
} }
@ -164,7 +207,6 @@ class ViewImageFragment : ViewMediaFragment() {
.centerInside() .centerInside()
.addListener(ImageRequestListener(false, isThumnailRequest = false)) .addListener(ImageRequestListener(false, isThumnailRequest = false))
) )
.centerInside()
.addListener(ImageRequestListener(true, isThumnailRequest = false)) .addListener(ImageRequestListener(true, isThumnailRequest = false))
.into(photoView) .into(photoView)
} }
@ -225,13 +267,7 @@ class ViewImageFragment : ViewMediaFragment() {
// another branch. take() will unsubscribe after we have it to not leak menmory // another branch. take() will unsubscribe after we have it to not leak menmory
transition transition
.take(1) .take(1)
.subscribe { .subscribe { target.onResourceReady(resource, null) }
target.onResourceReady(resource, null)
// It's needed. Don't ask why, I don't know, setImageDrawable() should
// do it by itself but somehow it doesn't work automatically.
// Just do it. If you don't, image will jump around when touched.
attacher.update()
}
} }
return true return true
} }

View File

@ -11,6 +11,7 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.Parcelable import android.os.Parcelable
import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -130,8 +131,8 @@ class SendTootService : Service(), Injectable {
tootToSend.retries++ tootToSend.retries++
val contentType : String? = if(tootToSend.formattingSyntax.length == 0) null else tootToSend.formattingSyntax val contentType : String? = if(tootToSend.formattingSyntax.isNotEmpty()) tootToSend.formattingSyntax else null
val preview : Boolean? = if(tootToSend.preview) tootToSend.preview else null val preview : Boolean? = if(tootToSend.preview) true else null
val newStatus = NewStatus( val newStatus = NewStatus(
tootToSend.text, tootToSend.text,
@ -167,14 +168,11 @@ class SendTootService : Service(), Injectable {
saveTootHelper.deleteDraft(tootToSend.savedTootUid) saveTootHelper.deleteDraft(tootToSend.savedTootUid)
} }
if (tootToSend.preview) { when {
response.body()?.let(::StatusPreviewEvent)?.let(eventHub::dispatch) tootToSend.preview -> response.body()?.let(::StatusPreviewEvent)?.let(eventHub::dispatch)
} else if (scheduled) { scheduled -> response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch)
response.body()?.let(::StatusScheduledEvent)?.let(eventHub::dispatch) else -> response.body()?.let(::StatusComposedEvent)?.let(eventHub::dispatch)
} else {
response.body()?.let(::StatusComposedEvent)?.let(eventHub::dispatch)
} }
notificationManager.cancel(tootId) notificationManager.cancel(tootId)
} else { } else {

View File

@ -27,6 +27,24 @@ inline fun <X, Y> LiveData<X>.switchMap(
crossinline switchMapFunction: (X) -> LiveData<Y> crossinline switchMapFunction: (X) -> LiveData<Y>
): LiveData<Y> = Transformations.switchMap(this) { input -> switchMapFunction(input) } ): LiveData<Y> = Transformations.switchMap(this) { input -> switchMapFunction(input) }
fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
observeForever(object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
observe(owner, object: Observer<T> {
override fun onChanged(value: T) {
removeObserver(this)
observer(value)
}
})
}
inline fun <X> LiveData<X>.filter(crossinline predicate: (X) -> Boolean): LiveData<X> { inline fun <X> LiveData<X>.filter(crossinline predicate: (X) -> Boolean): LiveData<X> {
val liveData = MediatorLiveData<X>() val liveData = MediatorLiveData<X>()
liveData.addSource(this) { value -> liveData.addSource(this) { value ->

View File

@ -164,18 +164,41 @@ class AccountViewModel @Inject constructor(
} }
} }
fun muteDomain(instance: String) { fun blockDomain(instance: String) {
mastodonApi.blockDomain(instance).enqueue(object: Callback<Any> { mastodonApi.blockDomain(instance).enqueue(object: Callback<Any> {
override fun onResponse(call: Call<Any>, response: Response<Any>) { override fun onResponse(call: Call<Any>, response: Response<Any>) {
if (response.isSuccessful) { if (response.isSuccessful) {
eventHub.dispatch(DomainMuteEvent(instance)) eventHub.dispatch(DomainMuteEvent(instance))
val relation = relationshipData.value?.data
if(relation != null) {
relationshipData.postValue(Success(relation.copy(blockingDomain = true)))
}
} else { } else {
Log.e(TAG, String.format("Error muting %s", instance)) Log.e(TAG, "Error muting %s".format(instance))
} }
} }
override fun onFailure(call: Call<Any>, t: Throwable) { override fun onFailure(call: Call<Any>, t: Throwable) {
Log.e(TAG, String.format("Error muting %s", instance), t) Log.e(TAG, "Error muting %s".format(instance), t)
}
})
}
fun unblockDomain(instance: String) {
mastodonApi.unblockDomain(instance).enqueue(object: Callback<Any> {
override fun onResponse(call: Call<Any>, response: Response<Any>) {
if (response.isSuccessful) {
val relation = relationshipData.value?.data
if(relation != null) {
relationshipData.postValue(Success(relation.copy(blockingDomain = false)))
}
} else {
Log.e(TAG, "Error unmuting %s".format(instance))
}
}
override fun onFailure(call: Call<Any>, t: Throwable) {
Log.e(TAG, "Error unmuting %s".format(instance), t)
} }
}) })
} }

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,3H5C3.89,3 3,3.9 3,5v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.11,3 19,3zM19,19H5V7h14V19zM13.5,13c0,0.83 -0.67,1.5 -1.5,1.5s-1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5S13.5,12.17 13.5,13zM12,9c-2.73,0 -5.06,1.66 -6,4c0.94,2.34 3.27,4 6,4s5.06,-1.66 6,-4C17.06,10.66 14.73,9 12,9zM12,15.5c-1.38,0 -2.5,-1.12 -2.5,-2.5c0,-1.38 1.12,-2.5 2.5,-2.5c1.38,0 2.5,1.12 2.5,2.5C14.5,14.38 13.38,15.5 12,15.5z"
android:fillColor="#000000"/>
</vector>

View File

@ -262,7 +262,7 @@
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingBottom="60dp" android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true" app:behavior_hideable="true"
app:behavior_peekHeight="0dp" app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
@ -276,7 +276,7 @@
android:paddingStart="24dp" android:paddingStart="24dp"
android:paddingTop="12dp" android:paddingTop="12dp"
android:paddingEnd="24dp" android:paddingEnd="24dp"
android:paddingBottom="60dp" android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true" app:behavior_hideable="true"
app:behavior_peekHeight="0dp" app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
@ -290,7 +290,7 @@
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingBottom="52dp" android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true" app:behavior_hideable="true"
app:behavior_peekHeight="0dp" app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
@ -301,19 +301,20 @@
android:layout_height="300dp" android:layout_height="300dp"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
android:elevation="12dp" android:elevation="12dp"
android:paddingBottom="60dp" android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true" app:behavior_hideable="true"
app:behavior_peekHeight="0dp" app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
<ScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/previewScroll" android:id="@+id/previewScroll"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="300dp"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
android:elevation="12dp" android:elevation="12dp"
android:paddingBottom="60dp" android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true" app:behavior_hideable="true"
app:behavior_skipCollapsed="true"
app:behavior_peekHeight="0dp" app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
> >
@ -323,7 +324,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/colorSurface" android:background="?attr/colorSurface"
/> />
</ScrollView> </androidx.core.widget.NestedScrollView>
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -459,7 +460,7 @@
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
app:icon="@drawable/ic_eye_24dp" app:icon="@drawable/ic_preview_24dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:visibility="gone" android:visibility="gone"
android:layout_toLeftOf="@+id/composeTootButton" android:layout_toLeftOf="@+id/composeTootButton"

View File

@ -4,11 +4,10 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
<com.github.chrisbanes.photoview.PhotoView <com.ortiz.touchview.TouchImageView
android:id="@+id/photoView" android:id="@+id/photoView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View File

@ -18,6 +18,9 @@
<item <item
android:id="@+id/status_copy_link" android:id="@+id/status_copy_link"
android:title="@string/action_copy_link" /> android:title="@string/action_copy_link" />
<item
android:id="@+id/status_open_in_web"
android:title="@string/action_open_in_web" />
<item <item
android:id="@+id/status_open_as" android:id="@+id/status_open_as"
android:title="@string/action_open_as" /> android:title="@string/action_open_as" />

View File

@ -21,6 +21,9 @@
<item <item
android:id="@+id/status_open_as" android:id="@+id/status_open_as"
android:title="@string/action_open_as" /> android:title="@string/action_open_as" />
<item
android:id="@+id/status_open_in_web"
android:title="@string/action_open_in_web" />
<item <item
android:id="@+id/status_mute_conversation" android:id="@+id/status_mute_conversation"
android:title="@string/action_mute_conversation" android:title="@string/action_mute_conversation"
@ -40,7 +43,4 @@
<item <item
android:id="@+id/status_delete" android:id="@+id/status_delete"
android:title="@string/action_delete" /> android:title="@string/action_delete" />
<item
android:id="@+id/status_delete_and_redraft"
android:title="@string/action_delete_and_redraft" />
</menu> </menu>

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_open_in_external_app"
android:icon="@drawable/ic_exit_to_app_24px"
android:title="@string/action_open_in_external_app"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_download" android:id="@+id/action_download"
android:icon="@drawable/ic_file_download_black_24dp" android:icon="@drawable/ic_file_download_black_24dp"

View File

@ -3,10 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_open_in_web"
android:title="@string/action_open_in_web"
app:showAsAction="never" />
<item <item
android:id="@+id/action_reveal" android:id="@+id/action_reveal"
android:title="@string/expand_collapse_all_statuses" android:title="@string/expand_collapse_all_statuses"

View File

@ -17,6 +17,7 @@
<dimen name="card_image_horizontal_width">100dp</dimen> <dimen name="card_image_horizontal_width">100dp</dimen>
<dimen name="compose_activity_snackbar_elevation">16dp</dimen> <dimen name="compose_activity_snackbar_elevation">16dp</dimen>
<dimen name="compose_activity_bottom_bar_height">60dp</dimen>
<dimen name="account_activity_scroll_title_visible_height">200dp</dimen> <dimen name="account_activity_scroll_title_visible_height">200dp</dimen>
<dimen name="account_activity_avatar_size">100dp</dimen> <dimen name="account_activity_avatar_size">100dp</dimen>

View File

@ -9,6 +9,7 @@
<string name="action_enable_formatting_syntax">Enable %s</string> <string name="action_enable_formatting_syntax">Enable %s</string>
<string name="action_disable_formatting_syntax">Disable %s</string> <string name="action_disable_formatting_syntax">Disable %s</string>
<string name="action_sticker">Stickers</string> <string name="action_sticker">Stickers</string>
<string name="action_open_in_external_app">Open in external app</string>
<string name="title_emoji_reacted_by">%s reacted by</string> <string name="title_emoji_reacted_by">%s reacted by</string>

View File

@ -109,6 +109,7 @@
<string name="action_mute">Mute</string> <string name="action_mute">Mute</string>
<string name="action_unmute">Unmute</string> <string name="action_unmute">Unmute</string>
<string name="action_mute_domain">Mute %s</string> <string name="action_mute_domain">Mute %s</string>
<string name="action_unmute_domain">Unmute %s</string>
<string name="action_mute_conversation">Mute conversation</string> <string name="action_mute_conversation">Mute conversation</string>
<string name="action_unmute_conversation">Unmute conversation</string> <string name="action_unmute_conversation">Unmute conversation</string>
<string name="action_mention">Mention</string> <string name="action_mention">Mention</string>

View File

@ -19,6 +19,7 @@ package com.keylesspalace.tusky
import android.text.SpannedString import android.text.SpannedString
import android.widget.EditText import android.widget.EditText
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.keylesspalace.tusky.appstore.*
import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.compose.ComposeViewModel
import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT
@ -143,6 +144,8 @@ class ComposeActivityTest {
activity.accountManager = accountManagerMock activity.accountManager = accountManagerMock
activity.viewModelFactory = viewModelFactoryMock activity.viewModelFactory = viewModelFactoryMock
activity.eventHub = EventHubImpl
controller.create().start() controller.create().start()
} }
@ -503,6 +506,7 @@ class ComposeActivityTest {
), ),
maximumTootCharacters, maximumTootCharacters,
null, null,
null,
null null
) )
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 275 KiB