Skip to content

Commit e9fd284

Browse files
Merge pull request #21312 from wordpress-mobile/adam/gravatar_quickeditor
Implement Gravatar QuickEditor
2 parents e34429a + 9d32788 commit e9fd284

File tree

12 files changed

+130
-37
lines changed

12 files changed

+130
-37
lines changed

WordPress/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ android {
163163
buildConfigField "boolean", "VOICE_TO_CONTENT", "false"
164164
buildConfigField "boolean", "READER_FLOATING_BUTTON", "false"
165165
buildConfigField "boolean", "ENABLE_SELF_HOSTED_USERS", "false"
166+
buildConfigField "boolean", "GRAVATAR_QUICK_EDITOR", "true"
166167

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

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

@@ -454,7 +456,7 @@ dependencies {
454456
implementation(libs.apache.commons.text)
455457
implementation(libs.airbnb.lottie.main)
456458
implementation(libs.facebook.shimmer)
457-
implementation(libs.yalantis.ucrop) {
459+
implementation(libs.automattic.ucrop) {
458460
exclude group: 'androidx.core', module: 'core'
459461
exclude group: 'androidx.constraintlayout', module: 'constraintlayout'
460462
exclude group: 'androidx.appcompat', module: 'appcompat'

WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueFragment.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ import androidx.appcompat.view.ContextThemeWrapper
3030
import androidx.core.widget.NestedScrollView
3131
import androidx.lifecycle.lifecycleScope
3232
import com.google.android.material.dialog.MaterialAlertDialogBuilder
33+
import com.gravatar.AvatarQueryOptions
34+
import com.gravatar.AvatarUrl
35+
import com.gravatar.quickeditor.GravatarQuickEditor
36+
import com.gravatar.quickeditor.ui.editor.AuthenticationMethod
37+
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
38+
import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams
3339
import com.gravatar.services.AvatarService
3440
import com.gravatar.services.GravatarResult
3541
import com.gravatar.types.Email
@@ -70,6 +76,7 @@ import org.wordpress.android.util.StringUtils
7076
import org.wordpress.android.util.ToastUtils
7177
import org.wordpress.android.util.WPAvatarUtils
7278
import org.wordpress.android.util.WPMediaUtils
79+
import org.wordpress.android.util.config.GravatarQuickEditorFeatureConfig
7380
import org.wordpress.android.util.extensions.getColorFromAttribute
7481
import org.wordpress.android.util.extensions.redirectContextClickToLongPressListener
7582
import org.wordpress.android.util.image.ImageManager
@@ -134,6 +141,9 @@ class SignupEpilogueFragment : LoginBaseFormFragment<SignupEpilogueListener?>(),
134141
@Inject
135142
lateinit var mAvatarService: AvatarService
136143

144+
@Inject
145+
lateinit var gravatarQuickEditorFeatureConfig: GravatarQuickEditorFeatureConfig
146+
137147
@LayoutRes
138148
override fun getContentLayout(): Int {
139149
return 0 // no content layout; entire view is inflated in createMainView
@@ -163,7 +173,33 @@ class SignupEpilogueFragment : LoginBaseFormFragment<SignupEpilogueListener?>(),
163173
headerAvatarLayout.isEnabled = mIsEmailSignup
164174
headerAvatarLayout.setOnClickListener {
165175
mUnifiedLoginTracker.trackClick(UnifiedLoginTracker.Click.SELECT_AVATAR)
166-
mMediaPickerLauncher.showGravatarPicker(this@SignupEpilogueFragment)
176+
if (gravatarQuickEditorFeatureConfig.isEnabled()) {
177+
GravatarQuickEditor.show(
178+
fragment = this,
179+
gravatarQuickEditorParams = GravatarQuickEditorParams {
180+
email = Email(mEmailAddress)
181+
avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal
182+
},
183+
authenticationMethod = AuthenticationMethod.Bearer(mAccount.accessToken.orEmpty()),
184+
onAvatarSelected = {
185+
mPhotoUrl = AvatarUrl(
186+
email = Email(mEmailAddress),
187+
avatarQueryOptions = AvatarQueryOptions {
188+
preferredSize = resources.getDimensionPixelSize(R.dimen.avatar_sz_large)
189+
}
190+
).url(cacheBuster = System.currentTimeMillis().toString()).toString()
191+
mImageManager.loadIntoCircle(
192+
mHeaderAvatar,
193+
ImageType.AVATAR_WITHOUT_BACKGROUND,
194+
mPhotoUrl
195+
)
196+
mHeaderAvatarAdd.visibility = View.GONE
197+
mIsAvatarAdded = true
198+
},
199+
)
200+
} else {
201+
mMediaPickerLauncher.showGravatarPicker(this@SignupEpilogueFragment)
202+
}
167203
}
168204
headerAvatarLayout.setOnLongClickListener {
169205
ToastUtils.showToast(

WordPress/src/main/java/org/wordpress/android/ui/barcodescanner/BarcodeScanner.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.ui.viewinterop.AndroidView
2222
import androidx.core.content.ContextCompat
2323
import androidx.lifecycle.compose.LocalLifecycleOwner
2424
import org.wordpress.android.ui.compose.theme.AppThemeM2
25+
import androidx.lifecycle.compose.LocalLifecycleOwner
2526
import androidx.camera.core.Preview as CameraPreview
2627

2728
@Composable

WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import androidx.lifecycle.ViewModelProvider
2121
import androidx.lifecycle.lifecycleScope
2222
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2323
import com.google.android.material.snackbar.Snackbar
24+
import com.gravatar.quickeditor.GravatarQuickEditor
25+
import com.gravatar.quickeditor.ui.editor.AuthenticationMethod
26+
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
27+
import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams
2428
import com.gravatar.services.AvatarService
2529
import com.gravatar.services.GravatarResult
2630
import com.gravatar.types.Email
@@ -82,6 +86,7 @@ import org.wordpress.android.util.ToastUtils
8286
import org.wordpress.android.util.ToastUtils.Duration.SHORT
8387
import org.wordpress.android.util.WPMediaUtils
8488
import org.wordpress.android.util.config.DomainManagementFeatureConfig
89+
import org.wordpress.android.util.config.GravatarQuickEditorFeatureConfig
8590
import org.wordpress.android.util.config.QRCodeAuthFlowFeatureConfig
8691
import org.wordpress.android.util.config.RecommendTheAppFeatureConfig
8792
import org.wordpress.android.util.extensions.getColorFromAttribute
@@ -131,6 +136,9 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
131136
@Inject
132137
lateinit var qrCodeAuthFlowFeatureConfig: QRCodeAuthFlowFeatureConfig
133138

139+
@Inject
140+
lateinit var gravatarQuickEditorFeatureConfig: GravatarQuickEditorFeatureConfig
141+
134142
@Inject
135143
lateinit var jetpackBrandingUtils: JetpackBrandingUtils
136144

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

157165
private val shouldShowDomainButton
158166
get() = BuildConfig.IS_JETPACK_APP && domainManagementFeatureConfig.isEnabled() && accountStore.hasAccessToken()
167+
159168
override fun onCreate(savedInstanceState: Bundle?) {
160169
super.onCreate(savedInstanceState)
161170
(requireActivity().application as WordPress).component().inject(this)
@@ -192,7 +201,21 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
192201

193202
val showPickerListener = OnClickListener {
194203
AnalyticsTracker.track(ME_GRAVATAR_TAPPED)
195-
showPhotoPickerForGravatar()
204+
if (gravatarQuickEditorFeatureConfig.isEnabled()) {
205+
GravatarQuickEditor.show(
206+
fragment = this@MeFragment,
207+
gravatarQuickEditorParams = GravatarQuickEditorParams {
208+
email = Email(accountStore.account.email)
209+
avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal
210+
},
211+
authenticationMethod = AuthenticationMethod.Bearer(accountStore.accessToken.orEmpty()),
212+
onAvatarSelected = {
213+
loadAvatar(null, true)
214+
},
215+
)
216+
} else {
217+
showPhotoPickerForGravatar()
218+
}
196219
}
197220
avatarContainer.setOnClickListener(showPickerListener)
198221
rowMyProfile.setOnClickListener {
@@ -470,11 +493,12 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
470493
isUpdatingGravatar = isUpdating
471494
}
472495

473-
private fun MeFragmentBinding.loadAvatar(injectFilePath: String?) {
496+
private fun MeFragmentBinding.loadAvatar(injectFilePath: String?, forceRefresh: Boolean = false) {
474497
val newAvatarUploaded = !injectFilePath.isNullOrEmpty()
475498
val avatarUrl = meGravatarLoader.constructGravatarUrl(accountStore.account.avatarUrl)
499+
val newAvatarSelected = newAvatarUploaded || forceRefresh
476500
meGravatarLoader.load(
477-
newAvatarUploaded,
501+
newAvatarSelected,
478502
avatarUrl,
479503
injectFilePath,
480504
meAvatar,
@@ -508,7 +532,7 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener {
508532
resource: Drawable,
509533
model: Any?
510534
) {
511-
if (newAvatarUploaded && resource is BitmapDrawable) {
535+
if (newAvatarSelected && resource is BitmapDrawable) {
512536
var bitmap = resource.bitmap
513537
// create a copy since the original bitmap may by automatically recycled
514538
bitmap = bitmap.copy(bitmap.config, true)

WordPress/src/main/java/org/wordpress/android/ui/main/utils/MeGravatarLoader.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ class MeGravatarLoader @Inject constructor(
2020
private val resourseProvider: ResourceProvider
2121
) {
2222
fun load(
23-
newAvatarUploaded: Boolean,
23+
newAvatarSelected: Boolean,
2424
avatarUrl: String,
2525
injectFilePath: String?,
2626
imageView: ImageView,
2727
imageType: ImageType,
2828
listener: RequestListener<Drawable>? = null
2929
) {
30-
if (newAvatarUploaded) {
30+
if (newAvatarSelected) {
3131
// invalidate the specific gravatar entry from the bitmap cache. It will be updated via the injected
3232
// request cache.
3333
WordPress.getBitmapCache().removeSimilar(avatarUrl)
@@ -45,19 +45,22 @@ class MeGravatarLoader @Inject constructor(
4545
imageManager.loadIntoCircle(
4646
imageView,
4747
imageType,
48-
if (newAvatarUploaded && injectFilePath != null) {
48+
if (newAvatarSelected && injectFilePath != null) {
4949
injectFilePath
5050
} else {
51-
avatarUrl
51+
// If new avatar selected we force refresh the avatar
52+
val constructGravatarUrl = constructGravatarUrl(avatarUrl, newAvatarSelected)
53+
constructGravatarUrl
5254
},
5355
listener,
5456
appPrefsWrapper.avatarVersion
5557
)
5658
}
5759
}
5860

59-
fun constructGravatarUrl(rawAvatarUrl: String): String {
61+
fun constructGravatarUrl(rawAvatarUrl: String, forceRefresh: Boolean = false): String {
6062
val avatarSz = resourseProvider.getDimensionPixelSize(R.dimen.avatar_sz_extra_small)
61-
return WPAvatarUtils.rewriteAvatarUrl(rawAvatarUrl, avatarSz)
63+
val cacheBuster = if (forceRefresh) System.currentTimeMillis().toString() else null
64+
return WPAvatarUtils.rewriteAvatarUrl(rawAvatarUrl, avatarSz, cacheBuster)
6265
}
6366
}

WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/readingpreferences/ReadingPreferencesScreen.kt

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
1515
import androidx.compose.foundation.layout.width
1616
import androidx.compose.foundation.layout.wrapContentHeight
1717
import androidx.compose.foundation.rememberScrollState
18-
import androidx.compose.foundation.text.ClickableText
1918
import androidx.compose.foundation.verticalScroll
2019
import androidx.compose.material3.MaterialTheme
2120
import androidx.compose.material3.Text
@@ -37,6 +36,7 @@ import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
3736
import androidx.compose.ui.res.stringResource
3837
import androidx.compose.ui.semantics.onClick
3938
import androidx.compose.ui.semantics.semantics
39+
import androidx.compose.ui.text.LinkAnnotation
4040
import androidx.compose.ui.text.SpanStyle
4141
import androidx.compose.ui.text.TextStyle
4242
import androidx.compose.ui.text.buildAnnotatedString
@@ -283,28 +283,22 @@ private fun ReadingPreferencesPreviewFeedback(
283283
end = endIndex,
284284
)
285285

286-
addStringAnnotation(
287-
tag = "url",
288-
annotation = "feedback",
286+
addLink(
287+
clickable = LinkAnnotation.Clickable(
288+
tag = "url",
289+
linkInteractionListener = {
290+
onSendFeedbackClick()
291+
}
292+
),
289293
start = startIndex,
290294
end = endIndex,
291295
)
292296
}
293297

294298
val buttonLabel = stringResource(R.string.reader_preferences_screen_preview_text_feedback_label)
295-
@Suppress("DEPRECATION")
296-
ClickableText(
299+
Text(
297300
text = annotatedString,
298301
style = textStyle,
299-
onClick = { offset ->
300-
annotatedString.getStringAnnotations(tag = "url", start = offset, end = offset)
301-
.firstOrNull()
302-
?.let { annotation ->
303-
if (annotation.item == "feedback") {
304-
onSendFeedbackClick()
305-
}
306-
}
307-
},
308302
modifier = Modifier.semantics {
309303
onClick(
310304
label = buttonLabel,

WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ private fun Loaded(uiState: UiState.Loaded) {
136136

137137
Column(
138138
modifier = Modifier
139-
.animateItem(fadeInSpec = null, fadeOutSpec = null)
139+
.animateItem()
140140
.fillMaxWidth()
141141
.padding(
142142
top = Margin.Large.value,

WordPress/src/main/java/org/wordpress/android/util/WPAvatarUtils.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class WPAvatarUtils {
2222
private WPAvatarUtils() {
2323
throw new IllegalStateException("Utility class");
2424
}
25+
2526
public static final DefaultAvatarOption DEFAULT_AVATAR = MysteryPerson.INSTANCE;
2627

2728
/**
@@ -35,7 +36,8 @@ private WPAvatarUtils() {
3536
* @return the fixed url
3637
*/
3738
public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz,
38-
@Nullable DefaultAvatarOption defaultImage) {
39+
@Nullable DefaultAvatarOption defaultImage,
40+
@Nullable String cacheBuster) {
3941
if (TextUtils.isEmpty(imageUrl)) {
4042
return "";
4143
}
@@ -47,17 +49,27 @@ public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatar
4749
try {
4850
return new AvatarUrl(new URL(imageUrl),
4951
new AvatarQueryOptions.Builder()
50-
.setPreferredSize(avatarSz)
51-
.setDefaultAvatarOption(defaultImage)
52-
.build()
53-
).url(null).toString();
52+
.setPreferredSize(avatarSz)
53+
.setDefaultAvatarOption(defaultImage)
54+
.build()
55+
).url(cacheBuster).toString();
5456
} catch (MalformedURLException | IllegalArgumentException e) {
5557
return "";
5658
}
5759
}
5860
}
5961

6062
public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz) {
61-
return rewriteAvatarUrl(imageUrl, avatarSz, DEFAULT_AVATAR);
63+
return rewriteAvatarUrl(imageUrl, avatarSz, DEFAULT_AVATAR, null);
64+
}
65+
66+
public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz,
67+
@Nullable DefaultAvatarOption defaultImage) {
68+
return rewriteAvatarUrl(imageUrl, avatarSz, defaultImage, null);
69+
}
70+
71+
public static String rewriteAvatarUrl(@NonNull final String imageUrl, int avatarSz,
72+
@Nullable String cacheBuster) {
73+
return rewriteAvatarUrl(imageUrl, avatarSz, DEFAULT_AVATAR, cacheBuster);
6274
}
6375
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.wordpress.android.util.config
2+
3+
import org.wordpress.android.BuildConfig
4+
import org.wordpress.android.annotation.Feature
5+
import javax.inject.Inject
6+
7+
@Feature(GravatarQuickEditorFeatureConfig.GRAVATAR_QUICK_EDITOR_REMOTE_FIELD, true)
8+
class GravatarQuickEditorFeatureConfig @Inject constructor(appConfig: AppConfig) : FeatureConfig(
9+
appConfig,
10+
BuildConfig.GRAVATAR_QUICK_EDITOR,
11+
GRAVATAR_QUICK_EDITOR_REMOTE_FIELD
12+
) {
13+
override fun isEnabled(): Boolean {
14+
return super.isEnabled() && BuildConfig.GRAVATAR_QUICK_EDITOR
15+
}
16+
17+
companion object {
18+
const val GRAVATAR_QUICK_EDITOR_REMOTE_FIELD = "gravatar_quick_editor"
19+
}
20+
}

config/gradle/included_builds.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ gradle.ext.aztecAndroidGlideLoaderPath = "org.wordpress.aztec:glide-loader"
1010
gradle.ext.aztecAndroidPicassoLoaderPath = "org.wordpress.aztec:picasso-loader"
1111
gradle.ext.aboutAutomatticBinaryPath = "com.automattic:about"
1212
gradle.ext.gravatarBinaryPath = "com.gravatar:gravatar"
13+
gradle.ext.gravatarQuickEditorBinaryPath = "com.gravatar:gravatar-quickeditor"
1314

1415
def localBuilds = new File("${rootDir}/local-builds.gradle")
1516
if (localBuilds.exists()) {

0 commit comments

Comments
 (0)