Compare commits
13 Commits
c4b7296bfb
...
64d7e263fe
Author | SHA1 | Date |
---|---|---|
Hararan | 64d7e263fe | |
Alibek Omarov | d16c7f08ab | |
Alibek Omarov | 3c1b82056c | |
Alibek Omarov | 3e9c568e76 | |
Alibek Omarov | c062e9c180 | |
Alibek Omarov | 78352944c1 | |
Alibek Omarov | 6ff14af290 | |
Alibek Omarov | 9f4f805141 | |
Alibek Omarov | d68260ba4e | |
Alibek Omarov | b495462b33 | |
Konrad Pozniak | 9680bbf82b | |
Ivan Kupalov | 762be59036 | |
Alibek Omarov | b3259fcfd9 |
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -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...
|
// 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 {
|
} 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()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showMuteDomainWarningDialog(instance: String) {
|
private fun toggleBlockDomain(instance: String) {
|
||||||
AlertDialog.Builder(this)
|
if(blockingDomain) {
|
||||||
.setMessage(getString(R.string.mute_domain_warning, instance))
|
viewModel.unblockDomain(instance)
|
||||||
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.muteDomain(instance) }
|
} else {
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
AlertDialog.Builder(this)
|
||||||
.show()
|
.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() {
|
private fun toggleBlock() {
|
||||||
|
@ -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 -> {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,11 +201,13 @@ class ComposeActivity : BaseActivity(),
|
||||||
|
|
||||||
stickerKeyboard.isSticky = true
|
stickerKeyboard.isSticky = true
|
||||||
|
|
||||||
eventHub.events.subscribe { event ->
|
eventHub.events.observeOn(AndroidSchedulers.mainThread())
|
||||||
when(event) {
|
.autoDispose(this, Lifecycle.Event.ON_DESTROY)
|
||||||
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
|
.subscribe { event: Event? ->
|
||||||
}
|
when(event) {
|
||||||
}
|
is StatusPreviewEvent -> onStatusPreviewReady(event.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun uriToFilename(uri: Uri): String {
|
private fun uriToFilename(uri: Uri): String {
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 |
Loading…
Reference in New Issue