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:
Adolfo Santiago 2021-11-20 13:21:54 +01:00
parent 69230e1c3f
commit af78abcc0a
No known key found for this signature in database
GPG key ID: 244D6F9A317B4A65
7 changed files with 313 additions and 142 deletions

View file

@ -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">&lt;b>%s&lt;/b> Repeat</item>
<item quantity="other">&lt;b>%s&lt;/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>

View file

@ -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()

View file

@ -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()

View file

@ -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
}

View file

@ -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')
}

View file

@ -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()
}

View file

@ -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"