Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 172 additions & 132 deletions app/src/main/java/app/pachli/components/compose/ComposeActivity.kt

Large diffs are not rendered by default.

855 changes: 566 additions & 289 deletions app/src/main/java/app/pachli/components/compose/ComposeViewModel.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,20 @@ typealias OnRemoveMediaListener = (item: QueuedMedia) -> Unit
*/
class MediaPreviewAdapter(
private val glide: RequestManager,
private val descriptionLimit: Int,
private val onDescriptionChanged: OnDescriptionChangedListener,
private val onEditFocus: OnEditFocusListener,
private val onEditImage: OnEditImageListener,
private val onRemoveMedia: OnRemoveMediaListener,
) : ListAdapter<QueuedMedia, AttachmentViewHolder>(QUEUED_MEDIA_DIFFER) {
private var descriptionLimit = 1500

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AttachmentViewHolder {
return AttachmentViewHolder(
ItemComposeMediaAttachmentBinding.inflate(LayoutInflater.from(parent.context), parent, false),
glide,
descriptionLimit,
onDescriptionChanged = onDescriptionChanged,
onMediaClick = ::showMediaPopup,
getDescriptionLimit = { return@AttachmentViewHolder descriptionLimit },
)
}

Expand All @@ -125,6 +126,13 @@ class MediaPreviewAdapter(
holder.bind(getItem(position), payloads)
}

fun setMediaDescriptionLimit(limit: Int) {
if (limit != descriptionLimit) {
descriptionLimit = limit
notifyItemRangeChanged(0, itemCount, Payload.MEDIA_DESCRIPTION_LIMIT)
}
}

/**
* Actions that can be performed on the attached media.
*
Expand Down Expand Up @@ -195,6 +203,9 @@ class MediaPreviewAdapter(

/** [QueuedMedia.uploadState] changed */
UPLOAD_STATE,

/** Media description limit changed. */
MEDIA_DESCRIPTION_LIMIT,
}

private val QUEUED_MEDIA_DIFFER = object : DiffUtil.ItemCallback<QueuedMedia>() {
Expand Down Expand Up @@ -228,23 +239,23 @@ class MediaPreviewAdapter(
* Displays media attachments using [ItemComposeMediaAttachmentBinding].
*
* @param binding View binding for the UI.
* @param descriptionLimit Max characters for a media description.
* @param onDescriptionChanged Called when the description is changed.
* @param onMediaClick Called when the user clicks the media preview image.
* @param getDescriptionLimit Called to fetch the current media description limit.
*/
class AttachmentViewHolder(
val binding: ItemComposeMediaAttachmentBinding,
private val glide: RequestManager,
descriptionLimit: Int,
private val onDescriptionChanged: OnDescriptionChangedListener,
private val onMediaClick: (QueuedMedia, View) -> Unit,
private val getDescriptionLimit: () -> Int,
) : RecyclerView.ViewHolder(binding.root) {
private val context = binding.root.context

private lateinit var item: QueuedMedia

init {
binding.descriptionLayout.counterMaxLength = descriptionLimit
binding.descriptionLayout.counterMaxLength = getDescriptionLimit()
binding.description.doAfterTextChanged { it?.let { onDescriptionChanged(item, it.toString()) } }

binding.media.setOnClickListener { onMediaClick(item, binding.media) }
Expand All @@ -270,6 +281,7 @@ class AttachmentViewHolder(
Payload.DESCRIPTION -> bindDescription(item.description)
Payload.FOCUS -> bindFocus(item.focus)
Payload.UPLOAD_STATE -> bindUploadState(item.uploadState)
Payload.MEDIA_DESCRIPTION_LIMIT -> binding.descriptionLayout.counterMaxLength = getDescriptionLimit()
else -> bindAll(item)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import android.widget.RadioGroup
import app.pachli.R
import app.pachli.core.network.model.Status

class ComposeOptionsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : RadioGroup(context, attrs) {
class ComposeVisibilityView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : RadioGroup(context, attrs) {

var listener: ComposeVisibilityListener? = null

Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/app/pachli/components/drafts/DraftHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class DraftHelper @Inject constructor(
scheduledAt: Date?,
language: String?,
statusId: String?,
) = withContext(Dispatchers.IO) {
): Int = withContext(Dispatchers.IO) {
val externalFilesDir = context.getExternalFilesDir("Pachli")

if (externalFilesDir == null || !(externalFilesDir.exists())) {
Expand Down Expand Up @@ -129,8 +129,9 @@ class DraftHelper @Inject constructor(
statusId = statusId,
)

draftDao.upsert(draft)
Timber.d("saved draft to db")
val key = draftDao.upsert(draft).toInt()
Timber.d("saved draft to db: %s", key)
return@withContext if (draftId == 0) key else draftId
}

suspend fun deleteDraftAndAttachments(draftId: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,6 @@ private fun getStatusComposeIntent(
val status = body.status!!
val account1 = status.actionableStatus.account
val contentWarning = status.actionableStatus.spoilerText
val replyVisibility = status.actionableStatus.visibility
val mentions = status.actionableStatus.mentions
val language = status.actionableStatus.language

Expand All @@ -444,11 +443,9 @@ private fun getStatusComposeIntent(
}
}
val composeOptions = ComposeOptions(
replyVisibility = replyVisibility,
contentWarning = contentWarning,
inReplyTo = InReplyTo.Status.from(status),
mentionedUsernames = mentionedUsernames,
modifiedInitialState = true,
language = language,
kind = ComposeOptions.ComposeKind.NEW,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ class ScheduledStatusActivity :
contentWarning = item.params.spoilerText,
mediaAttachments = item.mediaAttachments,
inReplyTo = item.params.inReplyToId?.let { InReplyTo.Id(it) },
visibility = item.params.visibility,
scheduledAt = item.scheduledAt,
sensitive = item.params.sensitive,
kind = ComposeOptions.ComposeKind.EDIT_SCHEDULED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData>(), StatusActionLis
requireContext(),
status.pachliAccountId,
ComposeOptions(
inReplyTo = InReplyTo.Status.from(status.actionable),
replyVisibility = actionableStatus.visibility,
inReplyTo = InReplyTo.Status.from(actionableStatus),
contentWarning = actionableStatus.spoilerText,
mentionedUsernames = mentionedUsernames,
language = actionableStatus.language,
Expand Down
44 changes: 40 additions & 4 deletions app/src/main/java/app/pachli/fragment/SFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import app.pachli.R
import app.pachli.components.drafts.DraftHelper
import app.pachli.core.activity.BaseActivity
import app.pachli.core.activity.OpenUrlUseCase
import app.pachli.core.activity.ViewUrlActivity
Expand Down Expand Up @@ -68,10 +69,12 @@ import app.pachli.interfaces.StatusActionListener
import app.pachli.translation.TranslationService
import app.pachli.usecase.TimelineCases
import app.pachli.view.showMuteAccountDialog
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.onSuccess
import com.google.android.material.snackbar.Snackbar
import io.github.z4kn4fein.semver.constraints.toConstraint
import java.util.Date
import javax.inject.Inject
import kotlinx.coroutines.launch
import timber.log.Timber
Expand Down Expand Up @@ -106,6 +109,9 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
@Inject
lateinit var openUrl: OpenUrlUseCase

@Inject
lateinit var draftHelper: DraftHelper

private var serverCanTranslate = false

protected abstract val pachliAccountId: Long
Expand Down Expand Up @@ -189,8 +195,7 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
).apply { remove(loggedInUsername) }

val composeOptions = ComposeOptions(
inReplyTo = InReplyTo.Status.from(status.actionableStatus),
replyVisibility = actionableStatus.visibility,
inReplyTo = InReplyTo.Status.from(actionableStatus),
contentWarning = actionableStatus.spoilerText,
mentionedUsernames = mentionedUsernames,
language = actionableStatus.language,
Expand Down Expand Up @@ -477,6 +482,36 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
.setMessage(R.string.dialog_redraft_post_warning)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
lifecycleScope.launch {
val status = statusViewData.actionable

// status.content has HTML tags, so fetch the original content.
val source = mastodonApi.statusSource(status.id).getOrElse {
Timber.w("error getting status source: %s", it)
val context = requireContext()
val error = context.getString(app.pachli.core.network.R.string.error_generic_fmt, it.fmt(context))
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
return@launch
}.body

val draftId = draftHelper.saveDraft(
draftId = 0,
pachliAccountId = pachliAccountId,
inReplyToId = status.inReplyToId,
content = source.text,
contentWarning = source.spoilerText,
sensitive = status.sensitive,
visibility = status.visibility,
mediaUris = status.attachments.map { it.url },
mediaDescriptions = status.attachments.map { it.description },
mediaFocus = status.attachments.map { it.meta?.focus },
poll = status.poll?.toNewPoll(Date()),
failedToSend = false,
failedToSendAlert = false,
language = status.language,
scheduledAt = null,
statusId = null,
)

timelineCases.delete(statusViewData.status.id).onSuccess {
val deletedStatus = it.body
removeItem(statusViewData)
Expand All @@ -492,10 +527,10 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
contentWarning = sourceStatus.spoilerText,
mediaAttachments = sourceStatus.attachments,
sensitive = sourceStatus.sensitive,
modifiedInitialState = true,
language = sourceStatus.language,
poll = sourceStatus.poll?.toNewPoll(sourceStatus.createdAt),
kind = ComposeOptions.ComposeKind.NEW,
kind = ComposeOptions.ComposeKind.EDIT_DRAFT,
draftId = draftId,
)
startActivityWithTransition(
ComposeActivityIntent(requireContext(), pachliAccountId, composeOptions),
Expand All @@ -504,6 +539,7 @@ abstract class SFragment<T : IStatusViewData> : Fragment(), StatusActionListener
}
.onFailure {
Timber.w("error deleting status: %s", it)
draftHelper.deleteDraftAndAttachments(draftId)
Toast.makeText(context, app.pachli.core.ui.R.string.error_generic, Toast.LENGTH_SHORT)
.show()
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/app/pachli/util/LocaleUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private fun ensureLanguagesAreFirst(locales: MutableList<Locale>, languages: Lis
}
}

// TODO: Doesn't need the full account, just the defaultPostLanguage
fun getInitialLanguages(language: String? = null, activeAccount: AccountEntity? = null): List<String> {
val selected = listOfNotNull(language, activeAccount?.defaultPostLanguage)
val system = AppCompatDelegate.getApplicationLocales().toList() +
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/layout/activity_compose.xml
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/compose_media_preview_margin"
app:layout_constraintTop_toBottomOf="@id/composeMediaPreviewBar"
app:layout_constraintStart_toStartOf="parent"
android:minWidth="@dimen/poll_preview_min_width"
android:visibility="gone"
tools:visibility="visible" />
Expand Down Expand Up @@ -377,7 +378,7 @@
app:behavior_peekHeight="0dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />

<app.pachli.components.compose.view.ComposeOptionsView
<app.pachli.components.compose.view.ComposeVisibilityView
android:id="@+id/composeOptionsBottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
Loading
Loading