Skip to content

Commit

Permalink
Merge pull request #21312 from wordpress-mobile/adam/gravatar_quicked…
Browse files Browse the repository at this point in the history
…itor

Implement Gravatar QuickEditor
  • Loading branch information
AdamGrzybkowski authored Nov 15, 2024
2 parents e34429a + 9d32788 commit e9fd284
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 37 deletions.
4 changes: 3 additions & 1 deletion WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ android {
buildConfigField "boolean", "VOICE_TO_CONTENT", "false"
buildConfigField "boolean", "READER_FLOATING_BUTTON", "false"
buildConfigField "boolean", "ENABLE_SELF_HOSTED_USERS", "false"
buildConfigField "boolean", "GRAVATAR_QUICK_EDITOR", "true"

// Override these constants in jetpack product flavor to enable/ disable features
buildConfigField "boolean", "ENABLE_SITE_CREATION", "true"
Expand Down Expand Up @@ -406,6 +407,7 @@ dependencies {
}
implementation(libs.wordpress.persistent.edittext)
implementation("$gradle.ext.gravatarBinaryPath:${libs.versions.gravatar.get()}")
implementation("$gradle.ext.gravatarQuickEditorBinaryPath:${libs.versions.gravatar.get()}")

implementation(libs.google.play.app.update)

Expand Down Expand Up @@ -454,7 +456,7 @@ dependencies {
implementation(libs.apache.commons.text)
implementation(libs.airbnb.lottie.main)
implementation(libs.facebook.shimmer)
implementation(libs.yalantis.ucrop) {
implementation(libs.automattic.ucrop) {
exclude group: 'androidx.core', module: 'core'
exclude group: 'androidx.constraintlayout', module: 'constraintlayout'
exclude group: 'androidx.appcompat', module: 'appcompat'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.gravatar.AvatarQueryOptions
import com.gravatar.AvatarUrl
import com.gravatar.quickeditor.GravatarQuickEditor
import com.gravatar.quickeditor.ui.editor.AuthenticationMethod
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams
import com.gravatar.services.AvatarService
import com.gravatar.services.GravatarResult
import com.gravatar.types.Email
Expand Down Expand Up @@ -70,6 +76,7 @@ import org.wordpress.android.util.StringUtils
import org.wordpress.android.util.ToastUtils
import org.wordpress.android.util.WPAvatarUtils
import org.wordpress.android.util.WPMediaUtils
import org.wordpress.android.util.config.GravatarQuickEditorFeatureConfig
import org.wordpress.android.util.extensions.getColorFromAttribute
import org.wordpress.android.util.extensions.redirectContextClickToLongPressListener
import org.wordpress.android.util.image.ImageManager
Expand Down Expand Up @@ -134,6 +141,9 @@ class SignupEpilogueFragment : LoginBaseFormFragment<SignupEpilogueListener?>(),
@Inject
lateinit var mAvatarService: AvatarService

@Inject
lateinit var gravatarQuickEditorFeatureConfig: GravatarQuickEditorFeatureConfig

@LayoutRes
override fun getContentLayout(): Int {
return 0 // no content layout; entire view is inflated in createMainView
Expand Down Expand Up @@ -163,7 +173,33 @@ class SignupEpilogueFragment : LoginBaseFormFragment<SignupEpilogueListener?>(),
headerAvatarLayout.isEnabled = mIsEmailSignup
headerAvatarLayout.setOnClickListener {
mUnifiedLoginTracker.trackClick(UnifiedLoginTracker.Click.SELECT_AVATAR)
mMediaPickerLauncher.showGravatarPicker(this@SignupEpilogueFragment)
if (gravatarQuickEditorFeatureConfig.isEnabled()) {
GravatarQuickEditor.show(
fragment = this,
gravatarQuickEditorParams = GravatarQuickEditorParams {
email = Email(mEmailAddress)
avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal
},
authenticationMethod = AuthenticationMethod.Bearer(mAccount.accessToken.orEmpty()),
onAvatarSelected = {
mPhotoUrl = AvatarUrl(
email = Email(mEmailAddress),
avatarQueryOptions = AvatarQueryOptions {
preferredSize = resources.getDimensionPixelSize(R.dimen.avatar_sz_large)
}
).url(cacheBuster = System.currentTimeMillis().toString()).toString()
mImageManager.loadIntoCircle(
mHeaderAvatar,
ImageType.AVATAR_WITHOUT_BACKGROUND,
mPhotoUrl
)
mHeaderAvatarAdd.visibility = View.GONE
mIsAvatarAdded = true
},
)
} else {
mMediaPickerLauncher.showGravatarPicker(this@SignupEpilogueFragment)
}
}
headerAvatarLayout.setOnLongClickListener {
ToastUtils.showToast(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.LocalLifecycleOwner
import org.wordpress.android.ui.compose.theme.AppThemeM2
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.camera.core.Preview as CameraPreview

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.gravatar.quickeditor.GravatarQuickEditor
import com.gravatar.quickeditor.ui.editor.AuthenticationMethod
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams
import com.gravatar.services.AvatarService
import com.gravatar.services.GravatarResult
import com.gravatar.types.Email
Expand Down Expand Up @@ -82,6 +86,7 @@ import org.wordpress.android.util.ToastUtils
import org.wordpress.android.util.ToastUtils.Duration.SHORT
import org.wordpress.android.util.WPMediaUtils
import org.wordpress.android.util.config.DomainManagementFeatureConfig
import org.wordpress.android.util.config.GravatarQuickEditorFeatureConfig
import org.wordpress.android.util.config.QRCodeAuthFlowFeatureConfig
import org.wordpress.android.util.config.RecommendTheAppFeatureConfig
import org.wordpress.android.util.extensions.getColorFromAttribute
Expand Down Expand Up @@ -131,6 +136,9 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
@Inject
lateinit var qrCodeAuthFlowFeatureConfig: QRCodeAuthFlowFeatureConfig

@Inject
lateinit var gravatarQuickEditorFeatureConfig: GravatarQuickEditorFeatureConfig

@Inject
lateinit var jetpackBrandingUtils: JetpackBrandingUtils

Expand All @@ -156,6 +164,7 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {

private val shouldShowDomainButton
get() = BuildConfig.IS_JETPACK_APP && domainManagementFeatureConfig.isEnabled() && accountStore.hasAccessToken()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(requireActivity().application as WordPress).component().inject(this)
Expand Down Expand Up @@ -192,7 +201,21 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {

val showPickerListener = OnClickListener {
AnalyticsTracker.track(ME_GRAVATAR_TAPPED)
showPhotoPickerForGravatar()
if (gravatarQuickEditorFeatureConfig.isEnabled()) {
GravatarQuickEditor.show(
fragment = this@MeFragment,
gravatarQuickEditorParams = GravatarQuickEditorParams {
email = Email(accountStore.account.email)
avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal
},
authenticationMethod = AuthenticationMethod.Bearer(accountStore.accessToken.orEmpty()),
onAvatarSelected = {
loadAvatar(null, true)
},
)
} else {
showPhotoPickerForGravatar()
}
}
avatarContainer.setOnClickListener(showPickerListener)
rowMyProfile.setOnClickListener {
Expand Down Expand Up @@ -470,11 +493,12 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
isUpdatingGravatar = isUpdating
}

private fun MeFragmentBinding.loadAvatar(injectFilePath: String?) {
private fun MeFragmentBinding.loadAvatar(injectFilePath: String?, forceRefresh: Boolean = false) {
val newAvatarUploaded = !injectFilePath.isNullOrEmpty()
val avatarUrl = meGravatarLoader.constructGravatarUrl(accountStore.account.avatarUrl)
val newAvatarSelected = newAvatarUploaded || forceRefresh
meGravatarLoader.load(
newAvatarUploaded,
newAvatarSelected,
avatarUrl,
injectFilePath,
meAvatar,
Expand Down Expand Up @@ -508,7 +532,7 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
resource: Drawable,
model: Any?
) {
if (newAvatarUploaded && resource is BitmapDrawable) {
if (newAvatarSelected && resource is BitmapDrawable) {
var bitmap = resource.bitmap
// create a copy since the original bitmap may by automatically recycled
bitmap = bitmap.copy(bitmap.config, true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ class MeGravatarLoader @Inject constructor(
private val resourseProvider: ResourceProvider
) {
fun load(
newAvatarUploaded: Boolean,
newAvatarSelected: Boolean,
avatarUrl: String,
injectFilePath: String?,
imageView: ImageView,
imageType: ImageType,
listener: RequestListener<Drawable>? = null
) {
if (newAvatarUploaded) {
if (newAvatarSelected) {
// invalidate the specific gravatar entry from the bitmap cache. It will be updated via the injected
// request cache.
WordPress.getBitmapCache().removeSimilar(avatarUrl)
Expand All @@ -45,19 +45,22 @@ class MeGravatarLoader @Inject constructor(
imageManager.loadIntoCircle(
imageView,
imageType,
if (newAvatarUploaded && injectFilePath != null) {
if (newAvatarSelected && injectFilePath != null) {
injectFilePath
} else {
avatarUrl
// If new avatar selected we force refresh the avatar
val constructGravatarUrl = constructGravatarUrl(avatarUrl, newAvatarSelected)
constructGravatarUrl
},
listener,
appPrefsWrapper.avatarVersion
)
}
}

fun constructGravatarUrl(rawAvatarUrl: String): String {
fun constructGravatarUrl(rawAvatarUrl: String, forceRefresh: Boolean = false): String {
val avatarSz = resourseProvider.getDimensionPixelSize(R.dimen.avatar_sz_extra_small)
return WPAvatarUtils.rewriteAvatarUrl(rawAvatarUrl, avatarSz)
val cacheBuster = if (forceRefresh) System.currentTimeMillis().toString() else null
return WPAvatarUtils.rewriteAvatarUrl(rawAvatarUrl, avatarSz, cacheBuster)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
Expand All @@ -37,6 +36,7 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
Expand Down Expand Up @@ -283,28 +283,22 @@ private fun ReadingPreferencesPreviewFeedback(
end = endIndex,
)

addStringAnnotation(
tag = "url",
annotation = "feedback",
addLink(
clickable = LinkAnnotation.Clickable(
tag = "url",
linkInteractionListener = {
onSendFeedbackClick()
}
),
start = startIndex,
end = endIndex,
)
}

val buttonLabel = stringResource(R.string.reader_preferences_screen_preview_text_feedback_label)
@Suppress("DEPRECATION")
ClickableText(
Text(
text = annotatedString,
style = textStyle,
onClick = { offset ->
annotatedString.getStringAnnotations(tag = "url", start = offset, end = offset)
.firstOrNull()
?.let { annotation ->
if (annotation.item == "feedback") {
onSendFeedbackClick()
}
}
},
modifier = Modifier.semantics {
onClick(
label = buttonLabel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ private fun Loaded(uiState: UiState.Loaded) {

Column(
modifier = Modifier
.animateItem(fadeInSpec = null, fadeOutSpec = null)
.animateItem()
.fillMaxWidth()
.padding(
top = Margin.Large.value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class WPAvatarUtils {
private WPAvatarUtils() {
throw new IllegalStateException("Utility class");
}

public static final DefaultAvatarOption DEFAULT_AVATAR = MysteryPerson.INSTANCE;

/**
Expand All @@ -35,7 +36,8 @@ private WPAvatarUtils() {
* @return the fixed url
*/
public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz,
@Nullable DefaultAvatarOption defaultImage) {
@Nullable DefaultAvatarOption defaultImage,
@Nullable String cacheBuster) {
if (TextUtils.isEmpty(imageUrl)) {
return "";
}
Expand All @@ -47,17 +49,27 @@ public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatar
try {
return new AvatarUrl(new URL(imageUrl),
new AvatarQueryOptions.Builder()
.setPreferredSize(avatarSz)
.setDefaultAvatarOption(defaultImage)
.build()
).url(null).toString();
.setPreferredSize(avatarSz)
.setDefaultAvatarOption(defaultImage)
.build()
).url(cacheBuster).toString();
} catch (MalformedURLException | IllegalArgumentException e) {
return "";
}
}
}

public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz) {
return rewriteAvatarUrl(imageUrl, avatarSz, DEFAULT_AVATAR);
return rewriteAvatarUrl(imageUrl, avatarSz, DEFAULT_AVATAR, null);
}

public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz,
@Nullable DefaultAvatarOption defaultImage) {
return rewriteAvatarUrl(imageUrl, avatarSz, defaultImage, null);
}

public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz,
@Nullable String cacheBuster) {
return rewriteAvatarUrl(imageUrl, avatarSz, DEFAULT_AVATAR, cacheBuster);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.wordpress.android.util.config

import org.wordpress.android.BuildConfig
import org.wordpress.android.annotation.Feature
import javax.inject.Inject

@Feature(GravatarQuickEditorFeatureConfig.GRAVATAR_QUICK_EDITOR_REMOTE_FIELD, true)
class GravatarQuickEditorFeatureConfig @Inject constructor(appConfig: AppConfig) : FeatureConfig(
appConfig,
BuildConfig.GRAVATAR_QUICK_EDITOR,
GRAVATAR_QUICK_EDITOR_REMOTE_FIELD
) {
override fun isEnabled(): Boolean {
return super.isEnabled() && BuildConfig.GRAVATAR_QUICK_EDITOR
}

companion object {
const val GRAVATAR_QUICK_EDITOR_REMOTE_FIELD = "gravatar_quick_editor"
}
}
1 change: 1 addition & 0 deletions config/gradle/included_builds.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ gradle.ext.aztecAndroidGlideLoaderPath = "org.wordpress.aztec:glide-loader"
gradle.ext.aztecAndroidPicassoLoaderPath = "org.wordpress.aztec:picasso-loader"
gradle.ext.aboutAutomatticBinaryPath = "com.automattic:about"
gradle.ext.gravatarBinaryPath = "com.gravatar:gravatar"
gradle.ext.gravatarQuickEditorBinaryPath = "com.gravatar:gravatar-quickeditor"

def localBuilds = new File("${rootDir}/local-builds.gradle")
if (localBuilds.exists()) {
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ wordpress-lint = '2.1.0'
wordpress-persistent-edittext = '1.0.2'
wordpress-rs = 'trunk-50f703a7f677084157d02f05d4d477d7eaf960b1'
wordpress-utils = '3.14.0'
yalantis-ucrop = '2.2.9'
automattic-ucrop = '2.2.10'
zendesk = '5.1.2'

[libraries]
Expand Down Expand Up @@ -260,7 +260,7 @@ wordpress-lint = { group = "org.wordpress", name = "lint", version.ref = "wordpr
wordpress-persistent-edittext = { group = "org.wordpress", name = "persistentedittext", version.ref = "wordpress-persistent-edittext" }
wordpress-rs-android = { group = "rs.wordpress.api", name = "android", version.ref = "wordpress-rs" }
wordpress-utils = { group = "org.wordpress", name = "utils", version.ref = "wordpress-utils" }
yalantis-ucrop = { group = "com.github.yalantis", name = "ucrop", version.ref = "yalantis-ucrop" }
automattic-ucrop = { group = "com.automattic", name = "ucrop", version.ref = "automattic-ucrop" }
zendesk-support = { group = "com.zendesk", name = "support", version.ref = "zendesk" }

[plugins]
Expand Down
2 changes: 1 addition & 1 deletion libs/image-editor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.main)
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.lifecycle.livedata.core)
implementation(libs.yalantis.ucrop) {
implementation(libs.automattic.ucrop) {
exclude group: 'com.squareup.okhttp3'
exclude group: 'androidx.core', module: 'core'
exclude group: 'androidx.constraintlayout', module: 'constraintlayout'
Expand Down

0 comments on commit e9fd284

Please sign in to comment.