Add language dropdown to compose view (#2651)
* Add UI for selecting post language * Apply selected language when sending status * Save/restore post language with drafts * Fall back to english if the configured language isn't found in the locale list (no-NB) * Remove comment about no_NB * Move language dropdown to top of compose view * Preserve language when redrafting * Set default language to target post's language when replying * Add Tusky license header to new source file * Tweak language dropdown button width
This commit is contained in:
parent
c638ad7e6b
commit
0041acf2d4
28 changed files with 1140 additions and 28 deletions
|
|
@ -35,6 +35,7 @@ import android.view.KeyEvent
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.PopupMenu
|
||||
|
|
@ -65,6 +66,7 @@ import com.keylesspalace.tusky.BaseActivity
|
|||
import com.keylesspalace.tusky.BuildConfig
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.EmojiAdapter
|
||||
import com.keylesspalace.tusky.adapter.LocaleAdapter
|
||||
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
|
||||
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
|
||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||
|
|
@ -244,6 +246,7 @@ class ComposeActivity :
|
|||
binding.composeScheduleView.setDateTime(composeOptions?.scheduledAt)
|
||||
}
|
||||
|
||||
setupLanguageSpinner(getInitialLanguage(composeOptions?.language))
|
||||
setupComposeField(preferences, viewModel.startingText)
|
||||
setupContentWarningField(composeOptions?.contentWarning)
|
||||
setupPollView()
|
||||
|
|
@ -476,6 +479,40 @@ class ComposeActivity :
|
|||
binding.addPollTextActionTextView.setOnClickListener { openPollDialog() }
|
||||
}
|
||||
|
||||
private fun setupLanguageSpinner(initialLanguage: String?) {
|
||||
val locales = Locale.getAvailableLocales()
|
||||
.filter { it.country.isNullOrEmpty() && it.script.isNullOrEmpty() && it.variant.isNullOrEmpty() } // Only "base" languages, "en" but not "en_DK"
|
||||
var currentLocaleIndex = locales.indexOfFirst { it.language == initialLanguage }
|
||||
if (currentLocaleIndex < 0) {
|
||||
Log.e(TAG, "Error looking up language tag '$initialLanguage', falling back to english")
|
||||
currentLocaleIndex = locales.indexOfFirst { it.language == "en" }
|
||||
}
|
||||
|
||||
val context = this
|
||||
binding.composePostLanguageButton.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||
viewModel.postLanguage = (parent.adapter.getItem(position) as Locale).language
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>) {
|
||||
parent.setSelection(locales.indexOfFirst { it.language == getInitialLanguage() })
|
||||
}
|
||||
}
|
||||
binding.composePostLanguageButton.apply {
|
||||
adapter = LocaleAdapter(context, android.R.layout.simple_spinner_dropdown_item, locales)
|
||||
setSelection(currentLocaleIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialLanguage(language: String? = null): String {
|
||||
return if (language.isNullOrEmpty()) {
|
||||
// Setting the application ui preference sets the default locale
|
||||
Locale.getDefault().language
|
||||
} else {
|
||||
language
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.run {
|
||||
|
|
@ -793,6 +830,10 @@ class ComposeActivity :
|
|||
return length
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
val selectedLanguage: String?
|
||||
get() = viewModel.postLanguage
|
||||
|
||||
private fun updateVisibleCharactersLeft() {
|
||||
val remainingLength = maximumTootCharacters - calculateTextLength()
|
||||
binding.composeCharactersLeftView.text = String.format(Locale.getDefault(), "%d", remainingLength)
|
||||
|
|
@ -1128,7 +1169,8 @@ class ComposeActivity :
|
|||
var scheduledAt: String? = null,
|
||||
var sensitive: Boolean? = null,
|
||||
var poll: NewPoll? = null,
|
||||
var modifiedInitialState: Boolean? = null
|
||||
var modifiedInitialState: Boolean? = null,
|
||||
var language: String? = null,
|
||||
) : Parcelable
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ class ComposeViewModel @Inject constructor(
|
|||
private var replyingStatusAuthor: String? = null
|
||||
private var replyingStatusContent: String? = null
|
||||
internal var startingText: String? = null
|
||||
internal var postLanguage: String? = null
|
||||
private var draftId: Int = 0
|
||||
private var scheduledTootId: String? = null
|
||||
private var startingContentWarning: String = ""
|
||||
|
|
@ -261,7 +262,8 @@ class ComposeViewModel @Inject constructor(
|
|||
mediaDescriptions = mediaDescriptions,
|
||||
poll = poll.value,
|
||||
failedToSend = false,
|
||||
scheduledAt = scheduledAt.value
|
||||
scheduledAt = scheduledAt.value,
|
||||
language = postLanguage,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -308,7 +310,8 @@ class ComposeViewModel @Inject constructor(
|
|||
draftId = draftId,
|
||||
idempotencyKey = randomAlphanumericString(16),
|
||||
retries = 0,
|
||||
mediaProcessed = mediaProcessed
|
||||
mediaProcessed = mediaProcessed,
|
||||
language = postLanguage,
|
||||
)
|
||||
|
||||
serviceClient.sendToot(tootToSend)
|
||||
|
|
@ -426,6 +429,7 @@ class ComposeViewModel @Inject constructor(
|
|||
draftId = composeOptions?.draftId ?: 0
|
||||
scheduledTootId = composeOptions?.scheduledTootId
|
||||
startingText = composeOptions?.content
|
||||
postLanguage = composeOptions?.language
|
||||
|
||||
val tootVisibility = composeOptions?.visibility ?: Status.Visibility.UNKNOWN
|
||||
if (tootVisibility.num != Status.Visibility.UNKNOWN.num) {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ data class ConversationStatusEntity(
|
|||
val expanded: Boolean,
|
||||
val collapsed: Boolean,
|
||||
val muted: Boolean,
|
||||
val poll: Poll?
|
||||
val poll: Poll?,
|
||||
val language: String?,
|
||||
) {
|
||||
|
||||
fun toViewData(): StatusViewData.Concrete {
|
||||
|
|
@ -125,7 +126,8 @@ data class ConversationStatusEntity(
|
|||
pinned = false,
|
||||
muted = muted,
|
||||
poll = poll,
|
||||
card = null
|
||||
card = null,
|
||||
language = language,
|
||||
),
|
||||
isExpanded = expanded,
|
||||
isShowingContent = showingHiddenContent,
|
||||
|
|
@ -167,7 +169,8 @@ fun Status.toEntity() =
|
|||
expanded = false,
|
||||
collapsed = true,
|
||||
muted = muted ?: false,
|
||||
poll = poll
|
||||
poll = poll,
|
||||
language = language,
|
||||
)
|
||||
|
||||
fun Conversation.toEntity(accountId: Long, order: Int) =
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ fun StatusViewData.Concrete.toConversationStatusEntity(
|
|||
expanded = expanded,
|
||||
collapsed = collapsed,
|
||||
muted = muted,
|
||||
poll = poll
|
||||
poll = poll,
|
||||
language = status.language,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ class DraftHelper @Inject constructor(
|
|||
mediaDescriptions: List<String?>,
|
||||
poll: NewPoll?,
|
||||
failedToSend: Boolean,
|
||||
scheduledAt: String?
|
||||
scheduledAt: String?,
|
||||
language: String?,
|
||||
) = withContext(Dispatchers.IO) {
|
||||
val externalFilesDir = context.getExternalFilesDir("Tusky")
|
||||
|
||||
|
|
@ -118,7 +119,8 @@ class DraftHelper @Inject constructor(
|
|||
attachments = attachments,
|
||||
poll = poll,
|
||||
failedToSend = failedToSend,
|
||||
scheduledAt = scheduledAt
|
||||
scheduledAt = scheduledAt,
|
||||
language = language,
|
||||
)
|
||||
|
||||
draftDao.insertOrReplace(draft)
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility,
|
||||
scheduledAt = draft.scheduledAt
|
||||
scheduledAt = draft.scheduledAt,
|
||||
language = draft.language,
|
||||
)
|
||||
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
|
@ -145,7 +146,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
poll = draft.poll,
|
||||
sensitive = draft.sensitive,
|
||||
visibility = draft.visibility,
|
||||
scheduledAt = draft.scheduledAt
|
||||
scheduledAt = draft.scheduledAt,
|
||||
language = draft.language,
|
||||
)
|
||||
|
||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||
|
|
|
|||
|
|
@ -365,6 +365,7 @@ public class NotificationHelper {
|
|||
composeOptions.setReplyingStatusContent(citedText);
|
||||
composeOptions.setMentionedUsernames(mentionedUsernames);
|
||||
composeOptions.setModifiedInitialState(true);
|
||||
composeOptions.setLanguage(actionableStatus.getLanguage());
|
||||
|
||||
Intent composeIntent = ComposeActivity.startIntent(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -216,7 +216,8 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
|||
contentWarning = actionableStatus.spoilerText,
|
||||
mentionedUsernames = mentionedUsernames,
|
||||
replyingStatusAuthor = actionableStatus.account.localUsername,
|
||||
replyingStatusContent = status.content.toString()
|
||||
replyingStatusContent = status.content.toString(),
|
||||
language = actionableStatus.language,
|
||||
)
|
||||
)
|
||||
bottomSheetActivity?.startActivityWithSlideInAnimation(intent)
|
||||
|
|
@ -461,7 +462,8 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
|
|||
contentWarning = redraftStatus.spoilerText,
|
||||
mediaAttachments = redraftStatus.attachments,
|
||||
sensitive = redraftStatus.sensitive,
|
||||
poll = redraftStatus.poll?.toNewPoll(status.createdAt)
|
||||
poll = redraftStatus.poll?.toNewPoll(status.createdAt),
|
||||
language = redraftStatus.language,
|
||||
)
|
||||
)
|
||||
startActivity(intent)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
|
|||
contentShowing = false,
|
||||
pinned = false,
|
||||
card = null,
|
||||
repliesCount = 0
|
||||
repliesCount = 0,
|
||||
language = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +142,8 @@ fun Status.toEntity(
|
|||
contentCollapsed = contentCollapsed,
|
||||
pinned = actionableStatus.pinned == true,
|
||||
card = actionableStatus.card?.let(gson::toJson),
|
||||
repliesCount = actionableStatus.repliesCount
|
||||
repliesCount = actionableStatus.repliesCount,
|
||||
language = actionableStatus.language,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +187,8 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
muted = status.muted,
|
||||
poll = poll,
|
||||
card = card,
|
||||
repliesCount = status.repliesCount
|
||||
repliesCount = status.repliesCount,
|
||||
language = status.language,
|
||||
)
|
||||
}
|
||||
val status = if (reblog != null) {
|
||||
|
|
@ -216,6 +219,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
poll = null,
|
||||
card = null,
|
||||
repliesCount = status.repliesCount,
|
||||
language = status.language,
|
||||
)
|
||||
} else {
|
||||
Status(
|
||||
|
|
@ -245,6 +249,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
|||
poll = poll,
|
||||
card = card,
|
||||
repliesCount = status.repliesCount,
|
||||
language = status.language,
|
||||
)
|
||||
}
|
||||
return StatusViewData.Concrete(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue