Added support for ZWSP character for toots
This adds support for adding a ZWSP character in toots for non-spaced custom emojis. This fixes https://git.mentality.rip/FWGS/Husky/issues/113.
This commit is contained in:
parent
69230e1c3f
commit
af78abcc0a
7 changed files with 313 additions and 142 deletions
|
@ -18,7 +18,7 @@
|
|||
<string name="action_expand_menu">Expand menu</string>
|
||||
|
||||
<string name="title_emoji_reacted_by">%s reacted by</string>
|
||||
|
||||
|
||||
<string name="hint_appname">Application name</string>
|
||||
<string name="hint_website">Application website</string>
|
||||
|
||||
|
@ -44,6 +44,9 @@
|
|||
<string name="pref_title_other">Other</string>
|
||||
<string name="pref_title_privacy">Privacy</string>
|
||||
|
||||
<string name="pref_title_composing">Composing</string>
|
||||
<string name="pref_title_composing_title">Composing using zero-width space characters in emojis</string>
|
||||
|
||||
<string name="pref_title_anonymize_upload_filenames">Anonymize uploaded file names</string>
|
||||
<string name="pref_title_live_notifications">Live notifications</string>
|
||||
<string name="pref_summary_live_notifications">May slightly increase power consumption</string>
|
||||
|
@ -67,7 +70,7 @@
|
|||
|
||||
<string name="streaming_notification_name">Live notifications</string>
|
||||
<string name="streaming_notification_description">Running live notifications for: </string>
|
||||
|
||||
|
||||
<!-- REPLACEMENT FOR TUSKY STRINGS -->
|
||||
<string name="action_toggle_visibility">Post visibility</string>
|
||||
<string name="action_schedule_toot">Schedule post</string>
|
||||
|
@ -83,49 +86,49 @@
|
|||
<string name="reblog_private">Repeat to original audience</string>
|
||||
<string name="unreblog_private">Remove repeat</string>
|
||||
<string name="action_open_toot">Open post</string>
|
||||
|
||||
|
||||
<string name="compose_shortcut_long_label">Compose Post</string>
|
||||
|
||||
|
||||
<string name="description_status_reblogged">
|
||||
Repeated
|
||||
</string>
|
||||
<string name="dialog_delete_toot_warning">Delete this post?</string>
|
||||
<string name="dialog_redraft_toot_warning">Delete and re-draft this post?</string>
|
||||
|
||||
|
||||
<string name="error_sender_account_gone">Error sending post.</string>
|
||||
|
||||
|
||||
<string name="notification_reblog_format">%s repeated your post</string>
|
||||
<string name="notification_favourite_format">%s favorited your post</string>
|
||||
<string name="notification_boost_name">Repeats</string>
|
||||
<string name="notification_boost_description">Notifications when your posts get repeated</string>
|
||||
<string name="notification_favourite_description">Notifications when your posts get marked as favorite</string>
|
||||
|
||||
|
||||
<string name="pref_title_confirm_reblogs">Show confirmation dialog before repeating</string>
|
||||
<string name="pref_title_notification_filter_reblogs">my posts are repeated</string>
|
||||
<string name="pref_title_show_boosts">Show repeats</string>
|
||||
<string name="pref_title_alway_open_spoiler">Always expand posts marked with content warnings</string>
|
||||
|
||||
|
||||
<plurals name="reblogs">
|
||||
<item quantity="one"><b>%s</b> Repeat</item>
|
||||
<item quantity="other"><b>%s</b> Repeats</item>
|
||||
</plurals>
|
||||
</plurals>
|
||||
<string name="send_status_link_to">Share post URL to…</string>
|
||||
<string name="send_status_content_to">Share post to…</string>
|
||||
<string name="send_toot_notification_title">Sending post…</string>
|
||||
<string name="send_toot_notification_error_title">Error sending post</string>
|
||||
<string name="send_toot_notification_channel_name">Sending posts</string>
|
||||
<string name="send_toot_notification_saved_content">A copy of the post has been saved to your drafts</string>
|
||||
|
||||
|
||||
<string name="status_share_content">Share content of post</string>
|
||||
<string name="status_share_link">Share link to post</string>
|
||||
<string name="status_boosted_format">%s repeated</string>
|
||||
<string name="status_replied_to_format">Reply to %s</string>
|
||||
|
||||
|
||||
<string name="title_scheduled_toot">Scheduled posts</string>
|
||||
<string name="title_reblogged_by">Repeated by</string>
|
||||
<string name="title_view_thread">Post</string>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
<string name="about_tusky_version">Husky %s</string>
|
||||
<string name="about_powered_by_tusky">Powered by Husky</string>
|
||||
<string name="about_tusky_license">Husky is free and open-source software.
|
||||
|
@ -136,7 +139,7 @@
|
|||
We sometimes call it “libre software,” borrowing the French or Spanish word for “free” as in freedom,
|
||||
to show we do not mean the software is gratis. Source: https://www.gnu.org/philosophy/free-sw.html
|
||||
* the url can be changed to link to the localized version of the license.
|
||||
--> <!--
|
||||
--> <!--
|
||||
<string name="about_project_site">
|
||||
Project website:\n
|
||||
https://husky.fwgs.ru
|
||||
|
@ -150,7 +153,7 @@
|
|||
<string name="license_description">Husky contains code and assets from the following open source projects:</string>
|
||||
<string name="add_account_description">Add new Fediverse Account</string>
|
||||
<string name="action_login">Login!</string>
|
||||
|
||||
|
||||
<string name="dialog_whats_an_instance">The address or domain of any instance can be entered
|
||||
here, such as shitposter.club, blob.cat, fedi.absturztau.be, expired.mentality.rip, and
|
||||
<a href="https://instances.social">more!</a>
|
||||
|
@ -160,7 +163,7 @@
|
|||
you were on the same site.
|
||||
\n\nMore info can be found at <a href="https://joinmastodon.org">joinmastodon.org</a>.
|
||||
</string>
|
||||
|
||||
|
||||
<string name="warning_scheduling_interval">Mastodon/Pleroma has a minimum scheduling interval of 5 minutes.</string> -->
|
||||
</resources>
|
||||
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ import com.keylesspalace.tusky.components.common.toFileName
|
|||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
|
||||
import com.keylesspalace.tusky.core.extensions.composeWithZwsp
|
||||
import com.keylesspalace.tusky.core.extensions.viewBinding
|
||||
import com.keylesspalace.tusky.databinding.ActivityComposeBinding
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
|
@ -226,6 +227,9 @@ class ComposeActivity : BaseActivity(),
|
|||
binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt)
|
||||
}
|
||||
|
||||
viewModel.composeWithZwsp.value =
|
||||
preferences.getBoolean(PrefKeys.COMPOSING_ZWSP_CHAR, false)
|
||||
|
||||
setupComposeField(viewModel.startingText)
|
||||
setupContentWarningField(composeOptions?.contentWarning)
|
||||
setupPollView()
|
||||
|
@ -264,7 +268,6 @@ class ComposeActivity : BaseActivity(),
|
|||
}
|
||||
}
|
||||
} else if(type == "text/plain" && intent.action == Intent.ACTION_SEND) {
|
||||
|
||||
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT).orEmpty()
|
||||
val shareBody = if(!subject.isNullOrBlank() && subject !in text) {
|
||||
|
@ -446,7 +449,11 @@ class ComposeActivity : BaseActivity(),
|
|||
viewModel.instanceStickers.observe { stickers ->
|
||||
if(stickers.isNotEmpty()) {
|
||||
binding.composeStickerButton.visibility = View.VISIBLE
|
||||
enableButton(binding.composeStickerButton, true, true)
|
||||
enableButton(
|
||||
binding.composeStickerButton,
|
||||
clickable = true,
|
||||
colorActive = true
|
||||
)
|
||||
binding.stickerKeyboard.setupStickerKeyboard(this@ComposeActivity, stickers)
|
||||
}
|
||||
}
|
||||
|
@ -1146,7 +1153,13 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
private fun sendStatus(preview: Boolean) {
|
||||
enableButtons(false)
|
||||
val contentText = binding.composeEditField.text.toString()
|
||||
val tempText = binding.composeEditField.text.toString()
|
||||
val contentText = if(viewModel.composeWithZwsp.value == true) {
|
||||
tempText.composeWithZwsp()
|
||||
} else {
|
||||
tempText
|
||||
}
|
||||
|
||||
var spoilerText = ""
|
||||
if(viewModel.showContentWarning.value!!) {
|
||||
spoilerText = binding.composeContentWarningField.text.toString()
|
||||
|
|
|
@ -1,32 +1,34 @@
|
|||
/* Copyright 2019 Tusky Contributors
|
||||
/*
|
||||
* Husky -- A Pleroma client for Android
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
* Copyright (C) 2021 The Husky Developers
|
||||
* Copyright (C) 2019 Tusky Contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.compose
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import com.keylesspalace.tusky.components.common.CommonComposeViewModel
|
||||
import com.keylesspalace.tusky.components.common.MediaUploader
|
||||
import com.keylesspalace.tusky.components.common.mutableLiveData
|
||||
import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia
|
||||
import com.keylesspalace.tusky.components.drafts.DraftHelper
|
||||
import com.keylesspalace.tusky.components.search.SearchType
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
|
@ -35,19 +37,24 @@ import com.keylesspalace.tusky.entity.Status
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.service.ServiceClient
|
||||
import com.keylesspalace.tusky.service.TootToSend
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.SaveTootHelper
|
||||
import com.keylesspalace.tusky.util.combineLiveData
|
||||
import com.keylesspalace.tusky.util.filter
|
||||
import com.keylesspalace.tusky.util.map
|
||||
import com.keylesspalace.tusky.util.randomAlphanumericString
|
||||
import com.keylesspalace.tusky.util.toLiveData
|
||||
import io.reactivex.Observable.just
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
|
||||
class ComposeViewModel @Inject constructor(
|
||||
private val api: MastodonApi,
|
||||
private val accountManager: AccountManager,
|
||||
private val mediaUploader: MediaUploader,
|
||||
private val serviceClient: ServiceClient,
|
||||
private val draftHelper: DraftHelper,
|
||||
private val saveTootHelper: SaveTootHelper,
|
||||
private val db: AppDatabase
|
||||
private val api: MastodonApi,
|
||||
private val accountManager: AccountManager,
|
||||
private val mediaUploader: MediaUploader,
|
||||
private val serviceClient: ServiceClient,
|
||||
private val draftHelper: DraftHelper,
|
||||
private val saveTootHelper: SaveTootHelper,
|
||||
private val db: AppDatabase
|
||||
) : CommonComposeViewModel(api, accountManager, mediaUploader, db) {
|
||||
|
||||
private var replyingStatusAuthor: String? = null
|
||||
|
@ -63,7 +70,7 @@ class ComposeViewModel @Inject constructor(
|
|||
private var modifiedInitialState: Boolean = false
|
||||
|
||||
val markMediaAsSensitive =
|
||||
mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
|
||||
mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false)
|
||||
|
||||
val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN)
|
||||
val showContentWarning = mutableLiveData(false)
|
||||
|
@ -71,6 +78,7 @@ class ComposeViewModel @Inject constructor(
|
|||
val poll: MutableLiveData<NewPoll?> = mutableLiveData(null)
|
||||
val scheduledAt: MutableLiveData<String?> = mutableLiveData(null)
|
||||
val formattingSyntax: MutableLiveData<String> = mutableLiveData("")
|
||||
val composeWithZwsp: MutableLiveData<Boolean> = mutableLiveData(false)
|
||||
|
||||
private val isEditingScheduledToot get() = !scheduledTootId.isNullOrEmpty()
|
||||
|
||||
|
@ -98,17 +106,16 @@ class ComposeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun deleteDraft() {
|
||||
if (savedTootUid != 0) {
|
||||
if(savedTootUid != 0) {
|
||||
saveTootHelper.deleteDraft(savedTootUid)
|
||||
}
|
||||
if (draftId != 0) {
|
||||
if(draftId != 0) {
|
||||
draftHelper.deleteDraftAndAttachments(draftId)
|
||||
.subscribe()
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveDraft(content: String, contentWarning: String) {
|
||||
|
||||
val mediaUris: MutableList<String> = mutableListOf()
|
||||
val mediaDescriptions: MutableList<String?> = mutableListOf()
|
||||
media.value?.forEach { item ->
|
||||
|
@ -116,18 +123,18 @@ class ComposeViewModel @Inject constructor(
|
|||
mediaDescriptions.add(item.description)
|
||||
}
|
||||
draftHelper.saveDraft(
|
||||
draftId = draftId,
|
||||
accountId = accountManager.activeAccount?.id!!,
|
||||
inReplyToId = inReplyToId,
|
||||
content = content,
|
||||
contentWarning = contentWarning,
|
||||
sensitive = markMediaAsSensitive.value!!,
|
||||
visibility = statusVisibility.value!!,
|
||||
mediaUris = mediaUris,
|
||||
mediaDescriptions = mediaDescriptions,
|
||||
poll = poll.value,
|
||||
formattingSyntax = formattingSyntax.value!!,
|
||||
failedToSend = false
|
||||
draftId = draftId,
|
||||
accountId = accountManager.activeAccount?.id!!,
|
||||
inReplyToId = inReplyToId,
|
||||
content = content,
|
||||
contentWarning = contentWarning,
|
||||
sensitive = markMediaAsSensitive.value!!,
|
||||
visibility = statusVisibility.value!!,
|
||||
mediaUris = mediaUris,
|
||||
mediaDescriptions = mediaDescriptions,
|
||||
poll = poll.value,
|
||||
formattingSyntax = formattingSyntax.value!!,
|
||||
failedToSend = false
|
||||
).subscribe()
|
||||
}
|
||||
|
||||
|
@ -137,60 +144,60 @@ class ComposeViewModel @Inject constructor(
|
|||
* @return LiveData which will signal once the screen can be closed or null if there are errors
|
||||
*/
|
||||
fun sendStatus(
|
||||
content: String,
|
||||
spoilerText: String,
|
||||
preview: Boolean
|
||||
content: String,
|
||||
spoilerText: String,
|
||||
preview: Boolean
|
||||
): LiveData<Unit> {
|
||||
|
||||
val deletionObservable = if (isEditingScheduledToot) {
|
||||
val deletionObservable = if(isEditingScheduledToot) {
|
||||
api.deleteScheduledStatus(scheduledTootId.toString()).toObservable().map { }
|
||||
} else {
|
||||
just(Unit)
|
||||
}.toLiveData()
|
||||
|
||||
val sendObservable = media
|
||||
.filter { items -> items.all { it.uploadPercent == -1 } }
|
||||
.map {
|
||||
val mediaIds = ArrayList<String>()
|
||||
val mediaUris = ArrayList<Uri>()
|
||||
val mediaDescriptions = ArrayList<String>()
|
||||
for (item in media.value!!) {
|
||||
mediaIds.add(item.id!!)
|
||||
mediaUris.add(item.uri)
|
||||
mediaDescriptions.add(item.description ?: "")
|
||||
}
|
||||
|
||||
val tootToSend = TootToSend(
|
||||
text = content,
|
||||
warningText = spoilerText,
|
||||
visibility = statusVisibility.value!!.serverString(),
|
||||
sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!),
|
||||
mediaIds = mediaIds,
|
||||
mediaUris = mediaUris.map { it.toString() },
|
||||
mediaDescriptions = mediaDescriptions,
|
||||
scheduledAt = scheduledAt.value,
|
||||
inReplyToId = inReplyToId,
|
||||
poll = poll.value,
|
||||
replyingStatusContent = null,
|
||||
replyingStatusAuthorUsername = null,
|
||||
formattingSyntax = formattingSyntax.value!!,
|
||||
preview = preview,
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
savedTootUid = savedTootUid,
|
||||
draftId = draftId,
|
||||
idempotencyKey = randomAlphanumericString(16),
|
||||
retries = 0
|
||||
)
|
||||
|
||||
serviceClient.sendToot(tootToSend)
|
||||
.filter { items -> items.all { it.uploadPercent == -1 } }
|
||||
.map {
|
||||
val mediaIds = ArrayList<String>()
|
||||
val mediaUris = ArrayList<Uri>()
|
||||
val mediaDescriptions = ArrayList<String>()
|
||||
for(item in media.value!!) {
|
||||
mediaIds.add(item.id!!)
|
||||
mediaUris.add(item.uri)
|
||||
mediaDescriptions.add(item.description ?: "")
|
||||
}
|
||||
|
||||
val tootToSend = TootToSend(
|
||||
text = content,
|
||||
warningText = spoilerText,
|
||||
visibility = statusVisibility.value!!.serverString(),
|
||||
sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!),
|
||||
mediaIds = mediaIds,
|
||||
mediaUris = mediaUris.map { it.toString() },
|
||||
mediaDescriptions = mediaDescriptions,
|
||||
scheduledAt = scheduledAt.value,
|
||||
inReplyToId = inReplyToId,
|
||||
poll = poll.value,
|
||||
replyingStatusContent = null,
|
||||
replyingStatusAuthorUsername = null,
|
||||
formattingSyntax = formattingSyntax.value!!,
|
||||
preview = preview,
|
||||
accountId = accountManager.activeAccount!!.id,
|
||||
savedTootUid = savedTootUid,
|
||||
draftId = draftId,
|
||||
idempotencyKey = randomAlphanumericString(16),
|
||||
retries = 0
|
||||
)
|
||||
|
||||
serviceClient.sendToot(tootToSend)
|
||||
}
|
||||
|
||||
return combineLiveData(deletionObservable, sendObservable) { _, _ -> }
|
||||
}
|
||||
|
||||
fun setup(composeOptions: ComposeActivity.ComposeOptions?) {
|
||||
|
||||
if (setupComplete.value == true) {
|
||||
if(setupComplete.value == true) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -198,17 +205,18 @@ class ComposeViewModel @Inject constructor(
|
|||
|
||||
val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN
|
||||
startingVisibility = Status.Visibility.byNum(
|
||||
preferredVisibility.num.coerceAtLeast(replyVisibility.num))
|
||||
preferredVisibility.num.coerceAtLeast(replyVisibility.num)
|
||||
)
|
||||
|
||||
inReplyToId = composeOptions?.inReplyToId
|
||||
|
||||
modifiedInitialState = composeOptions?.modifiedInitialState == true
|
||||
|
||||
val contentWarning = composeOptions?.contentWarning
|
||||
if (contentWarning != null) {
|
||||
if(contentWarning != null) {
|
||||
startingContentWarning = contentWarning
|
||||
}
|
||||
if (!contentWarningStateChanged) {
|
||||
if(!contentWarningStateChanged) {
|
||||
showContentWarning.value = !contentWarning.isNullOrBlank()
|
||||
}
|
||||
|
||||
|
@ -216,22 +224,27 @@ class ComposeViewModel @Inject constructor(
|
|||
val loadedDraftMediaUris = composeOptions?.mediaUrls
|
||||
val loadedDraftMediaDescriptions: List<String?>? = composeOptions?.mediaDescriptions
|
||||
val draftAttachments = composeOptions?.draftAttachments
|
||||
if (loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) {
|
||||
if(loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) {
|
||||
// when coming from SavedTootActivity
|
||||
loadedDraftMediaUris.zip(loadedDraftMediaDescriptions)
|
||||
.forEach { (uri, description) ->
|
||||
pickMedia(uri.toUri(), null).observeForever { errorOrItem ->
|
||||
if (errorOrItem.isRight() && description != null) {
|
||||
updateDescription(errorOrItem.asRight().localId, description)
|
||||
}
|
||||
.forEach { (uri, description) ->
|
||||
pickMedia(uri.toUri(), null).observeForever { errorOrItem ->
|
||||
if(errorOrItem.isRight() && description != null) {
|
||||
updateDescription(errorOrItem.asRight().localId, description)
|
||||
}
|
||||
}
|
||||
} else if (draftAttachments != null) {
|
||||
}
|
||||
} else if(draftAttachments != null) {
|
||||
// when coming from DraftActivity
|
||||
draftAttachments.forEach { attachment -> pickMedia(attachment.uri, attachment.description) }
|
||||
draftAttachments.forEach { attachment ->
|
||||
pickMedia(
|
||||
attachment.uri,
|
||||
attachment.description
|
||||
)
|
||||
}
|
||||
} else composeOptions?.mediaAttachments?.forEach { a ->
|
||||
// when coming from redraft or ScheduledTootActivity
|
||||
val mediaType = when (a.type) {
|
||||
val mediaType = when(a.type) {
|
||||
Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO
|
||||
Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE
|
||||
Attachment.Type.AUDIO -> QueuedMedia.Type.AUDIO
|
||||
|
@ -245,14 +258,14 @@ class ComposeViewModel @Inject constructor(
|
|||
startingText = composeOptions?.tootText
|
||||
|
||||
val tootVisibility = composeOptions?.visibility ?: Status.Visibility.UNKNOWN
|
||||
if (tootVisibility.num != Status.Visibility.UNKNOWN.num) {
|
||||
if(tootVisibility.num != Status.Visibility.UNKNOWN.num) {
|
||||
startingVisibility = tootVisibility
|
||||
}
|
||||
statusVisibility.value = startingVisibility
|
||||
val mentionedUsernames = composeOptions?.mentionedUsernames
|
||||
if (mentionedUsernames != null) {
|
||||
if(mentionedUsernames != null) {
|
||||
val builder = StringBuilder()
|
||||
for (name in mentionedUsernames) {
|
||||
for(name in mentionedUsernames) {
|
||||
builder.append('@')
|
||||
builder.append(name)
|
||||
builder.append(' ')
|
||||
|
@ -265,13 +278,14 @@ class ComposeViewModel @Inject constructor(
|
|||
composeOptions?.sensitive?.let { markMediaAsSensitive.value = it }
|
||||
|
||||
val poll = composeOptions?.poll
|
||||
if (poll != null && composeOptions.mediaAttachments.isNullOrEmpty()) {
|
||||
if(poll != null && composeOptions.mediaAttachments.isNullOrEmpty()) {
|
||||
this.poll.value = poll
|
||||
}
|
||||
replyingStatusContent = composeOptions?.replyingStatusContent
|
||||
replyingStatusAuthor = composeOptions?.replyingStatusAuthor
|
||||
|
||||
formattingSyntax.value = composeOptions?.formattingSyntax ?: accountManager.activeAccount!!.defaultFormattingSyntax
|
||||
|
||||
formattingSyntax.value = composeOptions?.formattingSyntax
|
||||
?: accountManager.activeAccount!!.defaultFormattingSyntax
|
||||
}
|
||||
|
||||
fun updatePoll(newPoll: NewPoll) {
|
||||
|
@ -283,7 +297,7 @@ class ComposeViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onCleared() {
|
||||
for (uploadDisposable in mediaToDisposable.values) {
|
||||
for(uploadDisposable in mediaToDisposable.values) {
|
||||
uploadDisposable.dispose()
|
||||
}
|
||||
super.onCleared()
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
/* Copyright 2018 Conny Duck
|
||||
/*
|
||||
* Husky -- A Pleroma client for Android
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
* Copyright (C) 2021 The Husky Developers
|
||||
* Copyright (C) 2018 Conny Duck
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.components.preference
|
||||
|
||||
|
@ -22,7 +27,14 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.settings.*
|
||||
import com.keylesspalace.tusky.settings.AppTheme
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.settings.emojiPreference
|
||||
import com.keylesspalace.tusky.settings.listPreference
|
||||
import com.keylesspalace.tusky.settings.makePreferenceScreen
|
||||
import com.keylesspalace.tusky.settings.preference
|
||||
import com.keylesspalace.tusky.settings.preferenceCategory
|
||||
import com.keylesspalace.tusky.settings.switchPreference
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.deserialize
|
||||
import com.keylesspalace.tusky.util.getNonNullString
|
||||
|
@ -31,8 +43,8 @@ import com.mikepenz.iconics.IconicsDrawable
|
|||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.iconics.utils.colorInt
|
||||
import com.mikepenz.iconics.utils.sizePx
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Inject
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||
|
||||
|
@ -153,9 +165,15 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
isSingleLineTitle = false
|
||||
setOnPreferenceClickListener {
|
||||
activity?.let { activity ->
|
||||
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.TAB_FILTER_PREFERENCES)
|
||||
val intent = PreferencesActivity.newIntent(
|
||||
activity,
|
||||
PreferencesActivity.TAB_FILTER_PREFERENCES
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
||||
activity.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -181,14 +199,14 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setTitle(R.string.pref_title_enable_swipe_for_tabs)
|
||||
isSingleLineTitle = false
|
||||
}
|
||||
|
||||
|
||||
switchPreference {
|
||||
setDefaultValue(true)
|
||||
key = PrefKeys.BIG_EMOJIS
|
||||
setTitle(R.string.pref_title_enable_big_emojis)
|
||||
isSingleLineTitle = false
|
||||
}
|
||||
|
||||
|
||||
switchPreference {
|
||||
setDefaultValue(false)
|
||||
key = PrefKeys.STICKERS
|
||||
|
@ -211,6 +229,15 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
preferenceCategory(R.string.pref_title_composing) {
|
||||
switchPreference {
|
||||
setDefaultValue(false)
|
||||
key = PrefKeys.COMPOSING_ZWSP_CHAR
|
||||
setTitle(R.string.pref_title_composing_title)
|
||||
isSingleLineTitle = false
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory(R.string.pref_title_privacy) {
|
||||
switchPreference {
|
||||
setDefaultValue(false)
|
||||
|
@ -234,9 +261,15 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setTitle(R.string.pref_title_status_tabs)
|
||||
setOnPreferenceClickListener {
|
||||
activity?.let { activity ->
|
||||
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.TAB_FILTER_PREFERENCES)
|
||||
val intent = PreferencesActivity.newIntent(
|
||||
activity,
|
||||
PreferencesActivity.TAB_FILTER_PREFERENCES
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
||||
activity.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -249,10 +282,11 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setDefaultValue(false)
|
||||
key = PrefKeys.WELLBEING_LIMITED_NOTIFICATIONS
|
||||
setOnPreferenceChangeListener { _, value ->
|
||||
for (account in accountManager.accounts) {
|
||||
val notificationFilter = deserialize(account.notificationsFilter).toMutableSet()
|
||||
for(account in accountManager.accounts) {
|
||||
val notificationFilter =
|
||||
deserialize(account.notificationsFilter).toMutableSet()
|
||||
|
||||
if (value == true) {
|
||||
if(value == true) {
|
||||
notificationFilter.add(Notification.Type.FAVOURITE)
|
||||
notificationFilter.add(Notification.Type.FOLLOW)
|
||||
notificationFilter.add(Notification.Type.REBLOG)
|
||||
|
@ -287,9 +321,15 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
setTitle(R.string.pref_title_http_proxy_settings)
|
||||
setOnPreferenceClickListener {
|
||||
activity?.let { activity ->
|
||||
val intent = PreferencesActivity.newIntent(activity, PreferencesActivity.PROXY_PREFERENCES)
|
||||
val intent = PreferencesActivity.newIntent(
|
||||
activity,
|
||||
PreferencesActivity.PROXY_PREFERENCES
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
activity.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
||||
activity.overridePendingTransition(
|
||||
R.anim.slide_from_right,
|
||||
R.anim.slide_to_left
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -319,13 +359,13 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
|
||||
try {
|
||||
val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1")
|
||||
.toInt()
|
||||
.toInt()
|
||||
|
||||
if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) {
|
||||
if(httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) {
|
||||
httpProxyPref?.summary = "$httpServer:$httpPort"
|
||||
return
|
||||
}
|
||||
} catch (e: NumberFormatException) {
|
||||
} catch(e: NumberFormatException) {
|
||||
// user has entered wrong port, fall back to empty summary
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Husky -- A Pleroma client for Android
|
||||
*
|
||||
* Copyright (C) 2021 The Husky Developers
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.core.extensions
|
||||
|
||||
/**
|
||||
* Returns if a char is a breakline or not.
|
||||
*
|
||||
* @return True if it is a breakline, False otherwise.
|
||||
*/
|
||||
fun Char.isBreakline(): Boolean {
|
||||
return (this == '\n')
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Husky -- A Pleroma client for Android
|
||||
*
|
||||
* Copyright (C) 2021 The Husky Developers
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.core.extensions
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Returns the text with emojis and zero-width space characters at the start and end positions.
|
||||
*
|
||||
* @return String with zero-width space characters at start and end positions for emojis.
|
||||
*/
|
||||
fun String.composeWithZwsp(): String {
|
||||
val zwspChar = '\u200b'
|
||||
val pattern = Pattern.compile("(:)([a-zA-Z0-9_]*)(:( )?(\\R)?)")
|
||||
val matcher = pattern.matcher(this)
|
||||
|
||||
var end: Int
|
||||
val originalString = StringBuilder(this)
|
||||
while(matcher.find()) {
|
||||
end = matcher.end()
|
||||
|
||||
if(end < originalString.length) {
|
||||
val endChar = originalString[end - 1]
|
||||
|
||||
if(endChar.isWhitespace()) {
|
||||
if(!originalString[end].isLetterOrDigit() && !endChar.isBreakline()) {
|
||||
originalString.setCharAt(end - 1, zwspChar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalString.toString().trim()
|
||||
}
|
|
@ -1,3 +1,23 @@
|
|||
/*
|
||||
* Husky -- A Pleroma client for Android
|
||||
*
|
||||
* Copyright (C) 2021 The Husky Developers
|
||||
* Copyright (C) 2020 Tusky Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.settings
|
||||
|
||||
enum class AppTheme(val value: String) {
|
||||
|
@ -37,6 +57,7 @@ object PrefKeys {
|
|||
const val HIDE_MUTED_USERS = "hideMutedUsers"
|
||||
const val ANIMATE_CUSTOM_EMOJIS = "animateCustomEmojis"
|
||||
const val RENDER_STATUS_AS_MENTION = "renderStatusAsMention"
|
||||
const val COMPOSING_ZWSP_CHAR = "composingZwspChar"
|
||||
|
||||
const val CUSTOM_TABS = "customTabs"
|
||||
const val WELLBEING_LIMITED_NOTIFICATIONS = "wellbeingModeLimitedNotifications"
|
||||
|
|
Loading…
Reference in a new issue