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
[![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 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 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)
![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
- "Reply to" feature that allows to jump to replied status, useful for hellthreading ;)
- Bigger emojis!
- "Preview" feature on Pleroma
### 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)!
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

View File

@ -178,7 +178,7 @@ dependencies {
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-iconics:$materialdrawerVersion"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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="action_send">โพสต์</string>
<string name="action_send_public">โพสต์!</string>
@ -15,7 +15,7 @@
<string name="action_hide_reblogs">ซ่อนรีพีต</string>
<string name="unreblog_private">ลบรีพีต</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_schedule_toot">โพสต์แบบกำหนดเวลา</string>
<string name="notification_boost_name">รีพีต</string>
@ -30,7 +30,7 @@
<string name="send_toot_notification_title">กำลังส่งโพสต์…</string>
<string name="send_toot_notification_error_title">เกิดข้อผิดพลาดในการส่งโพสต์</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="send_status_link_to">แบ่งปัน URL โพสต์ไป…</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 blocking: Boolean = false
private var muting: Boolean = false
private var blockingDomain: Boolean = false
private var showingReblogs: Boolean = false
private var subscribing: Boolean = false
private var loadedAccount: Account? = null
@ -543,6 +544,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
}
blocking = relation.blocking
muting = relation.muting
blockingDomain = relation.blockingDomain
showingReblogs = relation.showingReblogs
accountFollowsYouTextView.visible(relation.followedBy)
@ -665,7 +667,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
// If we can't get the domain, there's no way we can mute it anyway...
menu.removeItem(R.id.action_mute_domain)
} else {
muteDomain.title = getString(R.string.action_mute_domain, domain)
if (blockingDomain) {
muteDomain.title = getString(R.string.action_unmute_domain, domain)
} else {
muteDomain.title = getString(R.string.action_mute_domain, domain)
}
}
}
@ -710,12 +716,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
.show()
}
private fun showMuteDomainWarningDialog(instance: String) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.mute_domain_warning, instance))
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.muteDomain(instance) }
.setNegativeButton(android.R.string.cancel, null)
.show()
private fun toggleBlockDomain(instance: String) {
if(blockingDomain) {
viewModel.unblockDomain(instance)
} else {
AlertDialog.Builder(this)
.setMessage(getString(R.string.mute_domain_warning, instance))
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}
private fun toggleBlock() {
@ -796,7 +806,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
return true
}
R.id.action_mute_domain -> {
showMuteDomainWarningDialog(domain)
toggleBlockDomain(domain)
return true
}
R.id.action_show_reblogs -> {

View File

@ -142,6 +142,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
toolbar.setOnMenuItemClickListener { item: MenuItem ->
when (item.itemId) {
R.id.action_open_in_external_app -> openInExternalApp()
R.id.action_download -> requestDownloadMedia()
R.id.action_open_status -> onOpenStatus()
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)))
}
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

View File

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

View File

@ -54,6 +54,7 @@ import androidx.core.view.inputmethod.InputContentInfoCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -97,6 +98,8 @@ import kotlin.collections.ArrayList
import kotlin.math.max
import kotlin.math.min
import me.thanel.markdownedit.MarkdownEdit
import io.reactivex.android.schedulers.AndroidSchedulers
import com.uber.autodispose.android.lifecycle.autoDispose
class ComposeActivity : BaseActivity(),
ComposeOptionsListener,
@ -197,12 +200,14 @@ class ComposeActivity : BaseActivity(),
viewModel.setupComplete.value = true
stickerKeyboard.isSticky = true
eventHub.events.subscribe { event ->
when(event) {
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
}
}
eventHub.events.observeOn(AndroidSchedulers.mainThread())
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
.subscribe { event: Event? ->
when(event) {
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
}
}
}
private fun uriToFilename(uri: Uri): String {
@ -967,9 +972,8 @@ class ComposeActivity : BaseActivity(),
}
private fun onSendClicked(preview: Boolean) {
if(preview && previewBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
if(preview && previewBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
previewBehavior.state = BottomSheetBehavior.STATE_HIDDEN
return
}
if (verifyScheduledTime()) {
@ -988,8 +992,6 @@ class ComposeActivity : BaseActivity(),
emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN
scheduleBehavior.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. */
@ -1030,11 +1032,11 @@ class ComposeActivity : BaseActivity(),
this, getString(R.string.dialog_title_finishing_media_upload),
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()
if(!preview)
deleteDraftAndFinish()
})
}
} else {
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.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.util.withLifecycleContext
import com.ortiz.touchview.TouchImageView
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
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.orientation = LinearLayout.VERTICAL
val imageView = PhotoView(this).apply {
// If it seems a lot, try opening an image of A4 format or similar
maximumScale = 6.0f
val imageView = TouchImageView(this).apply {
maxZoom = 6f
}
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.StatusActionListener
import com.keylesspalace.tusky.util.CardViewMode
import com.keylesspalace.tusky.util.LinkHelper
import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.StatusDisplayOptions
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 {
fun newInstance() = SearchStatusesFragment()
}
@ -267,6 +272,10 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
}
openAsItem.title = openAsTitle
if(status.inReplyToId == null) {
popup.menu.findItem(R.id.status_reply_to)?.isVisible = false
}
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.status_share_content -> {
@ -296,6 +305,14 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl))
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 -> {
showOpenAsDialog(statusUrl!!, item.title)
return@setOnMenuItemClickListener true
@ -334,10 +351,6 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
showConfirmDeleteDialog(id, position)
return@setOnMenuItemClickListener true
}
R.id.status_delete_and_redraft -> {
showConfirmEditDialog(id, position, status)
return@setOnMenuItemClickListener true
}
R.id.pin -> {
viewModel.pinAccount(status, !status.isPinned())
return@setOnMenuItemClickListener true

View File

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

View File

@ -25,5 +25,6 @@ data class Relationship (
val muting: Boolean,
val requested: 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.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.viewdata.AttachmentViewData;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
@ -320,6 +321,10 @@ public abstract class SFragment extends BaseFragment implements Injectable {
clipboard.setPrimaryClip(clip);
return true;
}
case R.id.status_open_in_web: {
LinkHelper.openLinkInBrowser(Uri.parse(statusUrl), getContext());
return true;
}
case R.id.status_reply_to: {
onShowReplyTo(status.getInReplyToId());
return true;
@ -364,10 +369,6 @@ public abstract class SFragment extends BaseFragment implements Injectable {
showConfirmDeleteDialog(id, position);
return true;
}
case R.id.status_delete_and_redraft: {
showConfirmEditDialog(id, position, status);
return true;
}
case R.id.pin: {
timelineCases.pin(status, !status.isPinned());
return true;

View File

@ -21,9 +21,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.widget.ImageView
import android.widget.TextView
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.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.chrisbanes.photoview.PhotoViewAttacher
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide
@ -48,11 +45,11 @@ class ViewImageFragment : ViewMediaFragment() {
fun onPhotoTap()
}
private lateinit var attacher: PhotoViewAttacher
private lateinit var photoActionsListener: PhotoActionsListener
private lateinit var toolbar: View
private var transition = BehaviorSubject.create<Unit>()
private var shouldStartTransition = false
// 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.
@Volatile
@ -67,23 +64,6 @@ class ViewImageFragment : ViewMediaFragment() {
override fun setupMediaView(url: String, previewUrl: String?) {
descriptionView = mediaDescription
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
loadImageFromNetwork(url, previewUrl, photoView)
}
@ -94,10 +74,66 @@ class ViewImageFragment : ViewMediaFragment() {
return inflater.inflate(R.layout.fragment_view_image, container, false)
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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)
this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)
val url: String?
@ -116,6 +152,14 @@ class ViewImageFragment : ViewMediaFragment() {
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() {
photoActionsListener.onPhotoTap()
}
@ -155,7 +199,6 @@ class ViewImageFragment : ViewMediaFragment() {
.load(previewUrl)
.dontAnimate()
.onlyRetrieveFromCache(true)
.centerInside()
.addListener(ImageRequestListener(true, isThumnailRequest = true)))
else it
}
@ -164,7 +207,6 @@ class ViewImageFragment : ViewMediaFragment() {
.centerInside()
.addListener(ImageRequestListener(false, isThumnailRequest = false))
)
.centerInside()
.addListener(ImageRequestListener(true, isThumnailRequest = false))
.into(photoView)
}
@ -225,13 +267,7 @@ class ViewImageFragment : ViewMediaFragment() {
// another branch. take() will unsubscribe after we have it to not leak menmory
transition
.take(1)
.subscribe {
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()
}
.subscribe { target.onResourceReady(resource, null) }
}
return true
}

View File

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

View File

@ -27,6 +27,24 @@ inline fun <X, Y> LiveData<X>.switchMap(
crossinline switchMapFunction: (X) -> LiveData<Y>
): 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> {
val liveData = MediatorLiveData<X>()
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> {
override fun onResponse(call: Call<Any>, response: Response<Any>) {
if (response.isSuccessful) {
eventHub.dispatch(DomainMuteEvent(instance))
val relation = relationshipData.value?.data
if(relation != null) {
relationshipData.postValue(Success(relation.copy(blockingDomain = true)))
}
} 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) {
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:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="60dp"
android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
@ -276,7 +276,7 @@
android:paddingStart="24dp"
android:paddingTop="12dp"
android:paddingEnd="24dp"
android:paddingBottom="60dp"
android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
@ -290,7 +290,7 @@
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="52dp"
android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
@ -301,19 +301,20 @@
android:layout_height="300dp"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:paddingBottom="60dp"
android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
<ScrollView
<androidx.core.widget.NestedScrollView
android:id="@+id/previewScroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="300dp"
android:background="?attr/colorSurface"
android:elevation="12dp"
android:paddingBottom="60dp"
android:paddingBottom="@dimen/compose_activity_bottom_bar_height"
app:behavior_hideable="true"
app:behavior_skipCollapsed="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
>
@ -323,7 +324,7 @@
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
/>
</ScrollView>
</androidx.core.widget.NestedScrollView>
<RelativeLayout
android:layout_width="match_parent"
@ -459,7 +460,7 @@
android:layout_width="32dp"
android:layout_height="wrap_content"
android:gravity="center"
app:icon="@drawable/ic_eye_24dp"
app:icon="@drawable/ic_preview_24dp"
android:layout_marginStart="10dp"
android:visibility="gone"
android:layout_toLeftOf="@+id/composeTootButton"

View File

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

View File

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

View File

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

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
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
android:id="@+id/action_download"
android:icon="@drawable/ic_file_download_black_24dp"

View File

@ -3,10 +3,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
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
android:id="@+id/action_reveal"
android:title="@string/expand_collapse_all_statuses"

View File

@ -17,6 +17,7 @@
<dimen name="card_image_horizontal_width">100dp</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_avatar_size">100dp</dimen>

View File

@ -9,6 +9,7 @@
<string name="action_enable_formatting_syntax">Enable %s</string>
<string name="action_disable_formatting_syntax">Disable %s</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>

View File

@ -109,6 +109,7 @@
<string name="action_mute">Mute</string>
<string name="action_unmute">Unmute</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_unmute_conversation">Unmute conversation</string>
<string name="action_mention">Mention</string>

View File

@ -19,6 +19,7 @@ package com.keylesspalace.tusky
import android.text.SpannedString
import android.widget.EditText
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.ComposeViewModel
import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT
@ -142,6 +143,8 @@ class ComposeActivityTest {
activity.accountManager = accountManagerMock
activity.viewModelFactory = viewModelFactoryMock
activity.eventHub = EventHubImpl
controller.create().start()
}
@ -503,6 +506,7 @@ class ComposeActivityTest {
),
maximumTootCharacters,
null,
null,
null
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 275 KiB