Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UI for image-attachment "focus" #2620

Merged
merged 28 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f3bde24
Attempt-zero implementation of a "focus" feature for image attachment…
mcclure Jul 16, 2022
613358e
Remove code duplication between 'update description' and 'update focus'
mcclure Jul 17, 2022
7d86015
Fix ktlint/bitrise failures
mcclure Jul 18, 2022
c283a4c
Make updateMediaItem private
mcclure Jul 22, 2022
7b3fd74
When focus is set on a post attachment the preview focuses correctly.…
mcclure Jul 23, 2022
02b5e68
Replace use of PointF for Focus where focus is represented, fix ktlint
mcclure Jul 23, 2022
84926d9
Substitute 'focus' for 'focus point' in strings
mcclure Jul 23, 2022
a439ec5
First attempt draw focus point. Only updates on initial load. Modeled…
mcclure Sep 3, 2022
5e920da
Redraw focus after each tap
mcclure Sep 3, 2022
09ab141
Dark curtain where focus isn't (now looks like mastosoc)
mcclure Sep 3, 2022
8f08b1b
Correct ktlint for FocusDialog
mcclure Sep 3, 2022
d345e62
draft: switch to overlay for focus indicator
connyduck Sep 5, 2022
c89cdcf
Draw focus circle, but ImageView and FocusIndicatorView seem to share…
mcclure Sep 5, 2022
3437fa8
Switch focus circle to path approach
mcclure Sep 5, 2022
480b07b
Correctly scale, save and load focuses. Clamp to visible area. Focus …
mcclure Sep 5, 2022
0e6c942
ktlint fixes and comments
mcclure Sep 5, 2022
f4e4f8b
Focus indicator drawing should use device-independent pixels
mcclure Sep 5, 2022
d929b98
Shrink focus window when it gets unattractively tall (no linting, mis…
mcclure Sep 18, 2022
dab6381
Correct max-height behavior for screens in landscape mode
mcclure Sep 18, 2022
af5c995
Focus attachment result is are flipped on x axis; fix this
mcclure Sep 19, 2022
829c151
Correctly thread focus through on scheduled posts, redrafted posts, a…
mcclure Sep 19, 2022
cde5d5e
More focus ktlint fixes
mcclure Sep 19, 2022
a93369b
Fix specific case where a draft is given a focus, then deleted, then …
mcclure Sep 19, 2022
400683d
Fix accidental file change in focus PR
mcclure Sep 19, 2022
a81ad1c
Remerge develop with focus PR
mcclure Sep 19, 2022
829a0ed
ktLint fix
mcclure Sep 19, 2022
fc771f3
Fix property style warnings in focus
mcclure Sep 20, 2022
33d5745
Fix remaining style warnings from focus PR
mcclure Sep 20, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.PointF
import android.net.Uri
import android.os.Build
import android.os.Bundle
Expand Down Expand Up @@ -68,6 +69,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.makeFocusDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
Expand Down Expand Up @@ -216,6 +218,11 @@ class ComposeActivity :
viewModel.updateDescription(item.localId, newDescription)
}
},
onAddFocus = { item ->
makeFocusDialog(item.focus, item.uri) { newFocus ->
viewModel.updateFocus(item.localId, newFocus)
}
},
onEditImage = this::editImageInQueue,
onRemove = this::removeMediaFromQueue
)
Expand Down Expand Up @@ -1065,7 +1072,8 @@ class ComposeActivity :
val mediaSize: Long,
val uploadPercent: Int = 0,
val id: String? = null,
val description: String? = null
val description: String? = null,
val focus: PointF? = null // Range -1..1 y-up
) {
enum class Type {
IMAGE, VIDEO, AUDIO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package com.keylesspalace.tusky.components.compose

import android.graphics.PointF
import android.net.Uri
import android.util.Log
import androidx.core.net.toUri
Expand Down Expand Up @@ -324,11 +325,12 @@ class ComposeViewModel @Inject constructor(
return combineLiveData(deletionObservable, sendFlow.asLiveData()) { _, _ -> }
}

suspend fun updateDescription(localId: Int, description: String): Boolean {
// Updates a QueuedMedia item arbitrarily, then sends description and focus to server
suspend fun updateMediaItem(localId: Int, mutator: (QueuedMedia) -> QueuedMedia): Boolean {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be private?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

val newMediaList = media.updateAndGet { mediaValue ->
mediaValue.map { mediaItem ->
if (mediaItem.localId == localId) {
mediaItem.copy(description = description)
mutator(mediaItem)
} else {
mediaItem
}
Expand All @@ -337,7 +339,9 @@ class ComposeViewModel @Inject constructor(

val updatedItem = newMediaList.find { it.localId == localId }
if (updatedItem?.id != null) {
return api.updateMedia(updatedItem.id, description)
val focus = updatedItem.focus
val focusString = if (focus != null) "${focus.x},${focus.y}" else null
return api.updateMedia(updatedItem.id, updatedItem.description, focusString)
.fold({
true
}, { throwable ->
Expand All @@ -348,6 +352,18 @@ class ComposeViewModel @Inject constructor(
return true
}

suspend fun updateDescription(localId: Int, description: String): Boolean {
return updateMediaItem(localId, { mediaItem ->
mediaItem.copy(description = description)
})
}

suspend fun updateFocus(localId: Int, focus: PointF): Boolean {
return updateMediaItem(localId, { mediaItem ->
mediaItem.copy(focus = focus)
})
}

fun searchAutocompleteSuggestions(token: String): List<AutocompleteResult> {
when (token[0]) {
'@' -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.keylesspalace.tusky.components.compose.view.ProgressImageView
class MediaPreviewAdapter(
context: Context,
private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit,
private val onAddFocus: (ComposeActivity.QueuedMedia) -> Unit,
private val onEditImage: (ComposeActivity.QueuedMedia) -> Unit,
private val onRemove: (ComposeActivity.QueuedMedia) -> Unit
) : RecyclerView.Adapter<MediaPreviewAdapter.PreviewViewHolder>() {
Expand All @@ -44,15 +45,19 @@ class MediaPreviewAdapter(
val item = differ.currentList[position]
val popup = PopupMenu(view.context, view)
val addCaptionId = 1
val editImageId = 2
val removeId = 3
val addFocusId = 2
val editImageId = 3
val removeId = 4
popup.menu.add(0, addCaptionId, 0, R.string.action_set_caption)
if (item.type == ComposeActivity.QueuedMedia.Type.IMAGE)
if (item.type == ComposeActivity.QueuedMedia.Type.IMAGE) {
popup.menu.add(0, addFocusId, 0, R.string.action_set_focus)
popup.menu.add(0, editImageId, 0, R.string.action_edit_image)
}
popup.menu.add(0, removeId, 0, R.string.action_remove)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
addCaptionId -> onAddCaption(item)
addFocusId -> onAddFocus(item)
editImageId -> onEditImage(item)
removeId -> onRemove(item)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* Copyright 2019 Tusky Contributors
*
* This file is a part of Tusky.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */

package com.keylesspalace.tusky.components.compose.dialog

import android.app.Activity
import android.content.DialogInterface
import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.InputFilter
import android.text.InputType
import android.util.Log // ANDI SHOULD NOT CHECK THIS LINE IN TO GIT
import android.view.WindowManager
import android.widget.EditText
import android.widget.ImageView;
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import at.connyduck.sparkbutton.helpers.Utils
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.github.chrisbanes.photoview.OnPhotoTapListener
import com.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R
import kotlinx.coroutines.launch

fun <T> T.makeFocusDialog(
existingFocus: PointF?,
previewUri: Uri,
onUpdateFocus: suspend (PointF) -> Boolean
) where T : Activity, T : LifecycleOwner {
var focus = existingFocus ?: PointF(0.0f, 0.0f) // Default to center

val dialogLayout = LinearLayout(this)
val padding = Utils.dpToPx(this, 8)
dialogLayout.setPadding(padding, padding, padding, padding)

dialogLayout.orientation = LinearLayout.VERTICAL
val imageView = PhotoView(this).apply {
maximumScale = 6f
setOnPhotoTapListener(object : OnPhotoTapListener {
override fun onPhotoTap(view: ImageView, x:Float, y:Float) {
focus = PointF(x*2-1, 1-y*2) // PhotoView range is 0..1 Y-down but Mastodon API range is -1..1 Y-up
}
})
}

val margin = Utils.dpToPx(this, 4)
dialogLayout.addView(imageView)
(imageView.layoutParams as LinearLayout.LayoutParams).weight = 1f
imageView.layoutParams.height = 0
(imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0)

val okListener = { dialog: DialogInterface, _: Int ->
lifecycleScope.launch {
if (!onUpdateFocus(focus)) {
showFailedFocusMessage()
}
}
dialog.dismiss()
}

val dialog = AlertDialog.Builder(this)
.setView(dialogLayout)
.setPositiveButton(android.R.string.ok, okListener)
.setNegativeButton(android.R.string.cancel, null)
.create()

val window = dialog.window
window?.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)

dialog.show()

// Load the image and manually set it into the ImageView because it doesn't have a fixed size.
Glide.with(this)
.load(previewUri)
.downsample(DownsampleStrategy.CENTER_INSIDE)
.into(object : CustomTarget<Drawable>(4096, 4096) {
override fun onLoadCleared(placeholder: Drawable?) {
imageView.setImageDrawable(placeholder)
}

override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
imageView.setImageDrawable(resource)
}
})
}

private fun Activity.showFailedFocusMessage() {
Toast.makeText(this, R.string.error_failed_set_focus, Toast.LENGTH_SHORT).show()
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ interface MastodonApi {
@PUT("api/v1/media/{mediaId}")
suspend fun updateMedia(
@Path("mediaId") mediaId: String,
@Field("description") description: String
@Field("description") description: String?,
@Field("focus") focus: String?
): NetworkResult<Attachment>

@GET("api/v1/media/{mediaId}")
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,12 @@
<string name="compose_active_account_description">Posting with account %1$s</string>

<string name="error_failed_set_caption">Failed to set caption</string>
<string name="error_failed_set_focus">Failed to set focus</string>
<plurals name="hint_describe_for_visually_impaired">
<item quantity="other">Describe for visually impaired\n(%d character limit)</item>
</plurals>
<string name="action_set_caption">Set caption</string>
<string name="action_set_focus">Set focus</string>
<string name="action_edit_image">Edit image</string>
<string name="action_remove">Remove</string>
<string name="lock_account_label">Lock account</string>
Expand Down