Machine translation of posts (#4307)

This commit is contained in:
Willow 2024-03-09 16:12:18 +01:00 committed by GitHub
commit fbb22799dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 1912 additions and 180 deletions

View file

@ -109,6 +109,7 @@ import at.connyduck.sparkbutton.helpers.Utils;
import kotlin.Unit;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import kotlinx.coroutines.Job;
public class NotificationsFragment extends SFragment implements
@ -408,6 +409,12 @@ public class NotificationsFragment extends SFragment implements
sendFetchNotificationsRequest(null, topId, FetchEnd.TOP, -1);
}
@Nullable
@Override
protected Function2<Boolean, Integer, Unit> getOnMoreTranslate() {
return null;
}
@Override
public void onReply(int position) {
super.reply(notifications.get(position).asRight().getStatus());
@ -490,7 +497,7 @@ public class NotificationsFragment extends SFragment implements
@Override
public void onMore(@NonNull View view, int position) {
Notification notification = notifications.get(position).asRight();
super.more(notification.getStatus(), view, position);
super.more(notification.getStatus(), view, position, null);
}
@Override
@ -525,10 +532,6 @@ public class NotificationsFragment extends SFragment implements
updateViewDataAt(position, (vd) -> vd.copyWithShowingContent(isShowing));
}
private void setPinForStatus(String statusId, boolean pinned) {
updateStatus(statusId, status -> status.copyWithPinned(pinned));
}
@Override
public void onLoadMore(int position) {
// Check bounds before accessing list,
@ -555,6 +558,11 @@ public class NotificationsFragment extends SFragment implements
updateViewDataAt(position, (vd) -> vd.copyWithCollapsed(isCollapsed));
}
@Override
public void onUntranslate(int position) {
// not needed
}
private void updateStatus(String statusId, Function<Status, Status> mapper) {
int index = CollectionsKt.indexOfFirst(this.notifications, (s) -> s.isRight() &&
s.asRight().getStatus() != null &&

View file

@ -46,12 +46,14 @@ import com.keylesspalace.tusky.ViewMediaActivity.Companion.newIntent
import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.startIntent
import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.components.report.ReportActivity.Companion.getIntent
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.Translation
import com.keylesspalace.tusky.interfaces.AccountSelectionListener
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.usecase.TimelineCases
@ -60,6 +62,7 @@ import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation
import com.keylesspalace.tusky.view.showMuteAccountDialog
import com.keylesspalace.tusky.viewdata.AttachmentViewData
import java.util.Locale
import javax.inject.Inject
import kotlinx.coroutines.launch
@ -72,6 +75,10 @@ import kotlinx.coroutines.launch
abstract class SFragment : Fragment(), Injectable {
protected abstract fun removeItem(position: Int)
protected abstract fun onReblog(reblog: Boolean, position: Int)
/** `null` if translation is not supported on this screen */
protected abstract val onMoreTranslate: ((translate: Boolean, position: Int) -> Unit)?
private lateinit var bottomSheetActivity: BottomSheetActivity
@Inject
@ -83,6 +90,9 @@ abstract class SFragment : Fragment(), Injectable {
@Inject
lateinit var timelineCases: TimelineCases
@Inject
lateinit var instanceInfoRepository: InstanceInfoRepository
override fun startActivity(intent: Intent) {
requireActivity().startActivityWithSlideInAnimation(intent)
}
@ -96,6 +106,13 @@ abstract class SFragment : Fragment(), Injectable {
}
}
override fun onResume() {
super.onResume()
// make sure we have instance info for when we'll need it
instanceInfoRepository.precache()
}
protected fun openReblog(status: Status?) {
if (status == null) return
bottomSheetActivity.viewAccount(status.account.id)
@ -140,7 +157,7 @@ abstract class SFragment : Fragment(), Injectable {
requireActivity().startActivity(intent)
}
protected fun more(status: Status, view: View, position: Int) {
protected fun more(status: Status, view: View, position: Int, translation: Translation?) {
val id = status.actionableId
val accountId = status.actionableStatus.account.id
val accountUsername = status.actionableStatus.account.username
@ -167,16 +184,19 @@ abstract class SFragment : Fragment(), Injectable {
)
)
}
Status.Visibility.PRIVATE -> {
val reblogged = status.reblog?.reblogged ?: status.reblogged
menu.findItem(R.id.status_reblog_private).isVisible = !reblogged
menu.findItem(R.id.status_unreblog_private).isVisible = reblogged
}
else -> {}
}
} else {
popup.inflate(R.menu.status_more)
popup.menu.findItem(R.id.status_download_media).isVisible = status.attachments.isNotEmpty()
popup.menu.findItem(R.id.status_download_media).isVisible =
status.attachments.isNotEmpty()
}
val menu = popup.menu
val openAsItem = menu.findItem(R.id.status_open_as)
@ -187,7 +207,8 @@ abstract class SFragment : Fragment(), Injectable {
openAsItem.title = openAsText
}
val muteConversationItem = menu.findItem(R.id.status_mute_conversation)
val mutable = statusIsByCurrentUser || accountIsInMentions(activeAccount, status.mentions)
val mutable =
statusIsByCurrentUser || accountIsInMentions(activeAccount, status.mentions)
muteConversationItem.isVisible = mutable
if (mutable) {
muteConversationItem.setTitle(
@ -198,6 +219,15 @@ abstract class SFragment : Fragment(), Injectable {
}
)
}
// translation not there for your own posts
menu.findItem(R.id.status_translate)?.let { translateItem ->
translateItem.isVisible = onMoreTranslate != null &&
!status.language.equals(Locale.getDefault().language, ignoreCase = true) &&
instanceInfoRepository.cachedInstanceInfoOrFallback.translationEnabled == true
translateItem.setTitle(if (translation != null) R.string.action_show_original else R.string.action_translate)
}
popup.setOnMenuItemClickListener { item: MenuItem ->
when (item.itemId) {
R.id.post_share_content -> {
@ -219,6 +249,7 @@ abstract class SFragment : Fragment(), Injectable {
)
return@setOnMenuItemClickListener true
}
R.id.post_share_link -> {
val sendIntent = Intent().apply {
action = Intent.ACTION_SEND
@ -233,6 +264,7 @@ abstract class SFragment : Fragment(), Injectable {
)
return@setOnMenuItemClickListener true
}
R.id.status_copy_link -> {
(
requireActivity().getSystemService(
@ -243,62 +275,80 @@ abstract class SFragment : Fragment(), Injectable {
}
return@setOnMenuItemClickListener true
}
R.id.status_open_as -> {
showOpenAsDialog(statusUrl, item.title)
return@setOnMenuItemClickListener true
}
R.id.status_download_media -> {
requestDownloadAllMedia(status)
return@setOnMenuItemClickListener true
}
R.id.status_mute -> {
onMute(accountId, accountUsername)
return@setOnMenuItemClickListener true
}
R.id.status_block -> {
onBlock(accountId, accountUsername)
return@setOnMenuItemClickListener true
}
R.id.status_report -> {
openReportPage(accountId, accountUsername, id)
return@setOnMenuItemClickListener true
}
R.id.status_unreblog_private -> {
onReblog(false, position)
return@setOnMenuItemClickListener true
}
R.id.status_reblog_private -> {
onReblog(true, position)
return@setOnMenuItemClickListener true
}
R.id.status_delete -> {
showConfirmDeleteDialog(id, position)
return@setOnMenuItemClickListener true
}
R.id.status_delete_and_redraft -> {
showConfirmEditDialog(id, position, status)
return@setOnMenuItemClickListener true
}
R.id.status_edit -> {
editStatus(id, status)
return@setOnMenuItemClickListener true
}
R.id.pin -> {
lifecycleScope.launch {
timelineCases.pin(status.id, !status.isPinned()).onFailure { e: Throwable ->
val message = e.message
?: getString(if (status.isPinned()) R.string.failed_to_unpin else R.string.failed_to_pin)
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).show()
}
timelineCases.pin(status.id, !status.isPinned())
.onFailure { e: Throwable ->
val message = e.message
?: getString(if (status.isPinned()) R.string.failed_to_unpin else R.string.failed_to_pin)
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG)
.show()
}
}
return@setOnMenuItemClickListener true
}
R.id.status_mute_conversation -> {
lifecycleScope.launch {
timelineCases.muteConversation(status.id, status.muted != true)
}
return@setOnMenuItemClickListener true
}
R.id.status_translate -> {
onMoreTranslate?.invoke(translation == null, position)
}
}
false
}
@ -346,6 +396,7 @@ abstract class SFragment : Fragment(), Injectable {
startActivity(intent)
}
}
Attachment.Type.UNKNOWN -> {
requireContext().openLink(attachment.url)
}