diff --git a/.configure b/.configure index 7b35856c6cd..8efdda37b39 100644 --- a/.configure +++ b/.configure @@ -1,7 +1,7 @@ { "project_name": "pocketcasts-android", "branch": "trunk", - "pinned_hash": "21f42df268ccea3b3d0f258e6e8e8d0b09a32545", + "pinned_hash": "b2e97535bbca2c7f16164b0fe4aed3f4ebe04584", "files_to_copy": [ { "file": "android/pocket-casts/secret.properties", @@ -44,7 +44,5 @@ "encrypt": true } ], - "file_dependencies": [ - - ] + "file_dependencies": [] } \ No newline at end of file diff --git a/.configure-files/secret.properties.enc b/.configure-files/secret.properties.enc index b05dad59b98..e0455208ab9 100644 Binary files a/.configure-files/secret.properties.enc and b/.configure-files/secret.properties.enc differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c86a4b705..229a214a065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ * Bug Fixes * Fix global auto download setting was incorrectly overriding the podcast auto download setting ([#3342](https://github.com/Automattic/pocket-casts-android/pull/3342)) +* New Features + * Modify avatar will use Gravatar native Quick Editor + ([#3055](https://github.com/Automattic/pocket-casts-android/pull/3055)) 7.79 ----- diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2c550a4cb5e..f044094ac8e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -119,6 +119,7 @@ dependencies { implementation(projects.modules.services.ui) implementation(projects.modules.services.utils) implementation(projects.modules.services.views) + implementation(projects.modules.services.gravatar) debugImplementation(libs.compose.ui.tooling) diff --git a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/ui/MainActivityTest.kt b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/ui/MainActivityTest.kt index 583f5fb5e55..5fd737226bd 100644 --- a/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/ui/MainActivityTest.kt +++ b/app/src/androidTest/java/au/com/shiftyjelly/pocketcasts/ui/MainActivityTest.kt @@ -1,6 +1,5 @@ package au.com.shiftyjelly.pocketcasts.ui -import android.Manifest import android.os.SystemClock import android.view.InputDevice import android.view.MotionEvent @@ -29,7 +28,6 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice import au.com.shiftyjelly.pocketcasts.ui.theme.Theme import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn @@ -57,9 +55,8 @@ class MainActivityTest { @get:Rule var activityRule: ActivityScenarioRule = ActivityScenarioRule(MainActivity::class.java) - @get:Rule val composeTestRule = createAndroidComposeRule() - - @get:Rule var permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + @get:Rule + val composeTestRule = createAndroidComposeRule() lateinit var device: UiDevice diff --git a/app/src/main/java/au/com/shiftyjelly/pocketcasts/di/AppModule.kt b/app/src/main/java/au/com/shiftyjelly/pocketcasts/di/AppModule.kt index 0bd1624dc21..5e51edbc2ea 100644 --- a/app/src/main/java/au/com/shiftyjelly/pocketcasts/di/AppModule.kt +++ b/app/src/main/java/au/com/shiftyjelly/pocketcasts/di/AppModule.kt @@ -3,7 +3,9 @@ package au.com.shiftyjelly.pocketcasts.di import android.content.Context import android.net.ConnectivityManager import androidx.core.content.getSystemService +import au.com.shiftyjelly.pocketcasts.GravatarSdkService import au.com.shiftyjelly.pocketcasts.servers.di.Downloads +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService import dagger.Binds import dagger.Module import dagger.Provides @@ -29,6 +31,9 @@ abstract class AppModule { fun downloadRequestBuilder(): Request.Builder = Request.Builder() } + @Binds + abstract fun gravatarService(factory: GravatarSdkService.Factory): GravatarService.Factory + @Binds @Downloads abstract fun downloadsCallFactory(@Downloads client: OkHttpClient): Call.Factory diff --git a/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt b/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt index cba50984a92..0d0a09e2bc6 100644 --- a/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt +++ b/app/src/main/java/au/com/shiftyjelly/pocketcasts/ui/MainActivity.kt @@ -986,6 +986,7 @@ class MainActivity : return binding.snackbarFragment } + @SuppressLint("PrivateResource") override fun onMiniPlayerHidden() { updateSnackbarPosition(miniPlayerOpen = false) settings.updateBottomInset(0) @@ -997,6 +998,7 @@ class MainActivity : } } + @SuppressLint("PrivateResource") override fun onMiniPlayerVisible() { updateSnackbarPosition(miniPlayerOpen = true) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index de02a102eca..e09eb521c83 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,6 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root"> + android:clipToPadding="false" + tools:ignore="PrivateResource" /> - + () private val upgradeBannerViewModel by viewModels() + private lateinit var gravatarService: GravatarService + + override fun onCreate(savedInstanceState: Bundle?) { + gravatarService = gravatarServiceFactory.create(this) { accountViewModel.gravatarUpdated() } + + super.onCreate(savedInstanceState) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -130,9 +142,7 @@ class AccountDetailsFragment : BaseFragment() { upgradeBannerViewModel.onFeatureCardChanged(featureCard) }, onChangeAvatar = { email -> - analyticsTracker.track(AnalyticsEvent.ACCOUNT_DETAILS_CHANGE_AVATAR) - Gravatar.refreshGravatarTimestamp() - requireActivity().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(Gravatar.getGravatarChangeAvatarUrl(email)))) + openGravatarQuickEditor(email) }, onChangeEmail = { (requireActivity() as FragmentHostListener).addFragment(ChangeEmailFragment.newInstance()) @@ -277,6 +287,15 @@ class AccountDetailsFragment : BaseFragment() { activity?.finish() } + private fun openGravatarQuickEditor(email: String) { + analyticsTracker.track(AnalyticsEvent.ACCOUNT_DETAILS_CHANGE_AVATAR) + if (FeatureFlag.isEnabled(Feature.GRAVATAR_NATIVE_QUICK_EDITOR)) { + gravatarService.launchQuickEditor(theme.isLightTheme, email) + } else { + gravatarService.launchExternalQuickEditor(email) + } + } + private fun performSignOut() { LogBuffer.i(LogBuffer.TAG_BACKGROUND_TASKS, "User requested to sign out") userManager.signOut(playbackManager, wasInitiatedByUser = true) diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsViewModel.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsViewModel.kt index 574a7e5f67e..3da1d2c8d4a 100644 --- a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsViewModel.kt +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/AccountDetailsViewModel.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.reactive.asFlow @@ -65,7 +64,7 @@ class AccountDetailsViewModel } } - internal val headerState = signInState.asFlow().map { state -> + internal val headerState = combine(signInState.asFlow(), Gravatar.lastTimeStamp) { state, _ -> when (state) { is SignInState.SignedOut -> AccountHeaderState.empty() is SignInState.SignedIn -> { @@ -187,6 +186,10 @@ class AccountDetailsViewModel settings.marketingOptIn.set(isChecked, updateModifiedAt = true) } + fun gravatarUpdated() { + Gravatar.refreshGravatarTimestamp() + } + companion object { private const val SOURCE_KEY = "source" private const val ENABLED_KEY = "enabled" diff --git a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/ProfileViewModel.kt b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/ProfileViewModel.kt index d48cc6abcc1..c7c7b8714a7 100644 --- a/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/ProfileViewModel.kt +++ b/modules/features/profile/src/main/java/au/com/shiftyjelly/pocketcasts/profile/ProfileViewModel.kt @@ -48,7 +48,7 @@ class ProfileViewModel @Inject constructor( internal val isSignedIn get() = signInState.value.isSignedIn - internal val profileHeaderState = signInState.map { state -> + internal val profileHeaderState = combine(signInState, Gravatar.lastTimeStamp) { state, _ -> when (state) { is SignInState.SignedIn -> ProfileHeaderState( imageUrl = Gravatar.getUrl(state.email), diff --git a/modules/services/gravatar-noop/build.gradle.kts b/modules/services/gravatar-noop/build.gradle.kts new file mode 100644 index 00000000000..180c1b69930 --- /dev/null +++ b/modules/services/gravatar-noop/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "au.com.shiftyjelly.pocketcasts.gravatar" + buildFeatures { + buildConfig = true + viewBinding = false + compose = false + } +} + +dependencies { + ksp(libs.dagger.hilt.compiler) + ksp(libs.hilt.compiler) + + api(libs.dagger.hilt.android) + + implementation(projects.modules.services.utils) +} diff --git a/modules/services/gravatar-noop/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarModule.kt b/modules/services/gravatar-noop/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarModule.kt new file mode 100644 index 00000000000..b0864eb686a --- /dev/null +++ b/modules/services/gravatar-noop/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarModule.kt @@ -0,0 +1,18 @@ +package au.com.shiftyjelly.pocketcasts + +import androidx.fragment.app.Fragment +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.FragmentComponent + +@Module +@InstallIn(FragmentComponent::class) +object GravatarModule { + @Provides + fun provideGravatarService( + fragment: Fragment, + onAvatarSelected: () -> Unit, + ): GravatarService = NoOpGravatarSdkService(fragment, onAvatarSelected) +} diff --git a/modules/services/gravatar-noop/src/main/kotlin/au/com/shiftyjelly/pocketcasts/NoOpGravatarSdkService.kt b/modules/services/gravatar-noop/src/main/kotlin/au/com/shiftyjelly/pocketcasts/NoOpGravatarSdkService.kt new file mode 100644 index 00000000000..e82eb1b8202 --- /dev/null +++ b/modules/services/gravatar-noop/src/main/kotlin/au/com/shiftyjelly/pocketcasts/NoOpGravatarSdkService.kt @@ -0,0 +1,28 @@ +package au.com.shiftyjelly.pocketcasts + +import androidx.fragment.app.Fragment +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class NoOpGravatarSdkService @AssistedInject constructor(@Assisted fragment: Fragment?, @Assisted onAvatarSelected: (() -> Unit)?) : + GravatarService { + + override fun launchExternalQuickEditor(email: String) { + error("Operation not supported") + } + + override fun launchQuickEditor(isLightTheme: Boolean, email: String) { + error("Operation not supported") + } + + override suspend fun logout(email: String) { + error("Operation not supported") + } + + @AssistedFactory + interface Factory : GravatarService.Factory { + override fun create(fragment: Fragment?, onAvatarSelected: (() -> Unit)?): NoOpGravatarSdkService + } +} diff --git a/modules/services/gravatar/build.gradle.kts b/modules/services/gravatar/build.gradle.kts new file mode 100644 index 00000000000..1093d493228 --- /dev/null +++ b/modules/services/gravatar/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.ksp) + alias(libs.plugins.hilt) +} + +android { + namespace = "au.com.shiftyjelly.pocketcasts.gravatar" + buildFeatures { + buildConfig = true + viewBinding = false + compose = false + } +} + +dependencies { + ksp(libs.dagger.hilt.compiler) + ksp(libs.hilt.compiler) + + api(libs.dagger.hilt.android) + + implementation(projects.modules.services.utils) + + implementation(libs.gravatar) + implementation(libs.gravatar.quickeditor) +} diff --git a/modules/services/gravatar/src/main/AndroidManifest.xml b/modules/services/gravatar/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..dbf36fa33bf --- /dev/null +++ b/modules/services/gravatar/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/services/gravatar/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarModule.kt b/modules/services/gravatar/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarModule.kt new file mode 100644 index 00000000000..749bca70bb3 --- /dev/null +++ b/modules/services/gravatar/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarModule.kt @@ -0,0 +1,18 @@ +package au.com.shiftyjelly.pocketcasts + +import androidx.fragment.app.Fragment +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.FragmentComponent + +@Module +@InstallIn(FragmentComponent::class) +object GravatarModule { + @Provides + fun provideGravatarService( + fragment: Fragment, + onAvatarSelected: () -> Unit, + ): GravatarService = GravatarSdkService(fragment, onAvatarSelected) +} diff --git a/modules/services/gravatar/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarSdkService.kt b/modules/services/gravatar/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarSdkService.kt new file mode 100644 index 00000000000..81d417f069a --- /dev/null +++ b/modules/services/gravatar/src/main/kotlin/au/com/shiftyjelly/pocketcasts/GravatarSdkService.kt @@ -0,0 +1,80 @@ +package au.com.shiftyjelly.pocketcasts + +import android.content.Intent +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import au.com.shiftyjelly.pocketcasts.gravatar.BuildConfig.GRAVATAR_APP_ID +import au.com.shiftyjelly.pocketcasts.utils.Gravatar +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService +import com.gravatar.quickeditor.GravatarQuickEditor +import com.gravatar.quickeditor.ui.GetQuickEditorResult +import com.gravatar.quickeditor.ui.GravatarQuickEditorActivity +import com.gravatar.quickeditor.ui.GravatarQuickEditorResult +import com.gravatar.quickeditor.ui.editor.AuthenticationMethod +import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout +import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams +import com.gravatar.quickeditor.ui.editor.GravatarUiMode +import com.gravatar.quickeditor.ui.oauth.OAuthParams +import com.gravatar.types.Email +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class GravatarSdkService @AssistedInject constructor(@Assisted fragment: Fragment?, @Assisted onAvatarSelected: (() -> Unit)?) : + GravatarService { + + private val gravatarExternalQuickEditorLauncher = + fragment?.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ -> + onAvatarSelected?.invoke() + } + + private val gravatarNativeQuickEditorLauncher = fragment?.registerForActivityResult(GetQuickEditorResult()) { quickEditorResult -> + when (quickEditorResult) { + GravatarQuickEditorResult.AVATAR_SELECTED -> onAvatarSelected?.invoke() + GravatarQuickEditorResult.DISMISSED, + null, + -> { + /* Do nothing */ + } + } + } + + override fun launchExternalQuickEditor(email: String) { + gravatarExternalQuickEditorLauncher?.launch(Intent(Intent.ACTION_VIEW, Uri.parse(Gravatar.getGravatarChangeAvatarUrl(email)))) + ?: error("gravatarExternalQuickEditorLauncher is not initialized") + } + + override fun launchQuickEditor(isLightTheme: Boolean, email: String) { + gravatarNativeQuickEditorLauncher?.launch( + GravatarQuickEditorActivity.GravatarEditorActivityArguments( + gravatarQuickEditorParams = GravatarQuickEditorParams { + this.email = Email(email) + avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal + uiMode = if (isLightTheme) GravatarUiMode.LIGHT else GravatarUiMode.DARK + }, + authenticationMethod = AuthenticationMethod.OAuth( + OAuthParams { + clientId = GRAVATAR_APP_ID + redirectUri = Gravatar.GRAVATAR_QE_REDIRECT_URL + }, + ), + ), + ) ?: error("gravatarNativeQuickEditorLauncher is not initialized") + } + + override suspend fun logout(email: String) = withContext(Dispatchers.IO) { + GravatarQuickEditor.logout( + email = Email( + email, + ), + ) + } + + @AssistedFactory + interface Factory : GravatarService.Factory { + override fun create(fragment: Fragment?, onAvatarSelected: (() -> Unit)?): GravatarSdkService + } +} diff --git a/modules/services/repositories/build.gradle.kts b/modules/services/repositories/build.gradle.kts index 4e581ddff58..249436245c7 100644 --- a/modules/services/repositories/build.gradle.kts +++ b/modules/services/repositories/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { implementation(projects.modules.services.deeplink) implementation(projects.modules.services.images) + implementation(projects.modules.services.utils) testImplementation(libs.coroutines.test) testImplementation(libs.junit) diff --git a/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/user/UserManager.kt b/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/user/UserManager.kt index 5250dc22ae2..ffc617c2f30 100644 --- a/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/user/UserManager.kt +++ b/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/user/UserManager.kt @@ -24,6 +24,9 @@ import au.com.shiftyjelly.pocketcasts.repositories.searchhistory.SearchHistoryMa import au.com.shiftyjelly.pocketcasts.repositories.subscription.SubscriptionManager import au.com.shiftyjelly.pocketcasts.repositories.sync.SyncManager import au.com.shiftyjelly.pocketcasts.utils.Optional +import au.com.shiftyjelly.pocketcasts.utils.featureflag.Feature +import au.com.shiftyjelly.pocketcasts.utils.featureflag.FeatureFlag +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer import com.automattic.android.tracks.crashlogging.CrashLogging import dagger.hilt.android.qualifiers.ApplicationContext @@ -61,6 +64,7 @@ class UserManagerImpl @Inject constructor( private val crashLogging: CrashLogging, private val experimentProvider: ExperimentProvider, private val endOfYearSync: EndOfYearSync, + private val gravatarServiceFactory: GravatarService.Factory, ) : UserManager, CoroutineScope { companion object { @@ -118,6 +122,9 @@ class UserManagerImpl @Inject constructor( override fun signOut(playbackManager: PlaybackManager, wasInitiatedByUser: Boolean) { if (wasInitiatedByUser || !settings.getFullySignedOut()) { LogBuffer.i(LogBuffer.TAG_BACKGROUND_TASKS, "Signing out") + + logoutFromGravatar() + subscriptionManager.clearCachedStatus() syncManager.signOut { applicationScope.launch { @@ -146,6 +153,16 @@ class UserManagerImpl @Inject constructor( settings.setFullySignedOut(true) } + private fun logoutFromGravatar() { + if (FeatureFlag.isEnabled(Feature.GRAVATAR_NATIVE_QUICK_EDITOR)) { + syncManager.getEmail()?.let { email -> + applicationScope.launch { + gravatarServiceFactory.create().logout(email) + } + } + } + } + override fun signOutAndClearData( playbackManager: PlaybackManager, upNextQueue: UpNextQueue, diff --git a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/Gravatar.kt b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/Gravatar.kt index bfc17c539d4..fd19ede9d0d 100644 --- a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/Gravatar.kt +++ b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/Gravatar.kt @@ -2,9 +2,13 @@ package au.com.shiftyjelly.pocketcasts.utils import au.com.shiftyjelly.pocketcasts.utils.extensions.sha256 import java.net.URLEncoder +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow object Gravatar { + const val GRAVATAR_QE_REDIRECT_URL = "https://pocketcasts.com/gravatar/oauth" + fun getGravatarChangeAvatarUrl(email: String): String = "https://gravatar.com/profile?is_quick_editor=true&email=${URLEncoder.encode(email, "UTF-8")}&scope=avatars&is_app_origin=true" @@ -13,7 +17,8 @@ object Gravatar { * When a user updates their avatar, it takes a few seconds to be updated everywhere, so if we reload the same previous URL, * we'll get the old avatar. We'll get the recently uploaded avatar using the cache buster (which can be any random string). */ - private var lastTimeStamp = System.currentTimeMillis() + private val _lastTimeStamp = MutableStateFlow(System.currentTimeMillis()) + val lastTimeStamp = _lastTimeStamp.asStateFlow() /** * d=404: display no image if there is not one associated with the requested email hash @@ -23,10 +28,10 @@ object Gravatar { */ fun getUrl(email: String): String? = email.sha256()?.let { sha256Email -> - "https://www.gravatar.com/avatar/$sha256Email?d=404&s=400&_=$lastTimeStamp" + "https://www.gravatar.com/avatar/$sha256Email?d=404&s=400&_=${lastTimeStamp.value}" } fun refreshGravatarTimestamp() { - lastTimeStamp = System.currentTimeMillis() + _lastTimeStamp.tryEmit(System.currentTimeMillis()) } } diff --git a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt index a628ca00b75..8aa61aab9e7 100644 --- a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt +++ b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/featureflag/Feature.kt @@ -179,6 +179,14 @@ enum class Feature( hasFirebaseRemoteFlag = true, hasDevToggle = true, ), + GRAVATAR_NATIVE_QUICK_EDITOR( + key = "gravatar_native_quick_editor", + title = "Gravatar native QE", + defaultValue = true, + tier = FeatureTier.Free, + hasFirebaseRemoteFlag = true, + hasDevToggle = true, + ), ; companion object { diff --git a/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/gravatar/GravatarService.kt b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/gravatar/GravatarService.kt new file mode 100644 index 00000000000..f800a0be647 --- /dev/null +++ b/modules/services/utils/src/main/java/au/com/shiftyjelly/pocketcasts/utils/gravatar/GravatarService.kt @@ -0,0 +1,14 @@ +package au.com.shiftyjelly.pocketcasts.utils.gravatar + +import androidx.fragment.app.Fragment + +interface GravatarService { + + fun launchQuickEditor(isLightTheme: Boolean, email: String) + fun launchExternalQuickEditor(email: String) + suspend fun logout(email: String) + + interface Factory { + fun create(fragment: Fragment? = null, onAvatarSelected: (() -> Unit)? = null): GravatarService + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e972bc089c7..4dd2cc4dd0d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ dependencyResolutionManagement { content { includeGroup("com.automattic") includeGroup("com.automattic.tracks") + includeGroup("com.gravatar") includeGroupByRegex("org.wordpress.*") } } @@ -82,3 +83,5 @@ include(":modules:services:ui") include(":modules:services:utils") include(":modules:services:views") include(":modules:services:sharedtest") +include(":modules:services:gravatar") +include(":modules:services:gravatar-noop") diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index 61033981ce2..a460c0d2d5c 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -122,6 +122,7 @@ dependencies { implementation(projects.modules.services.analytics) implementation(projects.modules.services.compose) implementation(projects.modules.services.crashlogging) + implementation(projects.modules.services.gravatarNoop) implementation(projects.modules.services.images) implementation(projects.modules.services.localization) implementation(projects.modules.services.mediaNoop) diff --git a/wear/src/main/kotlin/au/com/shiftyjelly/pocketcasts/wear/di/WearAppModule.kt b/wear/src/main/kotlin/au/com/shiftyjelly/pocketcasts/wear/di/WearAppModule.kt index 5d8ed225ebd..d327d2b0353 100644 --- a/wear/src/main/kotlin/au/com/shiftyjelly/pocketcasts/wear/di/WearAppModule.kt +++ b/wear/src/main/kotlin/au/com/shiftyjelly/pocketcasts/wear/di/WearAppModule.kt @@ -2,6 +2,9 @@ package au.com.shiftyjelly.pocketcasts.wear.di import android.content.Context import android.net.ConnectivityManager +import au.com.shiftyjelly.pocketcasts.NoOpGravatarSdkService +import au.com.shiftyjelly.pocketcasts.utils.gravatar.GravatarService +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -10,11 +13,16 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -object WearAppModule { +abstract class WearAppModule { - @Provides - fun connectivityManager( - @ApplicationContext application: Context, - ): ConnectivityManager = - application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + companion object { + @Provides + fun connectivityManager( + @ApplicationContext application: Context, + ): ConnectivityManager = + application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + } + + @Binds + abstract fun gravatarService(factory: NoOpGravatarSdkService.Factory): GravatarService.Factory }