From 340f6db46bd344696b35d77c59ee3cf9af960869 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 12:22:58 +0300 Subject: [PATCH 01/65] Add null annotations before converting BlockProcessorFactory --- .../org/wordpress/android/ui/posts/PostUtils.java | 2 +- .../BlockProcessorFactory.java | 13 ++++++++----- .../GalleryBlockProcessor.java | 2 +- .../MediaUploadCompletionProcessor.java | 11 +++++++---- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java index 8537f9d70275..f0524294a1c0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java @@ -431,7 +431,7 @@ public static boolean shouldShowGutenbergEditor(boolean isNewPost, String postCo public static String replaceMediaFileWithUrlInGutenbergPost(@NonNull String postContent, @NonNull String localMediaId, MediaFile mediaFile, - String siteUrl) { + @NonNull String siteUrl) { if (mediaFile != null && contentContainsGutenbergBlocks(postContent)) { MediaUploadCompletionProcessor processor = new MediaUploadCompletionProcessor(localMediaId, mediaFile, siteUrl); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java index 1e8fd766a95c..f95c74bd7086 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java @@ -1,6 +1,7 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.wordpress.android.util.helpers.MediaFile; @@ -17,16 +18,16 @@ import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.VIDEOPRESS; class BlockProcessorFactory { + @NonNull private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - private final Map mMediaBlockTypeBlockProcessorMap; + private final Map mMediaBlockTypeBlockProcessorMap = new HashMap<>(); /** * This factory initializes block processors for all media block types and provides a method to retrieve a block * processor instance for a given block type. */ - BlockProcessorFactory(MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { + BlockProcessorFactory(@NonNull MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - mMediaBlockTypeBlockProcessorMap = new HashMap<>(); } /** @@ -35,7 +36,8 @@ class BlockProcessorFactory { * @param siteUrl The site url - used to generate the attachmentPage url * @return The factory instance - useful for chaining this method upon instantiation */ - BlockProcessorFactory init(@NonNull String localId, @NonNull MediaFile mediaFile, String siteUrl) { + @NonNull + BlockProcessorFactory init(@NonNull String localId, @NonNull MediaFile mediaFile, @NonNull String siteUrl) { mMediaBlockTypeBlockProcessorMap.put(IMAGE, new ImageBlockProcessor(localId, mediaFile)); mMediaBlockTypeBlockProcessorMap.put(VIDEOPRESS, new VideoPressBlockProcessor(localId, mediaFile)); mMediaBlockTypeBlockProcessorMap.put(VIDEO, new VideoBlockProcessor(localId, mediaFile)); @@ -56,7 +58,8 @@ BlockProcessorFactory init(@NonNull String localId, @NonNull MediaFile mediaFile * @param blockType The media block type for which to provide a {@link BlockProcessor} * @return The {@link BlockProcessor} for the given media block type */ - BlockProcessor getProcessorForMediaBlockType(MediaBlockType blockType) { + @Nullable + BlockProcessor getProcessorForMediaBlockType(@NonNull MediaBlockType blockType) { return mMediaBlockTypeBlockProcessorMap.get(blockType); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java index a1a28992c574..63c55c6fb4db 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java @@ -36,7 +36,7 @@ public class GalleryBlockProcessor extends BlockProcessor { .append("(.*)") // inner block contents .append("(\\s*\\s*.*)").toString(), Pattern.DOTALL); - public GalleryBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, String siteUrl, + public GalleryBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, @NonNull String siteUrl, MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { super(localId, mediaFile); mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java index 51f3f7790161..304111a570be 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java @@ -21,7 +21,8 @@ public class MediaUploadCompletionProcessor { * @param mediaFile The mediaFile containing the remote id and remote url * @param siteUrl The site url - used to generate the attachmentPage url */ - public MediaUploadCompletionProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, String siteUrl) { + public MediaUploadCompletionProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, + @NonNull String siteUrl) { mBlockProcessorFactory = new BlockProcessorFactory(this) .init(localId, mediaFile, siteUrl); } @@ -82,10 +83,12 @@ public String processContent(String content) { @NonNull private String processBlock(@NonNull String block, Boolean isSelfClosingTag) { final MediaBlockType blockType = MediaBlockType.detectBlockType(block); - final BlockProcessor blockProcessor = mBlockProcessorFactory.getProcessorForMediaBlockType(blockType); - if (blockProcessor != null) { - return blockProcessor.processBlock(block, isSelfClosingTag); + if (blockType != null) { + final BlockProcessor blockProcessor = mBlockProcessorFactory.getProcessorForMediaBlockType(blockType); + if (blockProcessor != null) { + return blockProcessor.processBlock(block, isSelfClosingTag); + } } return block; From 7e0cd6d01f2d690c363330a6a0061c8762f7b009 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 12:24:18 +0300 Subject: [PATCH 02/65] Rename .java to .kt --- .../{BlockProcessorFactory.java => BlockProcessorFactory.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/{BlockProcessorFactory.java => BlockProcessorFactory.kt} (100%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt similarity index 100% rename from WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.java rename to WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt From a9949f039c0ed8e2a5db4b327fd7eb95530b8acb Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 12:24:18 +0300 Subject: [PATCH 03/65] Convert `BlockProcessorFactory` to Kotlin --- .../BlockProcessorFactory.kt | 84 ++++++++----------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt index f95c74bd7086..f0bd3150ecf0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt @@ -1,34 +1,14 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import org.wordpress.android.util.helpers.MediaFile -import org.wordpress.android.util.helpers.MediaFile; - -import java.util.HashMap; -import java.util.Map; - -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.AUDIO; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.COVER; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.FILE; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.GALLERY; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.IMAGE; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.MEDIA_TEXT; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.VIDEO; -import static org.wordpress.android.ui.posts.mediauploadcompletionprocessors.MediaBlockType.VIDEOPRESS; - -class BlockProcessorFactory { - @NonNull - private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - private final Map mMediaBlockTypeBlockProcessorMap = new HashMap<>(); - - /** - * This factory initializes block processors for all media block types and provides a method to retrieve a block - * processor instance for a given block type. - */ - BlockProcessorFactory(@NonNull MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { - mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - } +internal class BlockProcessorFactory +/** + * This factory initializes block processors for all media block types and provides a method to retrieve a block + * processor instance for a given block type. + */(private val mMediaUploadCompletionProcessor: MediaUploadCompletionProcessor) { + private val mMediaBlockTypeBlockProcessorMap: MutableMap = + HashMap() /** * @param localId The local media id that needs replacement @@ -36,30 +16,38 @@ class BlockProcessorFactory { * @param siteUrl The site url - used to generate the attachmentPage url * @return The factory instance - useful for chaining this method upon instantiation */ - @NonNull - BlockProcessorFactory init(@NonNull String localId, @NonNull MediaFile mediaFile, @NonNull String siteUrl) { - mMediaBlockTypeBlockProcessorMap.put(IMAGE, new ImageBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(VIDEOPRESS, new VideoPressBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(VIDEO, new VideoBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(MEDIA_TEXT, new MediaTextBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(GALLERY, new GalleryBlockProcessor(localId, mediaFile, siteUrl, - mMediaUploadCompletionProcessor)); - mMediaBlockTypeBlockProcessorMap.put(COVER, new CoverBlockProcessor(localId, mediaFile, - mMediaUploadCompletionProcessor)); - mMediaBlockTypeBlockProcessorMap.put(FILE, new FileBlockProcessor(localId, mediaFile)); - mMediaBlockTypeBlockProcessorMap.put(AUDIO, new AudioBlockProcessor(localId, mediaFile)); - - return this; + fun init(localId: String, mediaFile: MediaFile, siteUrl: String): BlockProcessorFactory { + mMediaBlockTypeBlockProcessorMap[MediaBlockType.IMAGE] = + ImageBlockProcessor(localId, mediaFile) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.VIDEOPRESS] = + VideoPressBlockProcessor(localId, mediaFile) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.VIDEO] = + VideoBlockProcessor(localId, mediaFile) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.MEDIA_TEXT] = + MediaTextBlockProcessor(localId, mediaFile) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.GALLERY] = GalleryBlockProcessor( + localId, mediaFile, siteUrl, + mMediaUploadCompletionProcessor + ) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.COVER] = CoverBlockProcessor( + localId, mediaFile, + mMediaUploadCompletionProcessor + ) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.FILE] = + FileBlockProcessor(localId, mediaFile) + mMediaBlockTypeBlockProcessorMap[MediaBlockType.AUDIO] = + AudioBlockProcessor(localId, mediaFile) + + return this } /** * Retrieves the block processor instance for the given media block type. * - * @param blockType The media block type for which to provide a {@link BlockProcessor} - * @return The {@link BlockProcessor} for the given media block type + * @param blockType The media block type for which to provide a [BlockProcessor] + * @return The [BlockProcessor] for the given media block type */ - @Nullable - BlockProcessor getProcessorForMediaBlockType(@NonNull MediaBlockType blockType) { - return mMediaBlockTypeBlockProcessorMap.get(blockType); + fun getProcessorForMediaBlockType(blockType: MediaBlockType): BlockProcessor? { + return mMediaBlockTypeBlockProcessorMap[blockType] } } From 53ad66e3dc49542af864dfd9b8d1b9758613c24f Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 14:21:08 +0300 Subject: [PATCH 04/65] Reformat BlockProcessorFactory after conversion --- .../BlockProcessorFactory.kt | 60 +++++++------------ .../MediaUploadCompletionProcessor.java | 3 +- 2 files changed, 23 insertions(+), 40 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt index f0bd3150ecf0..98a34a391d75 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessorFactory.kt @@ -2,44 +2,30 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors import org.wordpress.android.util.helpers.MediaFile -internal class BlockProcessorFactory /** * This factory initializes block processors for all media block types and provides a method to retrieve a block * processor instance for a given block type. - */(private val mMediaUploadCompletionProcessor: MediaUploadCompletionProcessor) { - private val mMediaBlockTypeBlockProcessorMap: MutableMap = - HashMap() - - /** - * @param localId The local media id that needs replacement - * @param mediaFile The mediaFile containing the remote id and remote url - * @param siteUrl The site url - used to generate the attachmentPage url - * @return The factory instance - useful for chaining this method upon instantiation - */ - fun init(localId: String, mediaFile: MediaFile, siteUrl: String): BlockProcessorFactory { - mMediaBlockTypeBlockProcessorMap[MediaBlockType.IMAGE] = - ImageBlockProcessor(localId, mediaFile) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.VIDEOPRESS] = - VideoPressBlockProcessor(localId, mediaFile) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.VIDEO] = - VideoBlockProcessor(localId, mediaFile) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.MEDIA_TEXT] = - MediaTextBlockProcessor(localId, mediaFile) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.GALLERY] = GalleryBlockProcessor( - localId, mediaFile, siteUrl, - mMediaUploadCompletionProcessor - ) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.COVER] = CoverBlockProcessor( - localId, mediaFile, - mMediaUploadCompletionProcessor - ) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.FILE] = - FileBlockProcessor(localId, mediaFile) - mMediaBlockTypeBlockProcessorMap[MediaBlockType.AUDIO] = - AudioBlockProcessor(localId, mediaFile) - - return this - } + * @param localId The local media id that needs replacement + * @param mediaFile The mediaFile containing the remote id and remote url` + * @param siteUrl The site url - used to generate the attachmentPage url + * @return The factory instance - useful for chaining this method upon instantiation + */ +internal class BlockProcessorFactory( + mediaUploadCompletionProcessor: MediaUploadCompletionProcessor, + localId: String, + mediaFile: MediaFile, + siteUrl: String +) { + private val mediaBlockTypeBlockProcessorMap = hashMapOf( + MediaBlockType.IMAGE to ImageBlockProcessor(localId, mediaFile), + MediaBlockType.VIDEOPRESS to VideoPressBlockProcessor(localId, mediaFile), + MediaBlockType.VIDEO to VideoBlockProcessor(localId, mediaFile), + MediaBlockType.MEDIA_TEXT to MediaTextBlockProcessor(localId, mediaFile), + MediaBlockType.GALLERY to GalleryBlockProcessor(localId, mediaFile, siteUrl, mediaUploadCompletionProcessor), + MediaBlockType.COVER to CoverBlockProcessor(localId, mediaFile, mediaUploadCompletionProcessor), + MediaBlockType.FILE to FileBlockProcessor(localId, mediaFile), + MediaBlockType.AUDIO to AudioBlockProcessor(localId, mediaFile) + ) /** * Retrieves the block processor instance for the given media block type. @@ -47,7 +33,5 @@ internal class BlockProcessorFactory * @param blockType The media block type for which to provide a [BlockProcessor] * @return The [BlockProcessor] for the given media block type */ - fun getProcessorForMediaBlockType(blockType: MediaBlockType): BlockProcessor? { - return mMediaBlockTypeBlockProcessorMap[blockType] - } + fun getProcessorForMediaBlockType(blockType: MediaBlockType) = mediaBlockTypeBlockProcessorMap[blockType] } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java index 304111a570be..9cf13a536657 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaUploadCompletionProcessor.java @@ -23,8 +23,7 @@ public class MediaUploadCompletionProcessor { */ public MediaUploadCompletionProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, @NonNull String siteUrl) { - mBlockProcessorFactory = new BlockProcessorFactory(this) - .init(localId, mediaFile, siteUrl); + mBlockProcessorFactory = new BlockProcessorFactory(this, localId, mediaFile, siteUrl); } /** From cbe2e688bab6632f67a3cdb57ced351cfccd3a74 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 14:49:19 +0300 Subject: [PATCH 05/65] Make Document parameter of processBlockContentDocument() NonNull --- .../AudioBlockProcessor.kt | 15 ++++++--------- .../BlockProcessor.kt | 4 ++-- .../CoverBlockProcessor.java | 5 +++-- .../FileBlockProcessor.kt | 15 ++++++--------- .../GalleryBlockProcessor.java | 2 +- .../ImageBlockProcessor.java | 2 +- .../MediaTextBlockProcessor.java | 2 +- .../VideoBlockProcessor.java | 2 +- .../VideoPressBlockProcessor.kt | 2 +- 9 files changed, 22 insertions(+), 27 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt index 03e2ac4a1886..e949a14069cf 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt @@ -5,17 +5,14 @@ import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile class AudioBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { - override fun processBlockContentDocument(document: Document?): Boolean { - val audioElements = document?.select(AUDIO_TAG) + override fun processBlockContentDocument(document: Document): Boolean { + val audioElements = document.select(AUDIO_TAG) - audioElements?.let { elements -> - for (element in elements) { - // replaces the src attribute's local url with the remote counterpart. - element.attr(SRC_ATTRIBUTE, remoteUrl) - } - return true + for (element in audioElements) { + // replaces the src attribute's local url with the remote counterpart. + element.attr(SRC_ATTRIBUTE, remoteUrl) } - return false + return true } override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt index b024416d51f4..fa666fd960dd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt @@ -83,7 +83,7 @@ abstract class BlockProcessor internal constructor(@JvmField var localId: String .append(jsonAttributes) // json parser output .append(" /-->") .toString() - } else if (processBlockContentDocument(blockContentDocument)) { + } else if (blockContentDocument?.let { processBlockContentDocument(it) } == true) { // return injected block StringBuilder() .append("") - .toString() - } else if (blockContentDocument?.let { processBlockContentDocument(it) } == true) { - // return injected block - StringBuilder() - .append("\n") - .append(blockContentDocument?.body()?.html()) // HTML parser output - .append(closingComment) - .toString() - } else { - block - } - } else { - processInnerBlock(block) // delegate to inner blocks if needed + fun processBlock(block: String, isSelfClosingTag: Boolean = false) = when { + !splitBlock(block, isSelfClosingTag) -> block // leave block unchanged + !processBlockJsonAttributes(jsonAttributes) -> processInnerBlock(block) // delegate to inner blocks if needed + isSelfClosingTag -> { + // return injected block + StringBuilder() + .append("") + .toString() } - } else { - // leave block unchanged - block + + blockContentDocument?.let { processBlockContentDocument(it) } == true -> { + // return injected block + StringBuilder() + .append("\n") + .append(blockContentDocument?.body()?.html()) // HTML parser output + .append(closingComment) + .toString() + } + + else -> block // leave block unchanged } fun addIntPropertySafely(jsonAttributes: JsonObject, propertyName: String, value: String) = try { From 45ec0c403c2e340eb6e630c4758e244eaec8bb9c Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 15:18:36 +0300 Subject: [PATCH 07/65] Make jsonAttributes parameter of processBlockJsonAttributes() NonNull --- .../mediauploadcompletionprocessors/AudioBlockProcessor.kt | 4 ++-- .../mediauploadcompletionprocessors/BlockProcessor.kt | 7 +++++-- .../CoverBlockProcessor.java | 2 +- .../mediauploadcompletionprocessors/FileBlockProcessor.kt | 4 ++-- .../GalleryBlockProcessor.java | 3 +-- .../ImageBlockProcessor.java | 3 +-- .../MediaTextBlockProcessor.java | 3 +-- .../VideoBlockProcessor.java | 3 +-- .../VideoPressBlockProcessor.kt | 6 +++--- 9 files changed, 17 insertions(+), 18 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt index e949a14069cf..18b7a82b0ad4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/AudioBlockProcessor.kt @@ -15,8 +15,8 @@ class AudioBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcesso return true } - override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { - val id = jsonAttributes?.get(ID_ATTRIBUTE) + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes.get(ID_ATTRIBUTE) return if (id != null && !id.isJsonNull && id.asString == localId) { jsonAttributes.apply { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt index 10aaf1887c43..0de94a0e19ed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/BlockProcessor.kt @@ -74,7 +74,10 @@ abstract class BlockProcessor internal constructor(@JvmField var localId: String @JvmOverloads fun processBlock(block: String, isSelfClosingTag: Boolean = false) = when { !splitBlock(block, isSelfClosingTag) -> block // leave block unchanged - !processBlockJsonAttributes(jsonAttributes) -> processInnerBlock(block) // delegate to inner blocks if needed + jsonAttributes?.let { !processBlockJsonAttributes(it) } == true -> { + // delegate to inner blocks if needed + processInnerBlock(block) + } isSelfClosingTag -> { // return injected block StringBuilder() @@ -131,7 +134,7 @@ abstract class BlockProcessor internal constructor(@JvmField var localId: String * @param jsonAttributes the attributes object used to check for a match with the local id, and mutated if necessary * @return */ - abstract fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean + abstract fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean /** * This method can be optionally overridden by concrete implementations to delegate further processing via recursion diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java index e30844ed0c2a..701d1a468035 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java @@ -59,7 +59,7 @@ public String processInnerBlock(@NonNull String block) { } @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { + public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { JsonElement id = jsonAttributes.get("id"); if (id != null && !id.isJsonNull() && id.getAsInt() == Integer.parseInt(localId, 10)) { addIntPropertySafely(jsonAttributes, "id", remoteId); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt index 4a3ba1d2666b..0b6997e4dac6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/FileBlockProcessor.kt @@ -19,8 +19,8 @@ class FileBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor return true } - override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { - val id = jsonAttributes?.get(ID_ATTRIBUTE) + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes.get(ID_ATTRIBUTE) return if (id != null && !id.isJsonNull && id.asString == localId) { jsonAttributes.apply { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java index bf9c06a8292e..031e623b8021 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java @@ -1,7 +1,6 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -88,7 +87,7 @@ public boolean processBlockContentDocument(@NonNull Document document) { } @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { + public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { // The new format does not have an `ids` attributes, so returning false here will defer to recursive processing JsonArray ids = jsonAttributes.getAsJsonArray("ids"); if (ids == null || ids.isJsonNull()) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java index 05917ba78415..9b2c9e86a7df 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java @@ -1,7 +1,6 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -37,7 +36,7 @@ public boolean processBlockContentDocument(@NonNull Document document) { } @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { + public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { JsonElement id = jsonAttributes.get("id"); if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { addIntPropertySafely(jsonAttributes, "id", remoteId); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java index 2e73bd71928e..0bdbccfe74d9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java @@ -1,7 +1,6 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -49,7 +48,7 @@ public boolean processBlockContentDocument(@NonNull Document document) { } @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { + public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { JsonElement id = jsonAttributes.get("mediaId"); if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { addIntPropertySafely(jsonAttributes, "mediaId", remoteId); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java index adc1c7df7fae..dc290b33a8f3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java @@ -1,7 +1,6 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -33,7 +32,7 @@ public boolean processBlockContentDocument(@NonNull Document document) { } @Override - public boolean processBlockJsonAttributes(@Nullable JsonObject jsonAttributes) { + public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { JsonElement id = jsonAttributes.get("id"); if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { addIntPropertySafely(jsonAttributes, "id", remoteId); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt index 86491d220d07..7e5a6168c03b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoPressBlockProcessor.kt @@ -9,9 +9,9 @@ class VideoPressBlockProcessor(localId: String, mediaFile: MediaFile) : BlockPro return false } - override fun processBlockJsonAttributes(jsonAttributes: JsonObject?): Boolean { - val id = jsonAttributes?.get(ID_ATTRIBUTE) - val src = jsonAttributes?.get(SRC_ATTRIBUTE)?.asString + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes.get(ID_ATTRIBUTE) + val src = jsonAttributes.get(SRC_ATTRIBUTE)?.asString return if (id != null && !id.isJsonNull && id.asString == localId) { jsonAttributes.apply { From 4ce5cbb23d725f6579f99a381fd971ddd72ec966 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 15:20:17 +0300 Subject: [PATCH 08/65] Rename .java to .kt --- .../{CoverBlockProcessor.java => CoverBlockProcessor.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/{CoverBlockProcessor.java => CoverBlockProcessor.kt} (100%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt similarity index 100% rename from WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.java rename to WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt From 0e010460093e12a9b4ecc90a4fcb3beee9457fe6 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 15:20:17 +0300 Subject: [PATCH 09/65] Convert `CoverBlockProcessor` to Kotlin --- .../CoverBlockProcessor.kt | 134 ++++++++---------- 1 file changed, 62 insertions(+), 72 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt index 701d1a468035..ff8de93e9c82 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt @@ -1,106 +1,96 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile +import java.util.regex.Pattern -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +class CoverBlockProcessor( + localId: String, mediaFile: MediaFile, + private val mMediaUploadCompletionProcessor: MediaUploadCompletionProcessor +) : BlockProcessor(localId, mediaFile) { + private var mHasVideoBackground = false -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class CoverBlockProcessor extends BlockProcessor { - private boolean mHasVideoBackground = false; - - /** - * Template pattern used to match and splice cover inner blocks - */ - private static final Pattern PATTERN_COVER_INNER = Pattern.compile(new StringBuilder() - .append("(^.*?
\\s*)") - .append("(.*)") // inner block contents - .append("(\\s*
\\s*\\s*.*)").toString(), Pattern.DOTALL); - - /** - * Pattern to match background-image url in cover block html content - */ - private static final Pattern PATTERN_BACKGROUND_IMAGE_URL = Pattern.compile( - "background-image:\\s*url\\([^\\)]+\\)"); - - @NonNull - private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - - public CoverBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, - @NonNull MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { - super(localId, mediaFile); - mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - } - - @NonNull - @Override - public String processInnerBlock(@NonNull String block) { - Matcher innerMatcher = PATTERN_COVER_INNER.matcher(block); - boolean innerCapturesFound = innerMatcher.find(); + override fun processInnerBlock(block: String): String { + val innerMatcher = PATTERN_COVER_INNER.matcher(block) + val innerCapturesFound = innerMatcher.find() // process inner contents recursively if (innerCapturesFound) { - String innerProcessed = mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)); // - return new StringBuilder() - .append(innerMatcher.group(1)) - .append(innerProcessed) - .append(innerMatcher.group(3)) - .toString(); + val innerProcessed = + mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) // + return StringBuilder() + .append(innerMatcher.group(1)) + .append(innerProcessed) + .append(innerMatcher.group(3)) + .toString() } - return block; + return block } - @Override - public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("id"); - if (id != null && !id.isJsonNull() && id.getAsInt() == Integer.parseInt(localId, 10)) { - addIntPropertySafely(jsonAttributes, "id", remoteId); + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["id"] + if (id != null && !id.isJsonNull && id.asInt == localId.toInt(10)) { + addIntPropertySafely(jsonAttributes, "id", remoteId) - jsonAttributes.addProperty("url", remoteUrl); + jsonAttributes.addProperty("url", remoteUrl) // check if background type is video - JsonElement backgroundType = jsonAttributes.get("backgroundType"); - mHasVideoBackground = backgroundType != null && !backgroundType.isJsonNull() && "video".equals( - backgroundType.getAsString()); - return true; + val backgroundType = jsonAttributes["backgroundType"] + mHasVideoBackground = + backgroundType != null && !backgroundType.isJsonNull && "video" == backgroundType.asString + return true } - return false; + return false } - @Override - public boolean processBlockContentDocument(@NonNull Document document) { + override fun processBlockContentDocument(document: Document): Boolean { // select cover block div - Element targetDiv = document.selectFirst(".wp-block-cover"); + val targetDiv = document.selectFirst(".wp-block-cover") // if a match is found, proceed with replacement if (targetDiv != null) { if (mHasVideoBackground) { - Element videoElement = targetDiv.selectFirst("video"); + val videoElement = targetDiv.selectFirst("video") if (videoElement != null) { - videoElement.attr("src", remoteUrl); + videoElement.attr("src", remoteUrl) } else { - return false; + return false } } else { // replace background-image url in style attribute - String style = PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDiv.attr("style")).replaceFirst( - String.format("background-image:url(%1$s)", remoteUrl)); - targetDiv.attr("style", style); + val style = + PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDiv.attr("style")).replaceFirst( + String.format("background-image:url(%1\$s)", remoteUrl) + ) + targetDiv.attr("style", style) } // return injected block - return true; + return true } - return false; + return false + } + + companion object { + /** + * Template pattern used to match and splice cover inner blocks + */ + private val PATTERN_COVER_INNER: Pattern = Pattern.compile( + StringBuilder() + .append("(^.*?
\\s*)") + .append("(.*)") // inner block contents + .append("(\\s*
\\s*\\s*.*)").toString(), Pattern.DOTALL + ) + + /** + * Pattern to match background-image url in cover block html content + */ + private val PATTERN_BACKGROUND_IMAGE_URL: Pattern = Pattern.compile( + "background-image:\\s*url\\([^\\)]+\\)" + ) } } From 923677354bc5537b9d82b2cfd4a9bbafc99d0b0d Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 15:29:09 +0300 Subject: [PATCH 10/65] Reformat CoverBlockProcessor after conversion --- .../CoverBlockProcessor.kt | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt index ff8de93e9c82..15a0b98aab48 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt @@ -6,10 +6,11 @@ import org.wordpress.android.util.helpers.MediaFile import java.util.regex.Pattern class CoverBlockProcessor( - localId: String, mediaFile: MediaFile, - private val mMediaUploadCompletionProcessor: MediaUploadCompletionProcessor + localId: String, + mediaFile: MediaFile, + private val mediaUploadCompletionProcessor: MediaUploadCompletionProcessor ) : BlockProcessor(localId, mediaFile) { - private var mHasVideoBackground = false + private var hasVideoBackground = false override fun processInnerBlock(block: String): String { val innerMatcher = PATTERN_COVER_INNER.matcher(block) @@ -17,8 +18,7 @@ class CoverBlockProcessor( // process inner contents recursively if (innerCapturesFound) { - val innerProcessed = - mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) // + val innerProcessed = mediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) return StringBuilder() .append(innerMatcher.group(1)) .append(innerProcessed) @@ -38,8 +38,9 @@ class CoverBlockProcessor( // check if background type is video val backgroundType = jsonAttributes["backgroundType"] - mHasVideoBackground = - backgroundType != null && !backgroundType.isJsonNull && "video" == backgroundType.asString + hasVideoBackground = backgroundType != null && + !backgroundType.isJsonNull && + "video" == backgroundType.asString return true } @@ -52,7 +53,7 @@ class CoverBlockProcessor( // if a match is found, proceed with replacement if (targetDiv != null) { - if (mHasVideoBackground) { + if (hasVideoBackground) { val videoElement = targetDiv.selectFirst("video") if (videoElement != null) { videoElement.attr("src", remoteUrl) @@ -61,10 +62,8 @@ class CoverBlockProcessor( } } else { // replace background-image url in style attribute - val style = - PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDiv.attr("style")).replaceFirst( - String.format("background-image:url(%1\$s)", remoteUrl) - ) + val style = PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDiv.attr("style")) + .replaceFirst(String.format("background-image:url(%1\$s)", remoteUrl)) targetDiv.attr("style", style) } @@ -83,14 +82,13 @@ class CoverBlockProcessor( StringBuilder() .append("(^.*?
\\s*)") .append("(.*)") // inner block contents - .append("(\\s*
\\s*\\s*.*)").toString(), Pattern.DOTALL + .append("(\\s*\\s*\\s*.*)").toString(), + Pattern.DOTALL ) /** * Pattern to match background-image url in cover block html content */ - private val PATTERN_BACKGROUND_IMAGE_URL: Pattern = Pattern.compile( - "background-image:\\s*url\\([^\\)]+\\)" - ) + private val PATTERN_BACKGROUND_IMAGE_URL: Pattern = Pattern.compile("background-image:\\s*url\\([^)]+\\)") } } From ee7ae6ca9026ba38c97a60b2f8e0332f4c4ad9a0 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 16:14:44 +0300 Subject: [PATCH 11/65] Fix detekt errors in `CoverBlockProcessor` --- .../CoverBlockProcessor.kt | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt index 15a0b98aab48..afdbb466c5fe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/CoverBlockProcessor.kt @@ -3,6 +3,7 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors import com.google.gson.JsonObject import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile +import java.util.Locale import java.util.regex.Pattern class CoverBlockProcessor( @@ -22,7 +23,7 @@ class CoverBlockProcessor( return StringBuilder() .append(innerMatcher.group(1)) .append(innerProcessed) - .append(innerMatcher.group(3)) + .append(innerMatcher.group(GROUP_3)) .toString() } @@ -31,7 +32,7 @@ class CoverBlockProcessor( override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { val id = jsonAttributes["id"] - if (id != null && !id.isJsonNull && id.asInt == localId.toInt(10)) { + if (id != null && !id.isJsonNull && id.asInt == localId.toInt(RADIX)) { addIntPropertySafely(jsonAttributes, "id", remoteId) jsonAttributes.addProperty("url", remoteUrl) @@ -52,43 +53,40 @@ class CoverBlockProcessor( val targetDiv = document.selectFirst(".wp-block-cover") // if a match is found, proceed with replacement - if (targetDiv != null) { + return targetDiv?.let { targetDivElement -> if (hasVideoBackground) { - val videoElement = targetDiv.selectFirst("video") - if (videoElement != null) { - videoElement.attr("src", remoteUrl) - } else { - return false - } + val videoElement = targetDivElement.selectFirst("video") + videoElement?.attr("src", remoteUrl) ?: return false } else { // replace background-image url in style attribute - val style = PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDiv.attr("style")) - .replaceFirst(String.format("background-image:url(%1\$s)", remoteUrl)) - targetDiv.attr("style", style) + val style = PATTERN_BACKGROUND_IMAGE_URL.matcher(targetDivElement.attr("style")) + .replaceFirst(String.format(Locale.getDefault(), "background-image:url(%1\$s)", remoteUrl)) + targetDivElement.attr("style", style) } // return injected block - return true - } - - return false + true + } ?: false } companion object { /** * Template pattern used to match and splice cover inner blocks */ - private val PATTERN_COVER_INNER: Pattern = Pattern.compile( + private val PATTERN_COVER_INNER = Pattern.compile( StringBuilder() .append("(^.*?
\\s*)") .append("(.*)") // inner block contents - .append("(\\s*
\\s*\\s*.*)").toString(), + .append("(\\s*\\s*\\s*.*)") + .toString(), Pattern.DOTALL ) /** * Pattern to match background-image url in cover block html content */ - private val PATTERN_BACKGROUND_IMAGE_URL: Pattern = Pattern.compile("background-image:\\s*url\\([^)]+\\)") + private val PATTERN_BACKGROUND_IMAGE_URL = Pattern.compile("background-image:\\s*url\\([^)]+\\)") + private const val GROUP_3 = 3 + private const val RADIX = 10 } } From 220453d389b652ff26a044d14162c6ba417d5556 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 19:41:50 +0300 Subject: [PATCH 12/65] Add null annotations to GalleryBlockProcessor --- .../GalleryBlockProcessor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java index 031e623b8021..6425f9057f52 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java @@ -1,6 +1,7 @@ package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -18,13 +19,17 @@ import static org.wordpress.android.util.AppLog.T.MEDIA; public class GalleryBlockProcessor extends BlockProcessor { + @NonNull private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; + @Nullable private String mAttachmentPageUrl; + @Nullable private String mLinkTo; /** * Query selector for selecting the img element from gallery which needs processing */ + @NonNull private String mGalleryImageQuerySelector; /** @@ -36,7 +41,7 @@ public class GalleryBlockProcessor extends BlockProcessor { .append("(\\s*\\s*.*)").toString(), Pattern.DOTALL); public GalleryBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, @NonNull String siteUrl, - MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { + @NonNull MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { super(localId, mediaFile); mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; mGalleryImageQuerySelector = new StringBuilder() From 38a7891c92a104aa4173557a0c800d806fec0d97 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 19:42:26 +0300 Subject: [PATCH 13/65] Rename .java to .kt --- .../{GalleryBlockProcessor.java => GalleryBlockProcessor.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/{GalleryBlockProcessor.java => GalleryBlockProcessor.kt} (100%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt similarity index 100% rename from WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.java rename to WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt From 23fdbec6c2e79d4312e0fc9868564742c6096d19 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 19:42:26 +0300 Subject: [PATCH 14/65] Convert GalleryBlockProcessor to Kotlin --- .../GalleryBlockProcessor.kt | 173 ++++++++---------- 1 file changed, 74 insertions(+), 99 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt index 6425f9057f52..1d2e57578ba8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt @@ -1,137 +1,112 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.helpers.MediaFile; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.wordpress.android.util.AppLog.T.MEDIA; - -public class GalleryBlockProcessor extends BlockProcessor { - @NonNull - private final MediaUploadCompletionProcessor mMediaUploadCompletionProcessor; - @Nullable - private String mAttachmentPageUrl; - @Nullable - private String mLinkTo; +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors + +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import org.jsoup.nodes.Document +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.helpers.MediaFile +import java.util.regex.Pattern + +class GalleryBlockProcessor( + localId: String, mediaFile: MediaFile, siteUrl: String, + private val mMediaUploadCompletionProcessor: MediaUploadCompletionProcessor +) : BlockProcessor(localId, mediaFile) { + private val mAttachmentPageUrl: String? = mediaFile.getAttachmentPageURL(siteUrl) + private var mLinkTo: String? = null /** * Query selector for selecting the img element from gallery which needs processing */ - @NonNull - private String mGalleryImageQuerySelector; + private val mGalleryImageQuerySelector = StringBuilder() + .append("img[data-id=\"") + .append(localId) + .append("\"]") + .toString() - /** - * Template pattern used to match and splice inner image blocks in the refactored gallery format - */ - private static final Pattern PATTERN_GALLERY_INNER = Pattern.compile(new StringBuilder() - .append("(^.*?
\\s*)") - .append("(.*)") // inner block contents - .append("(\\s*
\\s*.*)").toString(), Pattern.DOTALL); - - public GalleryBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile, @NonNull String siteUrl, - @NonNull MediaUploadCompletionProcessor mediaUploadCompletionProcessor) { - super(localId, mediaFile); - mMediaUploadCompletionProcessor = mediaUploadCompletionProcessor; - mGalleryImageQuerySelector = new StringBuilder() - .append("img[data-id=\"") - .append(localId) - .append("\"]") - .toString(); - mAttachmentPageUrl = mediaFile.getAttachmentPageURL(siteUrl); - } - - @Override - public boolean processBlockContentDocument(@NonNull Document document) { + override fun processBlockContentDocument(document: Document): Boolean { // select image element with our local id - Element targetImg = document.select(mGalleryImageQuerySelector).first(); + val targetImg = document.select(mGalleryImageQuerySelector).first() // if a match is found, proceed with replacement if (targetImg != null) { // replace attributes - targetImg.attr("src", remoteUrl); - targetImg.attr("data-id", remoteId); - targetImg.attr("data-full-url", remoteUrl); - targetImg.attr("data-link", mAttachmentPageUrl); + targetImg.attr("src", remoteUrl) + targetImg.attr("data-id", remoteId) + targetImg.attr("data-full-url", remoteUrl) + targetImg.attr("data-link", mAttachmentPageUrl) // replace class - targetImg.removeClass("wp-image-" + localId); - targetImg.addClass("wp-image-" + remoteId); + targetImg.removeClass("wp-image-$localId") + targetImg.addClass("wp-image-$remoteId") // set parent anchor href if necessary - Element parent = targetImg.parent(); - if (parent != null && parent.is("a") && mLinkTo != null) { - switch (mLinkTo) { - case "file": - parent.attr("href", remoteUrl); - break; - case "post": - parent.attr("href", mAttachmentPageUrl); - break; - default: - return false; + val parent = targetImg.parent() + if (parent != null && parent.`is`("a") && mLinkTo != null) { + when (mLinkTo) { + "file" -> parent.attr("href", remoteUrl) + "post" -> parent.attr("href", mAttachmentPageUrl) + else -> return false } } // return injected block - return true; + return true } - return false; + return false } - @Override - public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { // The new format does not have an `ids` attributes, so returning false here will defer to recursive processing - JsonArray ids = jsonAttributes.getAsJsonArray("ids"); - if (ids == null || ids.isJsonNull()) { - return false; + val ids = jsonAttributes.getAsJsonArray("ids") + if (ids == null || ids.isJsonNull) { + return false } - JsonElement linkTo = jsonAttributes.get("linkTo"); - if (linkTo != null && !linkTo.isJsonNull()) { - mLinkTo = linkTo.getAsString(); + val linkTo = jsonAttributes["linkTo"] + if (linkTo != null && !linkTo.isJsonNull) { + mLinkTo = linkTo.asString } - for (int i = 0; i < ids.size(); i++) { - JsonElement id = ids.get(i); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { + for (i in 0 until ids.size()) { + val id = ids[i] + if (id != null && !id.isJsonNull && id.asString == localId) { try { - ids.set(i, new JsonPrimitive(Integer.parseInt(remoteId, 10))); - } catch (NumberFormatException e) { - AppLog.e(MEDIA, e.getMessage()); + ids[i] = JsonPrimitive(remoteId.toInt(10)) + } catch (e: NumberFormatException) { + AppLog.e(AppLog.T.MEDIA, e.message) } - return true; + return true } } - return false; + return false } - @NonNull - @Override - public String processInnerBlock(@NonNull String block) { - Matcher innerMatcher = PATTERN_GALLERY_INNER.matcher(block); - boolean innerCapturesFound = innerMatcher.find(); + override fun processInnerBlock(block: String): String { + val innerMatcher = PATTERN_GALLERY_INNER.matcher(block) + val innerCapturesFound = innerMatcher.find() // process inner contents recursively if (innerCapturesFound) { - String innerProcessed = mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)); // - return new StringBuilder() - .append(innerMatcher.group(1)) - .append(innerProcessed) - .append(innerMatcher.group(3)) - .toString(); + val innerProcessed = + mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) // + return StringBuilder() + .append(innerMatcher.group(1)) + .append(innerProcessed) + .append(innerMatcher.group(3)) + .toString() } - return block; + return block + } + + companion object { + /** + * Template pattern used to match and splice inner image blocks in the refactored gallery format + */ + private val PATTERN_GALLERY_INNER: Pattern = Pattern.compile( + StringBuilder() + .append("(^.*?
\\s*)") + .append("(.*)") // inner block contents + .append("(\\s*
\\s*.*)").toString(), Pattern.DOTALL + ) } } From 491e13806ff96153304c80f64547726875e19d12 Mon Sep 17 00:00:00 2001 From: Andy Valdez Date: Mon, 10 Jun 2024 17:34:33 -0400 Subject: [PATCH 15/65] Revert "Merge pull request #19981 from wordpress-mobile/fix/target-sdk-14-foreground-service-types" This reverts commit 621ccfb6fdb0bd6a9ec1ccb3453fb2b3ef2414ac, reversing changes made to c9602d925121e29d5c41b6b27583dce73ad30b36. --- WordPress/src/main/AndroidManifest.xml | 57 +++++++++----------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index dfb16f0c711f..715c240b83c5 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -844,105 +844,88 @@ + android:label="Reader Update Service" /> + android:label="Reader Update JobService" /> + android:label="Reader Discover Service" /> + android:label="Reader Discover JobService" /> + android:label="Reader Post Service" /> + android:label="Reader Post JobService" /> + android:label="Reader Search Service" /> + android:label="Reader Search Job Service" /> + android:label="Reader Comment Service" /> + android:label="Suggestion Service" /> + android:label="Notifications Quick Actions processing Service" /> + android:label="Notifications Update Service" /> + android:label="Notifications Update Job Service" /> + android:label="Installation Referrer Service" /> + android:label="Installation Referrer Service" /> + android:label="Login to WPCOM Service" /> + android:label="Site Creation Service" /> + android:permission="android.permission.BIND_REMOTEVIEWS" /> + android:exported="false" > From 6dfda9ea4f333aca4631123714ad9aa4b9be3498 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Sun, 9 Jun 2024 20:10:48 +0300 Subject: [PATCH 16/65] Fix detekt errors after converting GalleryBlockProcessor to Kotlin --- .../GalleryBlockProcessor.kt | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt index 1d2e57578ba8..fd325ebe8f19 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/GalleryBlockProcessor.kt @@ -8,16 +8,18 @@ import org.wordpress.android.util.helpers.MediaFile import java.util.regex.Pattern class GalleryBlockProcessor( - localId: String, mediaFile: MediaFile, siteUrl: String, - private val mMediaUploadCompletionProcessor: MediaUploadCompletionProcessor + localId: String, + mediaFile: MediaFile, + siteUrl: String, + private val mediaUploadCompletionProcessor: MediaUploadCompletionProcessor ) : BlockProcessor(localId, mediaFile) { - private val mAttachmentPageUrl: String? = mediaFile.getAttachmentPageURL(siteUrl) - private var mLinkTo: String? = null + private val attachmentPageUrl = mediaFile.getAttachmentPageURL(siteUrl) + private var linkTo: String? = null /** * Query selector for selecting the img element from gallery which needs processing */ - private val mGalleryImageQuerySelector = StringBuilder() + private val galleryImageQuerySelector = StringBuilder() .append("img[data-id=\"") .append(localId) .append("\"]") @@ -25,56 +27,54 @@ class GalleryBlockProcessor( override fun processBlockContentDocument(document: Document): Boolean { // select image element with our local id - val targetImg = document.select(mGalleryImageQuerySelector).first() + val targetImg = document.select(galleryImageQuerySelector).first() // if a match is found, proceed with replacement - if (targetImg != null) { + return targetImg?.let { // replace attributes - targetImg.attr("src", remoteUrl) - targetImg.attr("data-id", remoteId) - targetImg.attr("data-full-url", remoteUrl) - targetImg.attr("data-link", mAttachmentPageUrl) + it.attr("src", remoteUrl) + it.attr("data-id", remoteId) + it.attr("data-full-url", remoteUrl) + it.attr("data-link", attachmentPageUrl) // replace class - targetImg.removeClass("wp-image-$localId") - targetImg.addClass("wp-image-$remoteId") + it.removeClass("wp-image-$localId") + it.addClass("wp-image-$remoteId") // set parent anchor href if necessary - val parent = targetImg.parent() - if (parent != null && parent.`is`("a") && mLinkTo != null) { - when (mLinkTo) { + val parent = it.parent() + if (parent != null && parent.`is`("a") && linkTo != null) { + when (linkTo) { "file" -> parent.attr("href", remoteUrl) - "post" -> parent.attr("href", mAttachmentPageUrl) + "post" -> parent.attr("href", attachmentPageUrl) else -> return false } } // return injected block - return true - } - - return false + true + } ?: false } override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { // The new format does not have an `ids` attributes, so returning false here will defer to recursive processing val ids = jsonAttributes.getAsJsonArray("ids") - if (ids == null || ids.isJsonNull) { - return false - } - val linkTo = jsonAttributes["linkTo"] - if (linkTo != null && !linkTo.isJsonNull) { - mLinkTo = linkTo.asString - } - for (i in 0 until ids.size()) { - val id = ids[i] - if (id != null && !id.isJsonNull && id.asString == localId) { - try { - ids[i] = JsonPrimitive(remoteId.toInt(10)) - } catch (e: NumberFormatException) { - AppLog.e(AppLog.T.MEDIA, e.message) + + if (ids != null && !ids.isJsonNull) { + val linkTo = jsonAttributes["linkTo"] + if (linkTo != null && !linkTo.isJsonNull) { + this.linkTo = linkTo.asString + } + + ids.forEachIndexed { index, jsonElement -> + if (jsonElement != null && !jsonElement.isJsonNull && jsonElement.asString == localId) { + try { + ids[index] = JsonPrimitive(remoteId.toInt(RADIX)) + } catch (e: NumberFormatException) { + AppLog.e(AppLog.T.MEDIA, e.message) + } + return true } - return true } } return false @@ -86,12 +86,11 @@ class GalleryBlockProcessor( // process inner contents recursively if (innerCapturesFound) { - val innerProcessed = - mMediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) // + val innerProcessed = mediaUploadCompletionProcessor.processContent(innerMatcher.group(2)) return StringBuilder() .append(innerMatcher.group(1)) .append(innerProcessed) - .append(innerMatcher.group(3)) + .append(innerMatcher.group(GROUP_3)) .toString() } @@ -102,11 +101,14 @@ class GalleryBlockProcessor( /** * Template pattern used to match and splice inner image blocks in the refactored gallery format */ - private val PATTERN_GALLERY_INNER: Pattern = Pattern.compile( + private val PATTERN_GALLERY_INNER = Pattern.compile( StringBuilder() .append("(^.*?
\\s*)") .append("(.*)") // inner block contents - .append("(\\s*
\\s*.*)").toString(), Pattern.DOTALL + .append("(\\s*\\s*.*)").toString(), + Pattern.DOTALL ) + private const val RADIX = 10 + private const val GROUP_3 = 3 } } From fedca5cd5d35c3f08a03718414b8b9e392952c8e Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 13:07:40 +0300 Subject: [PATCH 17/65] Rename .java to .kt --- .../{ImageBlockProcessor.java => ImageBlockProcessor.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/{ImageBlockProcessor.java => ImageBlockProcessor.kt} (100%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt similarity index 100% rename from WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.java rename to WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt From b5c6b6c9d909d579e1f21a01f68be2b2e783ee54 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 13:07:40 +0300 Subject: [PATCH 18/65] Convert ImageBlockProcessor to Kotlin --- .../ImageBlockProcessor.kt | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt index 9b2c9e86a7df..f7a3d4fbf5f2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt @@ -1,47 +1,36 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors -import androidx.annotation.NonNull; +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - - -public class ImageBlockProcessor extends BlockProcessor { - public ImageBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile) { - super(localId, mediaFile); - } - - @Override - public boolean processBlockContentDocument(@NonNull Document document) { +class ImageBlockProcessor(localId: String, mediaFile: MediaFile) : + BlockProcessor(localId, mediaFile) { + override fun processBlockContentDocument(document: Document): Boolean { // select image element with our local id - Element targetImg = document.select("img").first(); + val targetImg = document.select("img").first() // if a match is found, proceed with replacement if (targetImg != null) { // replace attributes - targetImg.attr("src", remoteUrl); + targetImg.attr("src", remoteUrl) // replace class - targetImg.removeClass("wp-image-" + localId); - targetImg.addClass("wp-image-" + remoteId); + targetImg.removeClass("wp-image-$localId") + targetImg.addClass("wp-image-$remoteId") - return true; + return true } - return false; + return false } - @Override - public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("id"); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - addIntPropertySafely(jsonAttributes, "id", remoteId); - return true; + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["id"] + if (id != null && !id.isJsonNull && id.asString == localId) { + addIntPropertySafely(jsonAttributes, "id", remoteId) + return true } - return false; + return false } } From 5d5048dea310395e1a2416417f95ccde66dd386b Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 13:10:45 +0300 Subject: [PATCH 19/65] Reformat ImageBlockProcessor after conversion --- .../ImageBlockProcessor.kt | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt index f7a3d4fbf5f2..5165596d47f1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/ImageBlockProcessor.kt @@ -4,33 +4,31 @@ import com.google.gson.JsonObject import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile -class ImageBlockProcessor(localId: String, mediaFile: MediaFile) : - BlockProcessor(localId, mediaFile) { +class ImageBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { override fun processBlockContentDocument(document: Document): Boolean { // select image element with our local id val targetImg = document.select("img").first() // if a match is found, proceed with replacement - if (targetImg != null) { + return targetImg?.let { // replace attributes - targetImg.attr("src", remoteUrl) + it.attr("src", remoteUrl) // replace class - targetImg.removeClass("wp-image-$localId") - targetImg.addClass("wp-image-$remoteId") + it.removeClass("wp-image-$localId") + it.addClass("wp-image-$remoteId") - return true - } - - return false + true + } ?: false } override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { val id = jsonAttributes["id"] - if (id != null && !id.isJsonNull && id.asString == localId) { + return if (id != null && !id.isJsonNull && id.asString == localId) { addIntPropertySafely(jsonAttributes, "id", remoteId) - return true + true + } else { + false } - return false } } From 9929f5c0b5934bce860e7488a122b23bb87bb8e7 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 13:16:06 +0300 Subject: [PATCH 20/65] Rename .java to .kt --- .../{MediaTextBlockProcessor.java => MediaTextBlockProcessor.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/{MediaTextBlockProcessor.java => MediaTextBlockProcessor.kt} (100%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt similarity index 100% rename from WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.java rename to WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt From 78426b0ef5dd964ceb1b3eca7867a953dd221b6e Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 13:16:06 +0300 Subject: [PATCH 21/65] Convert MediaTextBlockProcessor to Kotlin --- .../MediaTextBlockProcessor.kt | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt index 0bdbccfe74d9..06f13d6242ed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt @@ -1,60 +1,50 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors -import androidx.annotation.NonNull; +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - -public class MediaTextBlockProcessor extends BlockProcessor { - public MediaTextBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile) { - super(localId, mediaFile); - } - - @Override - public boolean processBlockContentDocument(@NonNull Document document) { +class MediaTextBlockProcessor(localId: String, mediaFile: MediaFile) : + BlockProcessor(localId, mediaFile) { + override fun processBlockContentDocument(document: Document): Boolean { // select image element with our local id - Element targetImg = document.select("img").first(); + val targetImg = document.select("img").first() // if a match is found for img, proceed with replacement if (targetImg != null) { // replace attributes - targetImg.attr("src", remoteUrl); + targetImg.attr("src", remoteUrl) // replace class - targetImg.removeClass("wp-image-" + localId); - targetImg.addClass("wp-image-" + remoteId); + targetImg.removeClass("wp-image-$localId") + targetImg.addClass("wp-image-$remoteId") // return injected block - return true; + return true } else { // try video // select video element with our local id - Element targetVideo = document.select("video").first(); + val targetVideo = document.select("video").first() // if a match is found for video, proceed with replacement if (targetVideo != null) { // replace attribute - targetVideo.attr("src", remoteUrl); + targetVideo.attr("src", remoteUrl) // return injected block - return true; + return true } } - return false; + return false } - @Override - public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("mediaId"); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - addIntPropertySafely(jsonAttributes, "mediaId", remoteId); - return true; + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["mediaId"] + if (id != null && !id.isJsonNull && id.asString == localId) { + addIntPropertySafely(jsonAttributes, "mediaId", remoteId) + return true } - return false; + return false } } From 31798bfcadec830ec5413de8f93ad5d1d75ada91 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 18:54:03 +0300 Subject: [PATCH 22/65] Reformat MediaTextBlockProcessor after conversion --- .../MediaTextBlockProcessor.kt | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt index 06f13d6242ed..810c59709c54 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/MediaTextBlockProcessor.kt @@ -4,14 +4,13 @@ import com.google.gson.JsonObject import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile -class MediaTextBlockProcessor(localId: String, mediaFile: MediaFile) : - BlockProcessor(localId, mediaFile) { +class MediaTextBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { override fun processBlockContentDocument(document: Document): Boolean { // select image element with our local id val targetImg = document.select("img").first() // if a match is found for img, proceed with replacement - if (targetImg != null) { + return if (targetImg != null) { // replace attributes targetImg.attr("src", remoteUrl) @@ -20,31 +19,29 @@ class MediaTextBlockProcessor(localId: String, mediaFile: MediaFile) : targetImg.addClass("wp-image-$remoteId") // return injected block - return true + true } else { // try video // select video element with our local id val targetVideo = document.select("video").first() // if a match is found for video, proceed with replacement - if (targetVideo != null) { + targetVideo?.let { // replace attribute targetVideo.attr("src", remoteUrl) // return injected block - return true - } + true + } ?: false } - - return false } override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { val id = jsonAttributes["mediaId"] - if (id != null && !id.isJsonNull && id.asString == localId) { + return if (id != null && !id.isJsonNull && id.asString == localId) { addIntPropertySafely(jsonAttributes, "mediaId", remoteId) - return true + true + } else { + false } - - return false } } From ae9d19a9e378116e7c3fde9f83073274ed775469 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 19:22:54 +0300 Subject: [PATCH 23/65] Rename .java to .kt --- .../{VideoBlockProcessor.java => VideoBlockProcessor.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/{VideoBlockProcessor.java => VideoBlockProcessor.kt} (100%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt similarity index 100% rename from WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.java rename to WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt From e639e666d0273d7d8ced5313fe474e57dc8045cf Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 19:22:54 +0300 Subject: [PATCH 24/65] Convert VideoBlockProcessor to Kotlin --- .../VideoBlockProcessor.kt | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt index dc290b33a8f3..0cb09265af2d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt @@ -1,43 +1,33 @@ -package org.wordpress.android.ui.posts.mediauploadcompletionprocessors; +package org.wordpress.android.ui.posts.mediauploadcompletionprocessors -import androidx.annotation.NonNull; +import com.google.gson.JsonObject +import org.jsoup.nodes.Document +import org.wordpress.android.util.helpers.MediaFile -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.wordpress.android.util.helpers.MediaFile; - -public class VideoBlockProcessor extends BlockProcessor { - public VideoBlockProcessor(@NonNull String localId, @NonNull MediaFile mediaFile) { - super(localId, mediaFile); - } - - @Override - public boolean processBlockContentDocument(@NonNull Document document) { +class VideoBlockProcessor(localId: String, mediaFile: MediaFile) : + BlockProcessor(localId, mediaFile) { + override fun processBlockContentDocument(document: Document): Boolean { // select video element with our local id - Element targetVideo = document.select("video").first(); + val targetVideo = document.select("video").first() // if a match is found for video, proceed with replacement if (targetVideo != null) { // replace attribute - targetVideo.attr("src", remoteUrl); + targetVideo.attr("src", remoteUrl) // return injected block - return true; + return true } - return false; + return false } - @Override - public boolean processBlockJsonAttributes(@NonNull JsonObject jsonAttributes) { - JsonElement id = jsonAttributes.get("id"); - if (id != null && !id.isJsonNull() && id.getAsString().equals(localId)) { - addIntPropertySafely(jsonAttributes, "id", remoteId); - return true; + override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { + val id = jsonAttributes["id"] + if (id != null && !id.isJsonNull && id.asString == localId) { + addIntPropertySafely(jsonAttributes, "id", remoteId) + return true } - return false; + return false } } From 4d4abeb1eda291e92389dbea8e019b1470acfdaa Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 10 Jun 2024 19:24:00 +0300 Subject: [PATCH 25/65] Reformat VideoBlockProcessor after conversion --- .../VideoBlockProcessor.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt index 0cb09265af2d..4ede032a5b20 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/mediauploadcompletionprocessors/VideoBlockProcessor.kt @@ -4,30 +4,28 @@ import com.google.gson.JsonObject import org.jsoup.nodes.Document import org.wordpress.android.util.helpers.MediaFile -class VideoBlockProcessor(localId: String, mediaFile: MediaFile) : - BlockProcessor(localId, mediaFile) { +class VideoBlockProcessor(localId: String, mediaFile: MediaFile) : BlockProcessor(localId, mediaFile) { override fun processBlockContentDocument(document: Document): Boolean { // select video element with our local id val targetVideo = document.select("video").first() // if a match is found for video, proceed with replacement - if (targetVideo != null) { + return targetVideo?.let { // replace attribute targetVideo.attr("src", remoteUrl) // return injected block - return true - } - - return false + true + } ?: false } override fun processBlockJsonAttributes(jsonAttributes: JsonObject): Boolean { val id = jsonAttributes["id"] - if (id != null && !id.isJsonNull && id.asString == localId) { + return if (id != null && !id.isJsonNull && id.asString == localId) { addIntPropertySafely(jsonAttributes, "id", remoteId) - return true + true + } else { + false } - return false } } From 340027896c0d4e2417abfca891039ac38e5aca92 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Thu, 6 Jun 2024 17:29:02 +0530 Subject: [PATCH 26/65] Migrated `PostSettingsInputDialogFragment` to ViewBinding --- .../PostSettingsInputDialogFragment.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java index af749db0878c..981a32338872 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java @@ -8,9 +8,6 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; -import android.view.View; -import android.widget.EditText; -import android.widget.TextView; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -18,11 +15,12 @@ import androidx.fragment.app.DialogFragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.textfield.TextInputLayout; import org.wordpress.android.R; +import org.wordpress.android.databinding.PostSettingsInputDialogBinding; import org.wordpress.android.util.ActivityUtils; + public class PostSettingsInputDialogFragment extends DialogFragment implements TextWatcher { public static final String TAG = "post_settings_input_dialog_fragment"; @@ -35,6 +33,7 @@ public interface PostSettingsInputDialogListener { private static final String HINT_TAG = "hint"; private static final String DISABLE_EMPTY_INPUT_TAG = "disable_empty_input"; private static final String MULTILINE_INPUT_TAG = "is_multiline_input"; + private String mCurrentInput; private String mTitle; private String mHint; @@ -94,34 +93,32 @@ public void onDismiss(DialogInterface dialog) { public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new MaterialAlertDialogBuilder(new ContextThemeWrapper(getActivity(), R.style.PostSettingsTheme)); - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + LayoutInflater layoutInflater = requireActivity().getLayoutInflater(); //noinspection InflateParams - View dialogView = layoutInflater.inflate(R.layout.post_settings_input_dialog, null); - builder.setView(dialogView); - final EditText editText = dialogView.findViewById(R.id.post_settings_input_dialog_edit_text); + PostSettingsInputDialogBinding mBinding = + PostSettingsInputDialogBinding.inflate(layoutInflater, null, false); + builder.setView(mBinding.getRoot()); if (mIsMultilineInput) { - editText.setRawInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); + mBinding.postSettingsInputDialogEditText.setRawInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); } else { - editText.setInputType(InputType.TYPE_CLASS_TEXT); + mBinding.postSettingsInputDialogEditText.setInputType(InputType.TYPE_CLASS_TEXT); } if (!TextUtils.isEmpty(mCurrentInput)) { - editText.setText(mCurrentInput); + mBinding.postSettingsInputDialogEditText.setText(mCurrentInput); // move the cursor to the end - editText.setSelection(mCurrentInput.length()); + mBinding.postSettingsInputDialogEditText.setSelection(mCurrentInput.length()); } - editText.addTextChangedListener(this); + mBinding.postSettingsInputDialogEditText.addTextChangedListener(this); - TextInputLayout textInputLayout = dialogView.findViewById(R.id.post_settings_input_dialog_input_layout); - textInputLayout.setHint(mTitle); + mBinding.postSettingsInputDialogInputLayout.setHint(mTitle); - TextView hintTextView = dialogView.findViewById(R.id.post_settings_input_dialog_hint); - hintTextView.setText(mHint); + mBinding.postSettingsInputDialogHint.setText(mHint); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mCurrentInput = editText.getText().toString(); + mCurrentInput = mBinding.postSettingsInputDialogEditText.getText().toString(); if (mListener != null) { mListener.onInputUpdated(mCurrentInput); } From 6a13711360a84f9a1bdd1c3aadd1a6680960fa29 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Thu, 13 Jun 2024 16:14:17 +0530 Subject: [PATCH 27/65] Redundant : Extra Line Commit Removed --- .../android/ui/posts/PostSettingsInputDialogFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java index 981a32338872..be35b4896aec 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java @@ -20,7 +20,6 @@ import org.wordpress.android.databinding.PostSettingsInputDialogBinding; import org.wordpress.android.util.ActivityUtils; - public class PostSettingsInputDialogFragment extends DialogFragment implements TextWatcher { public static final String TAG = "post_settings_input_dialog_fragment"; From a2b5915e9b3689fbdc509e787e8a692c7b49ea28 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:03:32 -0400 Subject: [PATCH 28/65] Add helper for logging and tracking --- .../ui/voicetocontent/VoiceToContentLogger.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt new file mode 100644 index 000000000000..d0e9e4ea2424 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt @@ -0,0 +1,24 @@ +package org.wordpress.android.ui.voicetocontent + +import org.wordpress.android.analytics.AnalyticsTracker.Stat +import org.wordpress.android.fluxc.utils.AppLogWrapper +import org.wordpress.android.util.AppLog.T.POSTS +import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import javax.inject.Inject + +class VoiceToContentLogger @Inject constructor( + private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, + private val appLogWrapper: AppLogWrapper +) { + fun track(stat: Stat) { + analyticsTrackerWrapper.track(stat) + } + + fun track(stat: Stat, properties: Map) { + analyticsTrackerWrapper.track(stat, properties) + } + + fun logError(message: String) { + appLogWrapper.e(POSTS, "Voice to content $message") + } +} From eaf6b088c5554bb9bb7abc7d6f7bb26b30b80417 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:04:03 -0400 Subject: [PATCH 29/65] Add error resources --- WordPress/src/main/res/values/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 390b9124caa2..1ce386b333cc 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -4900,7 +4900,7 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> Clear selected color Link label Tap to edit - + Audio Recording Permission Required To record audio, this app needs permission to access your microphone. You have previously denied this permission. Please enable the microphone permission in the app settings to use this feature. @@ -4914,4 +4914,5 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> You don\'t have enough requests available to create a post from audio. Upgrade for more requests Done + Post from audio is unavailable at the moment, please try again later. From 558ba026b42a171719aa4f72b093c017cbae3345 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:04:43 -0400 Subject: [PATCH 30/65] Add error ui model-state --- .../android/ui/voicetocontent/VoiceToContentUiState.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt index d8d23fa27a7b..9fb2ca350065 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt @@ -32,6 +32,12 @@ data class RecordingPanelUIModel( @StringRes val actionLabel: Int ) +data class ErrorUiModel( + @StringRes val errorMessage: Int? = null, + val allowRetry: Boolean = false, + val onRetryTap: (() -> Unit)? = null +) + enum class VoiceToContentUIStateType(val trackingName: String) { INITIALIZING("initializing"), READY_TO_RECORD("ready_to_record"), @@ -45,5 +51,6 @@ data class VoiceToContentUiState( val uiStateType: VoiceToContentUIStateType, val header: HeaderUIModel, val secondaryHeader: SecondaryHeaderUIModel? = null, - val recordingPanel: RecordingPanelUIModel? = null + val recordingPanel: RecordingPanelUIModel? = null, + val errorPanel: ErrorUiModel? = null ) From 225a3f02597d772822e565dd9a510daf44957f89 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:05:26 -0400 Subject: [PATCH 31/65] Capture and log errors; return result failure --- .../PrepareVoiceToContentUseCase.kt | 21 ++++++-- .../voicetocontent/VoiceToContentUseCase.kt | 52 ++++++++++++------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt index 487eea60d28c..74508277149c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt @@ -6,19 +6,29 @@ import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.jetpackai.JetpackAIAssistantFeature import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAIAssistantFeatureResponse import org.wordpress.android.fluxc.store.jetpackai.JetpackAIStore +import org.wordpress.android.ui.voicetocontent.PrepareVoiceToContentResult.Success +import org.wordpress.android.ui.voicetocontent.PrepareVoiceToContentResult.Failure.NetworkUnavailable +import org.wordpress.android.ui.voicetocontent.PrepareVoiceToContentResult.Failure.RemoteRequestFailure +import org.wordpress.android.util.NetworkUtilsWrapper import javax.inject.Inject class PrepareVoiceToContentUseCase @Inject constructor( - private val jetpackAIStore: JetpackAIStore + private val jetpackAIStore: JetpackAIStore, + private val networkUtilsWrapper: NetworkUtilsWrapper, + private val logger: VoiceToContentLogger ) { suspend fun execute(site: SiteModel): PrepareVoiceToContentResult = withContext(Dispatchers.IO) { + if (!networkUtilsWrapper.isNetworkAvailable()) { + return@withContext NetworkUnavailable + } when (val response = jetpackAIStore.fetchJetpackAIAssistantFeature(site)) { is JetpackAIAssistantFeatureResponse.Success -> { - PrepareVoiceToContentResult.Success(model = response.model) + Success(model = response.model) } is JetpackAIAssistantFeatureResponse.Error -> { - PrepareVoiceToContentResult.Error + logger.logError("${response.type.name} - ${response.message}") + RemoteRequestFailure } } } @@ -26,5 +36,8 @@ class PrepareVoiceToContentUseCase @Inject constructor( sealed class PrepareVoiceToContentResult { data class Success(val model: JetpackAIAssistantFeature) : PrepareVoiceToContentResult() - data object Error : PrepareVoiceToContentResult() + sealed class Failure: PrepareVoiceToContentResult() { + data object NetworkUnavailable: Failure() + data object RemoteRequestFailure: Failure() + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt index 88edbf2e5f60..87e583278743 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt @@ -1,17 +1,22 @@ package org.wordpress.android.ui.voicetocontent -import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAIQueryResponse import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAITranscriptionResponse import org.wordpress.android.fluxc.store.jetpackai.JetpackAIStore +import org.wordpress.android.util.NetworkUtilsWrapper +import org.wordpress.android.ui.voicetocontent.VoiceToContentResult.Failure.NetworkUnavailable +import org.wordpress.android.ui.voicetocontent.VoiceToContentResult.Failure.RemoteRequestFailure +import org.wordpress.android.ui.voicetocontent.VoiceToContentResult.Success import java.io.File import javax.inject.Inject class VoiceToContentUseCase @Inject constructor( - private val jetpackAIStore: JetpackAIStore + private val jetpackAIStore: JetpackAIStore, + private val networkUtilsWrapper: NetworkUtilsWrapper, + private val logger: VoiceToContentLogger ) { companion object { const val FEATURE = "voice_to_content" @@ -25,6 +30,10 @@ class VoiceToContentUseCase @Inject constructor( file: File ): VoiceToContentResult = withContext(Dispatchers.IO) { + if (!networkUtilsWrapper.isNetworkAvailable()) { + return@withContext NetworkUnavailable + } + val transcriptionResponse = jetpackAIStore.fetchJetpackAITranscription( siteModel, FEATURE, @@ -36,21 +45,17 @@ class VoiceToContentUseCase @Inject constructor( transcriptionResponse.model } is JetpackAITranscriptionResponse.Error -> { - val message = "${transcriptionResponse.type} ${transcriptionResponse.message}" - Log.i( - javaClass.simpleName, - "Error transcribing audio file: $message" - ) + logger.logError("${transcriptionResponse.type} ${transcriptionResponse.message}") null } } - transcribedText?.let { + transcribedText?.let { transcribed -> val response = jetpackAIStore.fetchJetpackAIQuery( site = siteModel, feature = FEATURE, role = ROLE, - message = it, + message = transcribed, stream = false, type = TYPE ) @@ -61,22 +66,33 @@ class VoiceToContentUseCase @Inject constructor( // __JETPACK_AI_ERROR__ is a special marker we ask GPT to add to the request when it can’t // understand the request for any reason, so maybe something confused GPT on some requests. if (finalContent == JETPACK_AI_ERROR) { - return@withContext VoiceToContentResult(isError = true) + // Send back the transcribed text here + logger.logError(JETPACK_AI_ERROR) + return@withContext Success(content = transcribed) } else { - return@withContext VoiceToContentResult(content = response.choices[0].message.content) + return@withContext Success(content = response.choices[0].message.content) } } is JetpackAIQueryResponse.Error -> { - return@withContext VoiceToContentResult(isError = true) + logger.logError("${response.type.name} - ${response.message}") + return@withContext Success(content = transcribed) } } - } ?:return@withContext VoiceToContentResult(isError = true) + } ?: run { + logger.logError("Unable to transcribe audio content") + return@withContext RemoteRequestFailure + } } } -// todo: build out the result object -data class VoiceToContentResult( - val content: String? = null, - val isError: Boolean = false -) +sealed class VoiceToContentResult { + data class Success( + val content: String + ): VoiceToContentResult() + + sealed class Failure: VoiceToContentResult() { + data object NetworkUnavailable: Failure() + data object RemoteRequestFailure: Failure() + } +} From 6b3988c7c5b8e8c0d417a7012f6a60a69eca8585 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:06:09 -0400 Subject: [PATCH 32/65] Add transitionToError for result failures, support ErrorUiModel, Log errors from audio --- .../voicetocontent/VoiceToContentViewModel.kt | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt index 58c55824c4e9..102ef927edda 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt @@ -40,7 +40,8 @@ class VoiceToContentViewModel @Inject constructor( private val selectedSiteRepository: SelectedSiteRepository, private val recordingUseCase: RecordingUseCase, private val contextProvider: ContextProvider, - private val prepareVoiceToContentUseCase: PrepareVoiceToContentUseCase + private val prepareVoiceToContentUseCase: PrepareVoiceToContentUseCase, + private val logger: VoiceToContentLogger ) : ScopedViewModel(mainDispatcher) { private val _requestPermission = MutableLiveData() val requestPermission = _requestPermission as LiveData @@ -84,8 +85,8 @@ class VoiceToContentViewModel @Inject constructor( transitionToReadyToRecordOrIneligibleForFeature(result.model) } - is PrepareVoiceToContentResult.Error -> { - transitionToError() + is PrepareVoiceToContentResult.Failure -> { + result.transitionToError() } } } @@ -122,11 +123,12 @@ class VoiceToContentViewModel @Inject constructor( file?.let { executeVoiceToContent(it) } ?: run { - transitionToError() + logger.logError("$VOICE_TO_CONTENT - unable to access audio file") + transitionToError(GenericFailureMsg) } } is Error -> { - transitionToError() + audioRecorderResult.transitionToError() } } } @@ -149,13 +151,16 @@ class VoiceToContentViewModel @Inject constructor( // Workflow private fun executeVoiceToContent(file: File) { val site = selectedSiteRepository.getSelectedSite() ?: run { - transitionToError() + transitionToError(GenericFailureMsg) return } viewModelScope.launch { - val result = voiceToContentUseCase.execute(site, file) - Log.i(javaClass.simpleName, "***=> result is ${result.content}") + when (val result = voiceToContentUseCase.execute(site, file)) { + is VoiceToContentResult.Failure -> result.transitionToError() + is VoiceToContentResult.Success -> + Log.i(javaClass.simpleName, "***=> result is ${result.content}") + } _dismiss.postValue(Unit) } } @@ -191,7 +196,30 @@ class VoiceToContentViewModel @Inject constructor( _dismiss.postValue(Unit) } + private fun onRetryTap() { + transitionToInitializing() + start() + } + // transitions + private fun transitionToInitializing() { + _state.value = VoiceToContentUiState( + uiStateType = INITIALIZING, + header = HeaderUIModel( + label = R.string.voice_to_content_base_header_label, + onClose = ::onClose), + secondaryHeader = SecondaryHeaderUIModel( + label = R.string.voice_to_content_secondary_header_label, + isLabelVisible = true, + isProgressIndicatorVisible = true, + isTimeElapsedVisible = false), + recordingPanel = RecordingPanelUIModel( + actionLabel = R.string.voice_to_content_begin_recording_label, + isEnabled = false), + errorPanel = null + ) + } + private fun transitionToReadyToRecordOrIneligibleForFeature(model: JetpackAIAssistantFeature) { val isEligibleForFeature = voiceToContentFeatureUtils.isEligibleForVoiceToContent(model) val requestsAvailable = voiceToContentFeatureUtils.getRequestLimit(model) @@ -239,15 +267,40 @@ class VoiceToContentViewModel @Inject constructor( ) } - // todo: annmarie - transition to error hasn't been fully fleshed out - private fun transitionToError() { + private fun VoiceToContentResult.Failure.transitionToError() { + when (this) { + VoiceToContentResult.Failure.NetworkUnavailable -> transitionToError(NetworkUnavailableMsg, true) + VoiceToContentResult.Failure.RemoteRequestFailure -> transitionToError(GenericFailureMsg) + } + } + + private fun PrepareVoiceToContentResult.Failure.transitionToError() { + when (this) { + PrepareVoiceToContentResult.Failure.NetworkUnavailable -> transitionToError(NetworkUnavailableMsg, true) + PrepareVoiceToContentResult.Failure.RemoteRequestFailure -> transitionToError(GenericFailureMsg) + } + } + + private fun Error.transitionToError() { + logger.logError("$VOICE_TO_CONTENT - ${this.errorMessage}") + transitionToError(GenericFailureMsg) + } + + private fun transitionToError(errorMessage: Int, allowRetry: Boolean = false) { val currentState = _state.value _state.value = currentState.copy( uiStateType = ERROR, header = currentState.header.copy( label = R.string.voice_to_content_error_label), secondaryHeader = null, - recordingPanel = null + recordingPanel = null, + errorPanel = ErrorUiModel(errorMessage = errorMessage, allowRetry = allowRetry, onRetryTap = ::onRetryTap) ) } + + companion object { + private val NetworkUnavailableMsg = R.string.error_network_connection + private val GenericFailureMsg = R.string.voice_to_content_generic_error + private const val VOICE_TO_CONTENT = "Voice to content" + } } From d94a6091f51dd3773724ff647af9f774b9a38ec4 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:06:39 -0400 Subject: [PATCH 33/65] Update error view with message and retry icon --- .../android/ui/voicetocontent/VoiceToContentScreen.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt index e2f3fe49a760..3ae4dd42e18c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt @@ -24,6 +24,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -117,7 +118,12 @@ fun ErrorView(model: VoiceToContentUiState) { ) { Header(model.header) Spacer(modifier = Modifier.height(16.dp)) - Text("Unable to use Voice to Content at the moment, please try again later") + Text(stringResource(id = model.errorPanel?.errorMessage?:R.string.voice_to_content_generic_error)) + if (model.errorPanel?.allowRetry == true) { + IconButton(onClick = model.errorPanel.onRetryTap?:{}) { + Icon(imageVector = Icons.Default.Refresh, contentDescription = null) + } + } } } From bb4f0b32e2b08f4d6439186f274c0e1ebd0329f0 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:38:24 -0400 Subject: [PATCH 34/65] Add function for launching the browser to update plan for AI credits --- .../java/org/wordpress/android/ui/ActivityNavigator.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt b/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt index f5a2ee3a87cf..235a0be1e594 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityNavigator.kt @@ -201,4 +201,11 @@ class ActivityNavigator @Inject constructor() { .addNextIntent(intent) .startActivities() } + + fun openIneligibleForVoiceToContent( + context: Context, + url: String + ) { + WPWebViewActivity.openUrlByUsingGlobalWPCOMCredentials(context, url) + } } From df56977dafdef0dfe40af88e1425733edca166bd Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:38:45 -0400 Subject: [PATCH 35/65] Add voice-to-content tracker values to the enum --- .../org/wordpress/android/analytics/AnalyticsTracker.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 51034ad16777..49990e4c44cb 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -1140,7 +1140,13 @@ public enum Stat { IN_APP_UPDATE_SHOWN, IN_APP_UPDATE_DISMISSED, IN_APP_UPDATE_ACCEPTED, - IN_APP_UPDATE_COMPLETED_WITH_APP_RESTART_BY_USER; + IN_APP_UPDATE_COMPLETED_WITH_APP_RESTART_BY_USER, + VOICE_TO_CONTENT_SHEET_SHOWN, + VOICE_TO_CONTENT_BUTTON_START_RECORDING_TAPPED, + VOICE_TO_CONTENT_BUTTON_DONE_TAPPED, + VOICE_TO_CONTENT_BUTTON_UPGRADE_TAPPED, + VOICE_TO_CONTENT_BUTTON_CLOSE_TAPPED, + VOICE_TO_CONTENT_BUTTON_RECORDING_LIMIT_REACHED; /* * Please set the event name in the enum only if the new Stat's name in lower case does not match it. From 0cfeb4fe37e7666df94b5d44a1f2a4d0c1146b3b Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:39:08 -0400 Subject: [PATCH 36/65] Rename to reflect this class does both logging and tracking --- .../{VoiceToContentLogger.kt => VoiceToContentTelemetry.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/{VoiceToContentLogger.kt => VoiceToContentTelemetry.kt} (93%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentTelemetry.kt similarity index 93% rename from WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt rename to WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentTelemetry.kt index d0e9e4ea2424..cf6522c74348 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentLogger.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentTelemetry.kt @@ -6,7 +6,7 @@ import org.wordpress.android.util.AppLog.T.POSTS import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import javax.inject.Inject -class VoiceToContentLogger @Inject constructor( +class VoiceToContentTelemetry @Inject constructor( private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, private val appLogWrapper: AppLogWrapper ) { From 788d6f6c962ca49e99a4f880cd265ec80512bfec Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:39:32 -0400 Subject: [PATCH 37/65] Update to reflect name change of logger --- .../android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt | 2 +- .../android/ui/voicetocontent/VoiceToContentUseCase.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt index 74508277149c..2ad2c5bd5251 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/PrepareVoiceToContentUseCase.kt @@ -15,7 +15,7 @@ import javax.inject.Inject class PrepareVoiceToContentUseCase @Inject constructor( private val jetpackAIStore: JetpackAIStore, private val networkUtilsWrapper: NetworkUtilsWrapper, - private val logger: VoiceToContentLogger + private val logger: VoiceToContentTelemetry ) { suspend fun execute(site: SiteModel): PrepareVoiceToContentResult = withContext(Dispatchers.IO) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt index 87e583278743..118ff698e7dc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUseCase.kt @@ -16,7 +16,7 @@ import javax.inject.Inject class VoiceToContentUseCase @Inject constructor( private val jetpackAIStore: JetpackAIStore, private val networkUtilsWrapper: NetworkUtilsWrapper, - private val logger: VoiceToContentLogger + private val logger: VoiceToContentTelemetry ) { companion object { const val FEATURE = "voice_to_content" From a1a106315d9ba28cd3e8b6df4865bf7a46411c1a Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:40:12 -0400 Subject: [PATCH 38/65] Add track events and hook in the upgrade link tapped --- .../voicetocontent/VoiceToContentViewModel.kt | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt index 102ef927edda..56af82ddac9d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt @@ -13,24 +13,24 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.wordpress.android.R +import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.model.jetpackai.JetpackAIAssistantFeature import org.wordpress.android.modules.UI_THREAD import org.wordpress.android.ui.mysite.SelectedSiteRepository -import org.wordpress.android.util.audio.IAudioRecorder -import org.wordpress.android.viewmodel.ContextProvider -import org.wordpress.android.viewmodel.ScopedViewModel -import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.INITIALIZING -import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.READY_TO_RECORD -import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.RECORDING import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.ERROR import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.INELIGIBLE_FOR_FEATURE +import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.INITIALIZING import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.PROCESSING - +import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.READY_TO_RECORD +import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.RECORDING +import org.wordpress.android.util.audio.IAudioRecorder +import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error +import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success +import org.wordpress.android.viewmodel.ContextProvider +import org.wordpress.android.viewmodel.ScopedViewModel import java.io.File import javax.inject.Inject import javax.inject.Named -import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success -import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error @HiltViewModel class VoiceToContentViewModel @Inject constructor( @@ -41,7 +41,7 @@ class VoiceToContentViewModel @Inject constructor( private val recordingUseCase: RecordingUseCase, private val contextProvider: ContextProvider, private val prepareVoiceToContentUseCase: PrepareVoiceToContentUseCase, - private val logger: VoiceToContentLogger + private val logger: VoiceToContentTelemetry ) : ScopedViewModel(mainDispatcher) { private val _requestPermission = MutableLiveData() val requestPermission = _requestPermission as LiveData @@ -52,6 +52,11 @@ class VoiceToContentViewModel @Inject constructor( private val _amplitudes = MutableLiveData>() val amplitudes: LiveData> get() = _amplitudes + private val _onIneligibleForVoiceToContent = MutableLiveData() + val onIneligibleForVoiceToContent = _onIneligibleForVoiceToContent as LiveData + + private var isStarted = false + private val _state = MutableStateFlow(VoiceToContentUiState( uiStateType = INITIALIZING, header = HeaderUIModel( @@ -79,6 +84,10 @@ class VoiceToContentViewModel @Inject constructor( val site = selectedSiteRepository.getSelectedSite() if (site == null || !isVoiceToContentEnabled()) return + if (!isStarted) { + logger.track(Stat.VOICE_TO_CONTENT_SHEET_SHOWN) + } + viewModelScope.launch { when (val result = prepareVoiceToContentUseCase.execute(site)) { is PrepareVoiceToContentResult.Success -> { @@ -90,6 +99,8 @@ class VoiceToContentViewModel @Inject constructor( } } } + + isStarted = true } // Recording @@ -167,6 +178,7 @@ class VoiceToContentViewModel @Inject constructor( // Permissions private fun onRequestPermission() { + logger.track(Stat.VOICE_TO_CONTENT_BUTTON_START_RECORDING_TAPPED) _requestPermission.postValue(Unit) } @@ -185,14 +197,17 @@ class VoiceToContentViewModel @Inject constructor( // user actions private fun onMicTap() { + logger.track(Stat.VOICE_TO_CONTENT_BUTTON_START_RECORDING_TAPPED) startRecording() } private fun onStopTap() { + logger.track(Stat.VOICE_TO_CONTENT_BUTTON_DONE_TAPPED) stopRecording() } private fun onClose() { + logger.track(Stat.VOICE_TO_CONTENT_BUTTON_CLOSE_TAPPED) _dismiss.postValue(Unit) } @@ -201,6 +216,13 @@ class VoiceToContentViewModel @Inject constructor( start() } + private fun onLinkTap(url: String?) { + logger.track(Stat.VOICE_TO_CONTENT_BUTTON_UPGRADE_TAPPED) + url?.let { + _onIneligibleForVoiceToContent.postValue(it) + } + } + // transitions private fun transitionToInitializing() { _state.value = VoiceToContentUiState( @@ -222,6 +244,9 @@ class VoiceToContentViewModel @Inject constructor( private fun transitionToReadyToRecordOrIneligibleForFeature(model: JetpackAIAssistantFeature) { val isEligibleForFeature = voiceToContentFeatureUtils.isEligibleForVoiceToContent(model) + if (!isEligibleForFeature) { + logger.track(Stat.VOICE_TO_CONTENT_BUTTON_RECORDING_LIMIT_REACHED) + } val requestsAvailable = voiceToContentFeatureUtils.getRequestLimit(model) val currentState = _state.value _state.value = currentState.copy( @@ -236,7 +261,8 @@ class VoiceToContentViewModel @Inject constructor( onMicTap = ::onMicTap, onRequestPermission = ::onRequestPermission, hasPermission = hasAllPermissionsForRecording(), - upgradeUrl = model.upgradeUrl + upgradeUrl = model.upgradeUrl, + onLinkTap = ::onLinkTap ) ) } From e1c3b3a645b86b82e155b999ae0408da0ff2c028 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:40:40 -0400 Subject: [PATCH 39/65] Observe onIneligibleForVoiceToContent events and launch upgrade link --- .../VoiceToContentDialogFragment.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt index b257f0ac7206..b804a6cb3311 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt @@ -17,9 +17,14 @@ import org.wordpress.android.R import org.wordpress.android.util.audio.IAudioRecorder.Companion.REQUIRED_RECORDING_PERMISSIONS import android.provider.Settings import androidx.compose.material.ExperimentalMaterialApi +import org.wordpress.android.ui.ActivityNavigator +import javax.inject.Inject @AndroidEntryPoint class VoiceToContentDialogFragment : BottomSheetDialogFragment() { + @Inject + lateinit var activityNavigator: ActivityNavigator + private val viewModel: VoiceToContentViewModel by viewModels() @ExperimentalMaterialApi @@ -49,6 +54,10 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() { viewModel.dismiss.observe(viewLifecycleOwner) { dismiss() } + + viewModel.onIneligibleForVoiceToContent.observe(viewLifecycleOwner) { url -> + launchIneligibleForVoiceToContent(url) + } } private val requestMultiplePermissionsLauncher = registerForActivityResult( @@ -84,6 +93,12 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() { .show() } + private fun launchIneligibleForVoiceToContent(url: String) { + context?.let { + activityNavigator.openIneligibleForVoiceToContent(it, url) + } + } + companion object { const val TAG = "voice_to_content_fragment_tag" From 028cb1e243ddc2c57abb5f393aaa00ecba2324b3 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 11:57:28 -0400 Subject: [PATCH 40/65] Update new parameter --- .../ui/voicetocontent/VoiceToContentViewModelTest.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModelTest.kt index c437637ce5ac..f6392456f15f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModelTest.kt @@ -32,6 +32,9 @@ class VoiceToContentViewModelTest : BaseUnitTest() { @Mock lateinit var contextProvider: ContextProvider + @Mock + lateinit var telemtry: VoiceToContentTelemetry + private lateinit var viewModel: VoiceToContentViewModel // private var uiStateChanges = mutableListOf() @@ -77,7 +80,8 @@ class VoiceToContentViewModelTest : BaseUnitTest() { selectedSiteRepository, recordingUseCase, contextProvider, - prepareVoiceToContentUseCase + prepareVoiceToContentUseCase, + telemtry ) } From 595a4c1dabc7c39c4681114ef5d221a0e86c1f21 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 15:24:36 -0400 Subject: [PATCH 41/65] Make the bottom sheet scrollable to support landscape viewing --- .../VoiceToContentDialogFragment.kt | 24 +++++++++++++++++++ .../ui/voicetocontent/VoiceToContentScreen.kt | 21 ++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt index b804a6cb3311..ff491318e2bc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt @@ -1,5 +1,7 @@ package org.wordpress.android.ui.voicetocontent +import android.annotation.SuppressLint +import android.app.Dialog import android.content.Intent import android.net.Uri import android.os.Bundle @@ -16,7 +18,10 @@ import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.R import org.wordpress.android.util.audio.IAudioRecorder.Companion.REQUIRED_RECORDING_PERMISSIONS import android.provider.Settings +import android.widget.FrameLayout import androidx.compose.material.ExperimentalMaterialApi +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import org.wordpress.android.ui.ActivityNavigator import javax.inject.Inject @@ -46,6 +51,25 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() { viewModel.start() } + @SuppressLint("ClickableViewAccessibility") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog + dialog.setOnShowListener { + val bottomSheet: FrameLayout = dialog.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) ?: return@setOnShowListener + + val behavior = BottomSheetBehavior.from(bottomSheet) + behavior.isDraggable = true + behavior.skipCollapsed = true + behavior.state = BottomSheetBehavior.STATE_EXPANDED + + // Disable touch interception by the bottom sheet to allow nested scrolling + bottomSheet.setOnTouchListener { _, _ -> false } + } + return dialog + } + private fun observeViewModel() { viewModel.requestPermission.observe(viewLifecycleOwner) { requestAllPermissionsForRecording() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt index 3ae4dd42e18c..0591f6baf1f8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt @@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon @@ -34,7 +36,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -57,7 +61,12 @@ fun VoiceToContentScreen( val amplitudes by viewModel.amplitudes.observeAsState(initial = listOf()) val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp - val bottomSheetHeight = screenHeight * 0.6f // Set to 60% of screen height - but how can it be dynamic? + // Adjust the bottom sheet height based on orientation + val bottomSheetHeight = if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + screenHeight // Full height in landscape + } else { + screenHeight * 0.6f // 60% height in portrait + } Surface( modifier = Modifier @@ -65,7 +74,14 @@ fun VoiceToContentScreen( .height(bottomSheetHeight), color = MaterialTheme.colors.surface ) { - VoiceToContentView(state, amplitudes) + Box( + modifier = Modifier + .fillMaxSize() + .nestedScroll(rememberNestedScrollInteropConnection()) // Enable nested scrolling for the bottom sheet + .verticalScroll(rememberScrollState()) // Enable vertical scrolling for the bottom sheet + ) { + VoiceToContentView(state, amplitudes) + } } } @@ -75,6 +91,7 @@ fun VoiceToContentView(state: VoiceToContentUiState, amplitudes: List) { horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() + // .verticalScroll(rememberScrollState()) // Enable vertical scrolling .padding(16.dp) .background(MaterialTheme.colors.surface) // Use theme-aware background color ) { From 5e1ace937cb043e6e651749d34380ec863b9f225 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 15:32:00 -0400 Subject: [PATCH 42/65] Remove commented out line --- .../wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt index 0591f6baf1f8..656afab9409a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt @@ -91,7 +91,6 @@ fun VoiceToContentView(state: VoiceToContentUiState, amplitudes: List) { horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() - // .verticalScroll(rememberScrollState()) // Enable vertical scrolling .padding(16.dp) .background(MaterialTheme.colors.surface) // Use theme-aware background color ) { From 6401f11d68274989a44fb68050d4d5a844175c41 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 16:53:10 -0400 Subject: [PATCH 43/65] Refactor: remove log lines, simplify amplitudeList, and lower the recording update interval. --- .../wordpress/android/util/audio/AudioRecorder.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt index 283d18593793..73f957289151 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt @@ -76,20 +76,16 @@ class AudioRecorder( } } catch (e: IOException) { val errorMessage = "Error preparing MediaRecorder: ${e.message}" - Log.e(TAG, errorMessage) onRecordingFinished(Error(errorMessage)) } catch (e: IllegalStateException) { val errorMessage = "Illegal state when starting recording: ${e.message}" - Log.e(TAG, errorMessage) onRecordingFinished(Error(errorMessage)) } catch (e: SecurityException) { val errorMessage = "Security exception when starting recording: ${e.message}" - Log.e(TAG, errorMessage) onRecordingFinished(Error(errorMessage)) } } else { val errorMessage = "Permission to record audio not granted" - Log.e(TAG, errorMessage) onRecordingFinished(Error(errorMessage)) } } @@ -146,16 +142,22 @@ class AudioRecorder( private fun startRecordingUpdates() { recordingJob = coroutineScope.launch { var elapsedTimeInSeconds = 0 + val amplitudeList = mutableListOf() while (recorder != null) { delay(RECORDING_UPDATE_INTERVAL) elapsedTimeInSeconds += (RECORDING_UPDATE_INTERVAL / 1000).toInt() val fileSize = File(filePath).length() val amplitude = recorder?.maxAmplitude?.toFloat() ?: 0f + amplitudeList.add(amplitude) + // Keep the list to a manageable size (e.g., last 1000 samples) + if (amplitudeList.size > 1000) { + amplitudeList.removeAt(0) + } _recordingUpdates.value = RecordingUpdate( elapsedTime = elapsedTimeInSeconds, fileSize = fileSize, fileSizeLimitExceeded = fileSize >= recordingStrategy.maxFileSize, - amplitudes = listOf(amplitude) + amplitudes = amplitudeList.toList() ) if ( maxFileSizeExceeded(fileSize) || maxDurationExceeded(elapsedTimeInSeconds) ) { @@ -198,7 +200,7 @@ class AudioRecorder( companion object { private const val TAG = "AudioRecorder" - private const val RECORDING_UPDATE_INTERVAL = 1000L // in milliseconds + private const val RECORDING_UPDATE_INTERVAL = 100L // in milliseconds private const val RESUME_DELAY = 500L // in milliseconds private const val FILE_SIZE_THRESHOLD = 100000L private const val DURATION_THRESHOLD = 1 From 90ec0e7a72c61b98732d7e8865895f2de0f1faf8 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 16:55:15 -0400 Subject: [PATCH 44/65] Refactor: use recordingUpdate instead of direct interaction with amplitudes --- .../ui/voicetocontent/VoiceToContentViewModel.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt index 56af82ddac9d..6bb4264b368a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt @@ -26,6 +26,7 @@ import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.RECORDI import org.wordpress.android.util.audio.IAudioRecorder import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success +import org.wordpress.android.util.audio.RecordingUpdate import org.wordpress.android.viewmodel.ContextProvider import org.wordpress.android.viewmodel.ScopedViewModel import java.io.File @@ -49,8 +50,8 @@ class VoiceToContentViewModel @Inject constructor( private val _dismiss = MutableLiveData() val dismiss = _dismiss as LiveData - private val _amplitudes = MutableLiveData>() - val amplitudes: LiveData> get() = _amplitudes + private val _recordingUpdate = MutableLiveData() + val recordingUpdate: LiveData get() = _recordingUpdate private val _onIneligibleForVoiceToContent = MutableLiveData() val onIneligibleForVoiceToContent = _onIneligibleForVoiceToContent as LiveData @@ -104,11 +105,8 @@ class VoiceToContentViewModel @Inject constructor( } // Recording - // todo: This doesn't work as expected - @Suppress("MagicNumber") - private fun updateAmplitudes(newAmplitudes: List) { - _amplitudes.value = listOf(1.1f, 2.2f, 4.4f, 3.2f, 1.1f, 2.2f, 1.0f, 3.5f) - Log.d(javaClass.simpleName, "Update amplitudes: $newAmplitudes") + private fun updateRecordingData(recordingUpdate: RecordingUpdate) { + _recordingUpdate.value = recordingUpdate } private fun observeRecordingUpdates() { @@ -117,7 +115,7 @@ class VoiceToContentViewModel @Inject constructor( if (update.fileSizeLimitExceeded) { stopRecording() } else { - updateAmplitudes(update.amplitudes) + updateRecordingData(update) // todo: Handle other updates if needed when UI is ready, e.g., elapsed time and file size Log.d("AudioRecorder", "Recording update: $update") } From 87a9598763f2a5a341cac8c882a8785210e99082 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 16:55:39 -0400 Subject: [PATCH 45/65] Update waveform to use an oblong shape and have it scroll of the screen to the left --- .../ui/voicetocontent/WaveformVisualizer.kt | 185 ++++++------------ 1 file changed, 56 insertions(+), 129 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/WaveformVisualizer.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/WaveformVisualizer.kt index 45ea2427a950..3704426e5fbe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/WaveformVisualizer.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/WaveformVisualizer.kt @@ -1,149 +1,76 @@ package org.wordpress.android.ui.voicetocontent -// import androidx.compose.animation.core.* import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable -// import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity -// port androidx.compose.ui.unit.Density +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import org.wordpress.android.util.audio.RecordingUpdate @Composable -fun WaveformVisualizer( - amplitudes: List, - modifier: Modifier = Modifier, - color: Color = MaterialTheme.colors.primary -) { - val spacingDp = 8.dp - val density = LocalDensity.current - val spacingPx = with(density) { spacingDp.toPx() } // 2dp spacing between bars - val strokeWidth = with(density) { 2.dp.toPx() } +fun WaveformOblongVisualizer(recordingUpdate: RecordingUpdate, currentPosition: Int) { + val amplitudeList = recordingUpdate.amplitudes + val maxRadius = 150f // increased maximum radius for the oblongs + val minRadius = 50f // increased minimum radius for the oblongs + val maxAmplitude = 32767f // maximum possible amplitude from MediaRecorder + val oblongWidth = 20f // fixed width of the oblongs + val color = MaterialTheme.colors.primary - Canvas(modifier = modifier) { + Canvas(modifier = Modifier + .fillMaxWidth() + .height(150.dp)) { val width = size.width val height = size.height - val centerY = height / 2 + val barSpacing = width / 20 // number of visible oblongs + val visibleAmplitudes = amplitudeList.takeLast(currentPosition + 20).take(20) - // Calculate the number of lines that can fit within the width given the spacing - val numberOfLines = (width / spacingPx).toInt() - - // Adjust amplitudeStep to match the number of lines we can fit - val amplitudeStep = maxOf(1, amplitudes.size / numberOfLines) - - for (i in 0 until numberOfLines) { - val index = i * amplitudeStep - if (index >= amplitudes.size) break - - val x = i * spacingPx - val amplitude = amplitudes[index] - val y1 = centerY - (amplitude * height * 0.5f) - val y2 = centerY + (amplitude * height * 0.5f) - - drawLine( + visibleAmplitudes.forEachIndexed { index, amplitude -> + val normalizedAmplitude = amplitude.coerceIn(0f, maxAmplitude) + val oblongHeight = minRadius + (normalizedAmplitude / maxAmplitude) * (maxRadius - minRadius) + val xOffset = index * barSpacing + val yOffset = (height - oblongHeight) / 2 + drawRoundRect( color = color, - start = androidx.compose.ui.geometry.Offset(x, y1), - end = androidx.compose.ui.geometry.Offset(x, y2), - strokeWidth = strokeWidth + topLeft = Offset(xOffset, yOffset), + size = androidx.compose.ui.geometry.Size(oblongWidth, oblongHeight), + cornerRadius = CornerRadius(10f, 10f) // rounded corners to make it oblong ) } } } -// -//private fun Float.toPx(density: Density): Float { -// return with(density) { this@toPx.dp.toPx() } -//} - - - // Try three -// Canvas(modifier = modifier) { -// val width = size.width -// val height = size.height -// val centerY = height / 2 -// val stepWidth = width / (amplitudes.size.toFloat() - 1) -// -// for (i in amplitudes.indices) { -// val x = i * stepWidth -// val amplitude = amplitudes[i] -// val y1 = centerY - (amplitude * height * 0.5f) -// val y2 = centerY + (amplitude * height * 0.5f) -// -// drawLine( -// color = color, -// start = androidx.compose.ui.geometry.Offset(x, y1), -// end = androidx.compose.ui.geometry.Offset(x, y2), -// strokeWidth = 2.dp.toPx() -// ) -// } -// } - // Try two -// val infiniteTransition = rememberInfiniteTransition(label = "") -// val phase by infiniteTransition.animateFloat( -// initialValue = 0f, -// targetValue = 1f, -// animationSpec = infiniteRepeatable( -// animation = tween(durationMillis = 2000, easing = LinearEasing), -// repeatMode = RepeatMode.Restart -// ), label = "" -// ) -// -// Canvas(modifier = modifier) { -// val width = size.width -// val height = size.height -// val centerY = height / 2 -// -// // val amplitudeStep = maxOf(1, amplitudes.size / width.toInt()) -// val stepWidth = width / amplitudes.size.toFloat() -// -// for (i in amplitudes.indices) { -// // for (i in amplitudes.indices step amplitudeStep) { -// // Calculate the x-coordinate based on phase to create a scrolling effect -// val x = (i * stepWidth + phase * width) % width -// val amplitude = amplitudes[i] -// val y1 = centerY - (amplitude * height * 0.5f) -// val y2 = centerY + (amplitude * height * 0.5f) -// -// drawLine( -// color = color, -// start = androidx.compose.ui.geometry.Offset(x, y1), -// end = androidx.compose.ui.geometry.Offset(x, y2), -// strokeWidth = 2.dp.toPx() -// ) -// } - - // Try One -// val infiniteTransition = rememberInfiniteTransition(label = "") -// val phase by infiniteTransition.animateFloat( -// initialValue = 0f, -// targetValue = 1f, -// animationSpec = infiniteRepeatable( -// animation = tween(durationMillis = 1000, easing = LinearEasing), -// repeatMode = RepeatMode.Restart -// ), label = "" -// ) -// -// Canvas(modifier = modifier) { -// val width = size.width -// val height = size.height -// val centerY = height / 2 -// val amplitudeStep = maxOf(1, amplitudes.size / width.toInt()) -// -// for (i in amplitudes.indices step amplitudeStep) { -// val x = ((i / amplitudeStep) + phase * width) % width -// val amplitude = amplitudes[i] -// val y1 = centerY - (amplitude * height * 0.5f) -// val y2 = centerY + (amplitude * height * 0.5f) -// -// drawLine( -// color = color, -// start = androidx.compose.ui.geometry.Offset(x, y1), -// end = androidx.compose.ui.geometry.Offset(x, y2), -// strokeWidth = 2.dp.toPx() -// ) -// } -// } -//} +@Composable +fun ScrollingWaveformVisualizer(recordingUpdate: RecordingUpdate) { + val currentPosition = remember { mutableStateOf(0) } + LaunchedEffect(recordingUpdate) { + while (true) { + delay(100) // adjust delay as needed for scrolling speed + currentPosition.value += 1 + if (currentPosition.value >= recordingUpdate.amplitudes.size) { + currentPosition.value = 0 // reset to start if we reach the end + } + } + } + WaveformOblongVisualizer(recordingUpdate, currentPosition.value) +} +@Preview(showBackground = true) +@Composable +fun WaveformVisualizerOblongPreview() { + val mockRecordingUpdate = RecordingUpdate( + amplitudes = listOf( + 1000f, 5000f, 10000f, 20000f, 30000f, 15000f, 25000f, 12000f, 17000f, 11000f, + 1000f, 5000f, 10000f, 20000f, 30000f, 15000f, 25000f, 12000f, 17000f, 11000f + ) + ) + WaveformOblongVisualizer(recordingUpdate = mockRecordingUpdate, currentPosition = 0) +} From 77f2344f67499445f0633712a6bd66b7df31a299 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Thu, 13 Jun 2024 16:56:35 -0400 Subject: [PATCH 46/65] Replace amplitudes with RecordingUpdate and use ScrollingWaveformVisualizer --- .../ui/voicetocontent/VoiceToContentScreen.kt | 41 ++++++------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt index 656afab9409a..4cba83806d76 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt @@ -52,13 +52,14 @@ import androidx.constraintlayout.compose.Dimension import org.wordpress.android.R import org.wordpress.android.ui.compose.components.buttons.Drawable import org.wordpress.android.ui.compose.theme.AppTheme +import org.wordpress.android.util.audio.RecordingUpdate @Composable fun VoiceToContentScreen( viewModel: VoiceToContentViewModel ) { val state by viewModel.state.collectAsState() - val amplitudes by viewModel.amplitudes.observeAsState(initial = listOf()) + val recordingUpdate by viewModel.recordingUpdate.observeAsState(initial = RecordingUpdate()) val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp // Adjust the bottom sheet height based on orientation @@ -80,13 +81,13 @@ fun VoiceToContentScreen( .nestedScroll(rememberNestedScrollInteropConnection()) // Enable nested scrolling for the bottom sheet .verticalScroll(rememberScrollState()) // Enable vertical scrolling for the bottom sheet ) { - VoiceToContentView(state, amplitudes) + VoiceToContentView(state, recordingUpdate) } } } @Composable -fun VoiceToContentView(state: VoiceToContentUiState, amplitudes: List) { +fun VoiceToContentView(state: VoiceToContentUiState, recordingUpdate: RecordingUpdate) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier @@ -100,7 +101,7 @@ fun VoiceToContentView(state: VoiceToContentUiState, amplitudes: List) { else -> { Header(state.header) SecondaryHeader(state.secondaryHeader) - RecordingPanel(state, amplitudes) + RecordingPanel(state, recordingUpdate) } } } @@ -183,7 +184,7 @@ fun SecondaryHeader(model: SecondaryHeaderUIModel?) { } @Composable -fun RecordingPanel(model: VoiceToContentUiState, amplitudes: List) { +fun RecordingPanel(model: VoiceToContentUiState, recordingUpdate: RecordingUpdate) { model.recordingPanel?.let { Row( verticalAlignment = Alignment.CenterVertically, @@ -205,14 +206,7 @@ fun RecordingPanel(model: VoiceToContentUiState, amplitudes: List) { .height(IntrinsicSize.Max) .padding(48.dp) ) { - WaveformVisualizer( - amplitudes = amplitudes, - modifier = Modifier - .fillMaxWidth() - .height(40.dp) - .padding(16.dp), - color = MaterialTheme.colors.primary - ) + ScrollingWaveformVisualizer(recordingUpdate = recordingUpdate) } } else if (model.uiStateType == VoiceToContentUIStateType.INELIGIBLE_FOR_FEATURE) { InEligible(model = it) @@ -354,7 +348,7 @@ fun PreviewInitializingView() { hasPermission = false ) ) - VoiceToContentView(state = state, amplitudes = listOf()) + VoiceToContentView(state = state, recordingUpdate = RecordingUpdate()) } } @@ -376,7 +370,7 @@ fun PreviewReadyToRecordView() { isEligibleForFeature = true ) ) - VoiceToContentView(state = state, amplitudes = listOf()) + VoiceToContentView(state = state, recordingUpdate = RecordingUpdate()) } } @@ -397,7 +391,7 @@ fun PreviewNotEligibleToRecordView() { upgradeUrl = "https://www.wordpress.com" ) ) - VoiceToContentView(state = state, amplitudes = listOf()) + VoiceToContentView(state = state, recordingUpdate = RecordingUpdate()) } } @@ -420,18 +414,7 @@ fun PreviewRecordingView() { isEligibleForFeature = true ) ) - VoiceToContentView( - state = state, - amplitudes = listOf( - 1.1f, - 2.2f, - 3.3f, - 4.4f, - 2.2f, - 3.3f, - 1.1f - ) - ) + VoiceToContentView(state = state, recordingUpdate = RecordingUpdate()) } } @@ -444,6 +427,6 @@ fun PreviewProcessingView() { uiStateType = VoiceToContentUIStateType.PROCESSING, header = HeaderUIModel(label = R.string.voice_to_content_processing_label, onClose = { }) ) - VoiceToContentView(state = state, amplitudes = listOf()) + VoiceToContentView(state = state, recordingUpdate = RecordingUpdate()) } } From 6b55e13e9edce01ed1252b9574e0139c4c181f4f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 10:21:36 +0000 Subject: [PATCH 47/65] Update dependency fastlane-plugin-wpmreleasetoolkit to v11 --- Gemfile | 2 +- Gemfile.lock | 68 +++++++++++++++++++++++++--------------------------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/Gemfile b/Gemfile index e64e0667d18c..734bb6e959ab 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem 'nokogiri' ### Fastlane Plugins gem 'fastlane-plugin-sentry' -gem 'fastlane-plugin-wpmreleasetoolkit', '~> 9.2' +gem 'fastlane-plugin-wpmreleasetoolkit', '~> 11.0' # gem 'fastlane-plugin-wpmreleasetoolkit', path: '../../release-toolkit' # gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', branch: '' diff --git a/Gemfile.lock b/Gemfile.lock index 5222314be129..63531764df18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM base64 nkf rexml - activesupport (7.1.3) + activesupport (7.1.3.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -17,30 +17,29 @@ GEM tzinfo (~> 2.0) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.15) + artifactory (3.0.17) ast (2.4.2) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.894.0) - aws-sdk-core (3.191.2) + aws-partitions (1.937.0) + aws-sdk-core (3.196.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) - base64 jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.77.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-kms (1.82.0) + aws-sdk-core (~> 3, >= 3.193.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.143.0) - aws-sdk-core (~> 3, >= 3.191.0) + aws-sdk-s3 (1.151.0) + aws-sdk-core (~> 3, >= 3.194.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) - bigdecimal (3.1.6) - buildkit (1.5.0) + bigdecimal (3.1.8) + buildkit (1.6.0) sawyer (>= 0.6) chroma (0.2.0) claide (1.1.0) @@ -52,7 +51,7 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.1) connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) @@ -85,10 +84,9 @@ GEM rake (>= 12.0.0, < 14.0.0) domain_name (0.6.20240107) dotenv (2.8.1) - drb (2.2.0) - ruby2_keywords + drb (2.2.1) emoji_regex (3.2.3) - excon (0.109.0) + excon (0.110.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -119,15 +117,15 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.219.0) + fastimage (2.3.1) + fastlane (2.220.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -148,10 +146,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -160,10 +158,10 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-sentry (1.19.0) os (~> 1.1, >= 1.1.4) - fastlane-plugin-wpmreleasetoolkit (9.4.0) + fastlane-plugin-wpmreleasetoolkit (11.0.1) activesupport (>= 6.1.7.1) buildkit (~> 1.5) chroma (= 0.2.0) @@ -172,7 +170,7 @@ GEM git (~> 1.3) google-cloud-storage (~> 1.31) java-properties (~> 0.3.0) - nokogiri (~> 1.11, < 1.16) + nokogiri (~> 1.11, < 1.17) octokit (~> 6.1) parallel (~> 1.14) plist (~> 3.1) @@ -200,12 +198,12 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.31.0) google-apis-core (>= 0.11.0, < 2.a) - google-cloud-core (1.6.1) + google-cloud-core (1.7.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) + google-cloud-errors (1.4.0) google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) @@ -224,12 +222,12 @@ GEM http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) java-properties (0.3.0) jmespath (1.6.2) json (2.7.2) - jwt (2.8.0) + jwt (2.8.1) base64 kramdown (2.4.0) rexml @@ -238,8 +236,8 @@ GEM language_server-protocol (3.17.0.3) mini_magick (4.12.0) mini_mime (1.1.5) - mini_portile2 (2.8.5) - minitest (5.22.2) + mini_portile2 (2.8.6) + minitest (5.23.1) multi_json (1.15.0) multipart-post (2.4.1) mutex_m (0.2.0) @@ -248,7 +246,7 @@ GEM naturally (2.2.1) nkf (0.2.0) no_proxy_fix (0.1.2) - nokogiri (1.15.5) + nokogiri (1.16.5) mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (6.1.1) @@ -256,15 +254,15 @@ GEM sawyer (~> 0.9) open4 (1.3.4) options (2.3.2) - optparse (0.4.0) + optparse (0.5.0) os (1.1.4) parallel (1.24.0) parser (3.3.2.0) ast (~> 2.4.1) racc plist (3.7.1) - progress_bar (1.3.3) - highline (>= 1.6, < 3) + progress_bar (1.3.4) + highline (>= 1.6) options (~> 2.3.0) public_suffix (5.0.5) racc (1.8.0) @@ -302,7 +300,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - security (0.1.3) + security (0.1.5) signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -344,7 +342,7 @@ DEPENDENCIES danger-dangermattic (~> 1.0) fastlane (~> 2) fastlane-plugin-sentry - fastlane-plugin-wpmreleasetoolkit (~> 9.2) + fastlane-plugin-wpmreleasetoolkit (~> 11.0) nokogiri rmagick (~> 4.1) From 231728075d33dea99ffa15c75e929dd94b43a464 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Fri, 14 Jun 2024 12:05:35 +0200 Subject: [PATCH 48/65] Update fastlane-plugin-wpmreleasetoolkit to `11.0.2` --- Gemfile.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 63531764df18..9f729ce44ab1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,7 +5,7 @@ GEM base64 nkf rexml - activesupport (7.1.3.3) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -21,17 +21,17 @@ GEM ast (2.4.2) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.937.0) - aws-sdk-core (3.196.1) + aws-partitions (1.944.0) + aws-sdk-core (3.197.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.82.0) - aws-sdk-core (~> 3, >= 3.193.0) + aws-sdk-kms (1.84.0) + aws-sdk-core (~> 3, >= 3.197.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.151.0) - aws-sdk-core (~> 3, >= 3.194.0) + aws-sdk-s3 (1.152.3) + aws-sdk-core (~> 3, >= 3.197.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -51,7 +51,7 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.3.1) + concurrent-ruby (1.3.3) connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) @@ -161,7 +161,7 @@ GEM xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-sentry (1.19.0) os (~> 1.1, >= 1.1.4) - fastlane-plugin-wpmreleasetoolkit (11.0.1) + fastlane-plugin-wpmreleasetoolkit (11.0.2) activesupport (>= 6.1.7.1) buildkit (~> 1.5) chroma (= 0.2.0) @@ -219,7 +219,7 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) i18n (1.14.5) @@ -234,9 +234,9 @@ GEM kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) language_server-protocol (3.17.0.3) - mini_magick (4.12.0) + mini_magick (4.13.0) mini_mime (1.1.5) - mini_portile2 (2.8.6) + mini_portile2 (2.8.7) minitest (5.23.1) multi_json (1.15.0) multipart-post (2.4.1) @@ -246,7 +246,7 @@ GEM naturally (2.2.1) nkf (0.2.0) no_proxy_fix (0.1.2) - nokogiri (1.16.5) + nokogiri (1.16.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (6.1.1) @@ -256,7 +256,7 @@ GEM options (2.3.2) optparse (0.5.0) os (1.1.4) - parallel (1.24.0) + parallel (1.25.1) parser (3.3.2.0) ast (~> 2.4.1) racc @@ -277,8 +277,8 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.8) - strscan (>= 3.0.9) + rexml (3.2.9) + strscan rmagick (4.3.0) rouge (2.0.7) rubocop (1.64.1) From d20a7bfd494fc14d16eef7e7048e5ba98b2d2905 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Fri, 14 Jun 2024 12:11:55 +0200 Subject: [PATCH 49/65] Adjust to release-toolkit new action names --- fastlane/lanes/release.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fastlane/lanes/release.rb b/fastlane/lanes/release.rb index 78b704103e38..ff178e9484e3 100644 --- a/fastlane/lanes/release.rb +++ b/fastlane/lanes/release.rb @@ -76,8 +76,8 @@ push_to_git_remote(tags: false) - setbranchprotection(repository: GHHELPER_REPO, branch: "release/#{new_version}") - setfrozentag(repository: GHHELPER_REPO, milestone: new_version) + set_branch_protection(repository: GHHELPER_REPO, branch: "release/#{new_version}") + set_milestone_frozen_marker(repository: GHHELPER_REPO, milestone: new_version) end ##################################################################################### @@ -293,7 +293,7 @@ release_branch = "release/#{current_release_version}" # Remove branch protection first, so that we can push the final commits directly to the release branch - removebranchprotection(repository: GHHELPER_REPO, branch: release_branch) + remove_branch_protection(repository: GHHELPER_REPO, branch: release_branch) # Don't check translation coverage for now since we are finalizing the release in CI # check_translations_coverage @@ -315,7 +315,7 @@ push_to_git_remote(tags: false) # Wrap up - setfrozentag(repository: GHHELPER_REPO, milestone: version_name, freeze: false) + set_milestone_frozen_marker(repository: GHHELPER_REPO, milestone: version_name, freeze: false) create_new_milestone(repository: GHHELPER_REPO) close_milestone(repository: GHHELPER_REPO, milestone: version_name) From a2583a406dbad8a7e2ba70064ba65d826534e9fd Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 09:14:26 -0400 Subject: [PATCH 50/65] Add fun for ending the recording session --- .../main/java/org/wordpress/android/util/audio/IAudioRecorder.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/util/audio/IAudioRecorder.kt b/WordPress/src/main/java/org/wordpress/android/util/audio/IAudioRecorder.kt index 73ab0ca30725..78cbd55efbad 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/audio/IAudioRecorder.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/audio/IAudioRecorder.kt @@ -9,6 +9,7 @@ interface IAudioRecorder { fun pauseRecording() fun resumeRecording() fun recordingUpdates(): Flow + fun endRecordingSession() sealed class AudioRecorderResult { data class Success(val recordingPath: String) : AudioRecorderResult() From 7e71d71873c8ce22864a25ddb9e24164864f4258 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 09:14:37 -0400 Subject: [PATCH 51/65] Add fun for ending the recording session --- .../wordpress/android/ui/voicetocontent/RecordingUseCase.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/RecordingUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/RecordingUseCase.kt index 3552ea314fe2..069d10464522 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/RecordingUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/RecordingUseCase.kt @@ -14,7 +14,6 @@ class RecordingUseCase @Inject constructor( audioRecorder.startRecording(onRecordingFinished) } - @Suppress("ReturnCount") fun stopRecording() { audioRecorder.stopRecording() } @@ -22,5 +21,8 @@ class RecordingUseCase @Inject constructor( fun recordingUpdates(): Flow { return audioRecorder.recordingUpdates() } -} + fun endRecordingSession() { + audioRecorder.endRecordingSession() + } +} From 280e1563a10d117e592171e28fafc518360a233a Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 09:15:33 -0400 Subject: [PATCH 52/65] Implement endRecordingSession so that the recording resources can be released outside of a stop recording action --- .../wordpress/android/util/audio/AudioRecorder.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt index 73f957289151..12c0a01b2f65 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt @@ -90,7 +90,7 @@ class AudioRecorder( } } - override fun stopRecording() { + private fun clearResources() { try { recorder?.apply { stop() @@ -104,7 +104,11 @@ class AudioRecorder( _isPaused.value = false _isRecording.value = false } - // return filePath + } + + override fun stopRecording() { + clearResources() + // return the filePath onRecordingFinished(Success(filePath)) } @@ -136,6 +140,10 @@ class AudioRecorder( } } + override fun endRecordingSession() { + clearResources() + } + override fun recordingUpdates(): Flow = recordingUpdates @Suppress("MagicNumber") From e9ffc3ee69ace248d0aa100266d65109cb2adcad Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 09:17:25 -0400 Subject: [PATCH 53/65] Invoke endRecordingSession when the bottom sheet is closed via swipe/x or the stop button is tapped --- .../android/ui/voicetocontent/VoiceToContentViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt index 6bb4264b368a..dc57bde0b915 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt @@ -104,6 +104,10 @@ class VoiceToContentViewModel @Inject constructor( isStarted = true } + fun onBottomSheetClosed() { + recordingUseCase.endRecordingSession() + } + // Recording private fun updateRecordingData(recordingUpdate: RecordingUpdate) { _recordingUpdate.value = recordingUpdate @@ -206,6 +210,7 @@ class VoiceToContentViewModel @Inject constructor( private fun onClose() { logger.track(Stat.VOICE_TO_CONTENT_BUTTON_CLOSE_TAPPED) + recordingUseCase.endRecordingSession() _dismiss.postValue(Unit) } From fc7a0fb67e3d40b3216c6c602578cc003a6e722e Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 09:17:58 -0400 Subject: [PATCH 54/65] Add listener for when bottom sheet is closing --- .../VoiceToContentDialogFragment.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt index ff491318e2bc..5fdd8d1aa90c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt @@ -64,6 +64,27 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() { behavior.skipCollapsed = true behavior.state = BottomSheetBehavior.STATE_EXPANDED + behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + @SuppressLint("SwitchIntDef") + override fun onStateChanged(bottomSheet: View, newState: Int) { + when (newState) { + BottomSheetBehavior.STATE_HIDDEN -> { + // Bottom sheet is hidden, you can listen for this event here + viewModel.onBottomSheetClosed() + } + BottomSheetBehavior.STATE_COLLAPSED -> { + // Bottom sheet is collapsed, you can listen for this event here + viewModel.onBottomSheetClosed() + } + // Handle other states if necessary + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) { + // Handle the slide offset if needed + } + }) + // Disable touch interception by the bottom sheet to allow nested scrolling bottomSheet.setOnTouchListener { _, _ -> false } } From 336d8076d6ec6652ff1c1eb8dc7810456533e7dd Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 13:48:35 -0400 Subject: [PATCH 55/65] Replace elapsedTime with remainingTimeInSeconds --- .../java/org/wordpress/android/util/audio/RecordingUpdate.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/audio/RecordingUpdate.kt b/WordPress/src/main/java/org/wordpress/android/util/audio/RecordingUpdate.kt index f067d6cff8a4..80a91b2b6ffe 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/audio/RecordingUpdate.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/audio/RecordingUpdate.kt @@ -1,7 +1,7 @@ package org.wordpress.android.util.audio data class RecordingUpdate( - val elapsedTime: Int = 0, // in seconds + val remainingTimeInSeconds: Int = -1, val fileSize: Long = 0L, // in bytes val fileSizeLimitExceeded: Boolean = false, val amplitudes: List = emptyList() From f51d13c581d44c059b9e09941bcca9c1ab2b41d5 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 13:49:03 -0400 Subject: [PATCH 56/65] Replace timeElapsed with timeMaxDurationInSeconds --- .../android/ui/voicetocontent/VoiceToContentUiState.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt index 9fb2ca350065..01d189f89456 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentUiState.kt @@ -13,7 +13,7 @@ data class SecondaryHeaderUIModel( val isLabelVisible: Boolean = true, val isProgressIndicatorVisible: Boolean = false, val requestsAvailable: String = "0", - val timeElapsed: String = "00:00:00", + val timeMaxDurationInSeconds: Int = 0, val isTimeElapsedVisible: Boolean = false ) From 1ef3fca1ca1e83555114e89177e84999f2df56d1 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 13:50:17 -0400 Subject: [PATCH 57/65] Implement time remaining calculation and update recording_update_interval --- .../wordpress/android/util/audio/AudioRecorder.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt index 12c0a01b2f65..a7a1ecedff16 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/audio/AudioRecorder.kt @@ -149,11 +149,16 @@ class AudioRecorder( @Suppress("MagicNumber") private fun startRecordingUpdates() { recordingJob = coroutineScope.launch { - var elapsedTimeInSeconds = 0 + val startTime = System.currentTimeMillis() val amplitudeList = mutableListOf() while (recorder != null) { delay(RECORDING_UPDATE_INTERVAL) - elapsedTimeInSeconds += (RECORDING_UPDATE_INTERVAL / 1000).toInt() + // Calculate elapsed time in seconds + val elapsedTimeInSeconds = ((System.currentTimeMillis() - startTime) / 1000).toInt() + + // Calculate remaining time + val remainingTimeInSeconds = recordingStrategy.maxDuration - elapsedTimeInSeconds + val fileSize = File(filePath).length() val amplitude = recorder?.maxAmplitude?.toFloat() ?: 0f amplitudeList.add(amplitude) @@ -162,7 +167,7 @@ class AudioRecorder( amplitudeList.removeAt(0) } _recordingUpdates.value = RecordingUpdate( - elapsedTime = elapsedTimeInSeconds, + remainingTimeInSeconds = remainingTimeInSeconds, fileSize = fileSize, fileSizeLimitExceeded = fileSize >= recordingStrategy.maxFileSize, amplitudes = amplitudeList.toList() @@ -208,7 +213,7 @@ class AudioRecorder( companion object { private const val TAG = "AudioRecorder" - private const val RECORDING_UPDATE_INTERVAL = 100L // in milliseconds + private const val RECORDING_UPDATE_INTERVAL = 75L // in milliseconds private const val RESUME_DELAY = 500L // in milliseconds private const val FILE_SIZE_THRESHOLD = 100000L private const val DURATION_THRESHOLD = 1 From 811aec9b81b4dea7da0cb09e162febd4038adce0 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 13:51:00 -0400 Subject: [PATCH 58/65] Pass the max duration to the model via the strategy --- .../android/ui/voicetocontent/VoiceToContentViewModel.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt index dc57bde0b915..a96c0c71ef0c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt @@ -26,6 +26,7 @@ import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.RECORDI import org.wordpress.android.util.audio.IAudioRecorder import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success +import org.wordpress.android.util.audio.RecordingStrategy import org.wordpress.android.util.audio.RecordingUpdate import org.wordpress.android.viewmodel.ContextProvider import org.wordpress.android.viewmodel.ScopedViewModel @@ -132,6 +133,7 @@ class VoiceToContentViewModel @Inject constructor( recordingUseCase.startRecording { audioRecorderResult -> when (audioRecorderResult) { is Success -> { + transitionToProcessing() val file = getRecordingFile(audioRecorderResult.recordingPath) file?.let { executeVoiceToContent(it) @@ -276,8 +278,9 @@ class VoiceToContentViewModel @Inject constructor( uiStateType = RECORDING, header = currentState.header.copy(label = R.string.voice_to_content_recording_label), secondaryHeader = currentState.secondaryHeader?.copy( - timeElapsed = "00:00:00", - isTimeElapsedVisible = true + isTimeElapsedVisible = true, + timeMaxDurationInSeconds = MAX_DURATION, + isLabelVisible = false ), recordingPanel = currentState.recordingPanel?.copy( onStopTap = ::onStopTap, @@ -330,6 +333,7 @@ class VoiceToContentViewModel @Inject constructor( private val NetworkUnavailableMsg = R.string.error_network_connection private val GenericFailureMsg = R.string.voice_to_content_generic_error private const val VOICE_TO_CONTENT = "Voice to content" + private val MAX_DURATION = RecordingStrategy.VoiceToContentRecordingStrategy().maxDuration } } From 521481fdbf28d10d90b4523f61a8a88b1b530205 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 13:51:52 -0400 Subject: [PATCH 59/65] Update secondaryHeader so that time remaining is shown on view --- .../ui/voicetocontent/VoiceToContentScreen.kt | 47 +++++++++++++++++-- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt index 4cba83806d76..b787266cbe25 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt @@ -53,6 +53,7 @@ import org.wordpress.android.R import org.wordpress.android.ui.compose.components.buttons.Drawable import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.util.audio.RecordingUpdate +import java.util.Locale @Composable fun VoiceToContentScreen( @@ -100,7 +101,7 @@ fun VoiceToContentView(state: VoiceToContentUiState, recordingUpdate: RecordingU VoiceToContentUIStateType.ERROR -> ErrorView(state) else -> { Header(state.header) - SecondaryHeader(state.secondaryHeader) + SecondaryHeader(state.secondaryHeader, recordingUpdate) RecordingPanel(state, recordingUpdate) } } @@ -159,14 +160,16 @@ fun Header(model: HeaderUIModel) { } @Composable -fun SecondaryHeader(model: SecondaryHeaderUIModel?) { +fun SecondaryHeader(model: SecondaryHeaderUIModel?, recordingUpdate: RecordingUpdate) { model?.let { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { - Text(text = stringResource(id = model.label), style = secondaryHeaderStyle) - Spacer(modifier = Modifier.width(8.dp)) // Add space between text and progress + if (model.isLabelVisible) { + Text(text = stringResource(id = model.label), style = secondaryHeaderStyle) + Spacer(modifier = Modifier.width(8.dp)) // Add space between text and progress + } if (model.isProgressIndicatorVisible) { Box( modifier = Modifier.size(20.dp) // size the progress indicator @@ -176,13 +179,47 @@ fun SecondaryHeader(model: SecondaryHeaderUIModel?) { ) } } else { - Text(text = model.requestsAvailable, style = secondaryHeaderStyle) + Text( + text = if (model.isTimeElapsedVisible) + formatTime(recordingUpdate.remainingTimeInSeconds, model.timeMaxDurationInSeconds) + else model.requestsAvailable, + style = secondaryHeaderStyle + ) } Spacer(modifier = Modifier.height(16.dp)) } } } +@Composable +fun formatTime(remainingTimeInSeconds: Int, maxDurationInSeconds: Int): String { + val default = getDefaultTimeString(maxDurationInSeconds) + if (remainingTimeInSeconds == -1) return default + + val minutes = remainingTimeInSeconds / 60 + val seconds = remainingTimeInSeconds % 60 + + val value = if (minutes == 1) default + else return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds) + + return value +} + +@Composable +fun getDefaultTimeString(maxDurationInSeconds: Int): String { + if (maxDurationInSeconds <= 0) { + return "00:00" + } + + // Calculate minutes and seconds + val minutes = (maxDurationInSeconds - 1) / 60 + val seconds = (maxDurationInSeconds - 1) % 60 + + // Format and return the default time string + return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds) +} + + @Composable fun RecordingPanel(model: VoiceToContentUiState, recordingUpdate: RecordingUpdate) { model.recordingPanel?.let { From 9aa4aec2b5f3cdd617da32d95f71ca44cb0ac792 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 14:06:22 -0400 Subject: [PATCH 60/65] Handle closing the bottom sheet on outside touch dynamically --- .../VoiceToContentDialogFragment.kt | 27 ++++++++++++++----- .../voicetocontent/VoiceToContentViewModel.kt | 8 ++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt index 5fdd8d1aa90c..7375ae03ffc2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentDialogFragment.kt @@ -2,6 +2,7 @@ package org.wordpress.android.ui.voicetocontent import android.annotation.SuppressLint import android.app.Dialog +import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Bundle @@ -18,6 +19,7 @@ import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.R import org.wordpress.android.util.audio.IAudioRecorder.Companion.REQUIRED_RECORDING_PERMISSIONS import android.provider.Settings +import android.util.Log import android.widget.FrameLayout import androidx.compose.material.ExperimentalMaterialApi import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -68,15 +70,10 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() { @SuppressLint("SwitchIntDef") override fun onStateChanged(bottomSheet: View, newState: Int) { when (newState) { - BottomSheetBehavior.STATE_HIDDEN -> { - // Bottom sheet is hidden, you can listen for this event here - viewModel.onBottomSheetClosed() - } + BottomSheetBehavior.STATE_HIDDEN, BottomSheetBehavior.STATE_COLLAPSED -> { - // Bottom sheet is collapsed, you can listen for this event here - viewModel.onBottomSheetClosed() + onBottomSheetClosed() } - // Handle other states if necessary } } @@ -88,9 +85,25 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() { // Disable touch interception by the bottom sheet to allow nested scrolling bottomSheet.setOnTouchListener { _, _ -> false } } + + // Observe the ViewModel to update the cancelable state of closing on outside touch + viewModel.isCancelableOutsideTouch.observe(this) { cancelable -> + Log.i(javaClass.simpleName, "***=> disable outside touch") + dialog.setCanceledOnTouchOutside(cancelable) + } + return dialog } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + viewModel.onBottomSheetClosed() + } + + private fun onBottomSheetClosed() { + dismiss() + } + private fun observeViewModel() { viewModel.requestPermission.observe(viewLifecycleOwner) { requestAllPermissionsForRecording() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt index dc57bde0b915..9e99790777ae 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentViewModel.kt @@ -56,6 +56,9 @@ class VoiceToContentViewModel @Inject constructor( private val _onIneligibleForVoiceToContent = MutableLiveData() val onIneligibleForVoiceToContent = _onIneligibleForVoiceToContent as LiveData + private val _isCancelableOutsideTouch = MutableLiveData(true) + val isCancelableOutsideTouch: LiveData get() = _isCancelableOutsideTouch + private var isStarted = false private val _state = MutableStateFlow(VoiceToContentUiState( @@ -129,6 +132,7 @@ class VoiceToContentViewModel @Inject constructor( private fun startRecording() { transitionToRecording() + disableDialogCancelableOutsideTouch() recordingUseCase.startRecording { audioRecorderResult -> when (audioRecorderResult) { is Success -> { @@ -147,6 +151,10 @@ class VoiceToContentViewModel @Inject constructor( } } + private fun disableDialogCancelableOutsideTouch() { + _isCancelableOutsideTouch.value = false + } + @Suppress("ReturnCount") private fun getRecordingFile(recordingPath: String): File? { if (recordingPath.isEmpty()) return null From b65e2a14ca3856e58cb273541df0f223bf187bda Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Fri, 14 Jun 2024 14:40:40 -0400 Subject: [PATCH 61/65] Address detekt issue with too many return statements --- .../wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt index b787266cbe25..1d139dd42969 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/voicetocontent/VoiceToContentScreen.kt @@ -200,7 +200,7 @@ fun formatTime(remainingTimeInSeconds: Int, maxDurationInSeconds: Int): String { val seconds = remainingTimeInSeconds % 60 val value = if (minutes == 1) default - else return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds) + else String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds) return value } From ed44c201c92c30a6e84b6878d58671a7941949c8 Mon Sep 17 00:00:00 2001 From: Irfan Omur Date: Mon, 17 Jun 2024 12:00:52 +0300 Subject: [PATCH 62/65] Update wordPressLoginVersion to 1.16.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 531047d75949..c56c5d175942 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ ext { gutenbergMobileVersion = 'v1.120.0' wordPressAztecVersion = 'v2.1.3' wordPressFluxCVersion = 'trunk-0fe67fa241426afeaaa66bc3970ba46634efa5c8' - wordPressLoginVersion = '1.15.0' + wordPressLoginVersion = '1.16.0' wordPressPersistentEditTextVersion = '1.0.2' wordPressUtilsVersion = '3.14.0' indexosMediaForMobileVersion = '43a9026f0973a2f0a74fa813132f6a16f7499c3a' From a378a5ec01ffe91878bc75402005b0a1a1c10e4b Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 17 Jun 2024 17:01:47 +0200 Subject: [PATCH 63/65] Use `copy_branch_protection` instead of `set_branch_protection` --- fastlane/lanes/release.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fastlane/lanes/release.rb b/fastlane/lanes/release.rb index ff178e9484e3..d8491ed76b88 100644 --- a/fastlane/lanes/release.rb +++ b/fastlane/lanes/release.rb @@ -76,7 +76,11 @@ push_to_git_remote(tags: false) - set_branch_protection(repository: GHHELPER_REPO, branch: "release/#{new_version}") + copy_branch_protection( + repository: GHHELPER_REPO, + from_branch: DEFAULT_BRANCH, + to_branch: "release/#{new_version}" + ) set_milestone_frozen_marker(repository: GHHELPER_REPO, milestone: new_version) end From 48d7fdfc2a13069106381af529159f08aae24009 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Tue, 18 Jun 2024 10:15:59 +0530 Subject: [PATCH 64/65] Changed mbinding to binding --- .../PostSettingsInputDialogFragment.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java index be35b4896aec..63d5fa824d13 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostSettingsInputDialogFragment.java @@ -94,31 +94,32 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { new MaterialAlertDialogBuilder(new ContextThemeWrapper(getActivity(), R.style.PostSettingsTheme)); LayoutInflater layoutInflater = requireActivity().getLayoutInflater(); //noinspection InflateParams - PostSettingsInputDialogBinding mBinding = + PostSettingsInputDialogBinding binding = PostSettingsInputDialogBinding.inflate(layoutInflater, null, false); - builder.setView(mBinding.getRoot()); + builder.setView(binding.getRoot()); if (mIsMultilineInput) { - mBinding.postSettingsInputDialogEditText.setRawInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); + binding.postSettingsInputDialogEditText.setRawInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); } else { - mBinding.postSettingsInputDialogEditText.setInputType(InputType.TYPE_CLASS_TEXT); + binding.postSettingsInputDialogEditText.setInputType(InputType.TYPE_CLASS_TEXT); } if (!TextUtils.isEmpty(mCurrentInput)) { - mBinding.postSettingsInputDialogEditText.setText(mCurrentInput); + binding.postSettingsInputDialogEditText.setText(mCurrentInput); // move the cursor to the end - mBinding.postSettingsInputDialogEditText.setSelection(mCurrentInput.length()); + binding.postSettingsInputDialogEditText.setSelection(mCurrentInput.length()); } - mBinding.postSettingsInputDialogEditText.addTextChangedListener(this); + binding.postSettingsInputDialogEditText.addTextChangedListener(this); - mBinding.postSettingsInputDialogInputLayout.setHint(mTitle); + binding.postSettingsInputDialogInputLayout.setHint(mTitle); - mBinding.postSettingsInputDialogHint.setText(mHint); + binding.postSettingsInputDialogHint.setText(mHint); builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mCurrentInput = mBinding.postSettingsInputDialogEditText.getText().toString(); - if (mListener != null) { + Editable text = binding.postSettingsInputDialogEditText.getText(); + if (mListener != null && text != null) { + mCurrentInput = text.toString(); mListener.onInputUpdated(mCurrentInput); } } From 7e498ba1d0d13dd3a4fc91b921facb456a755ebe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:43:35 +0000 Subject: [PATCH 65/65] Bump androidx.webkit:webkit from 1.10.0 to 1.11.0 Bumps androidx.webkit:webkit from 1.10.0 to 1.11.0. --- updated-dependencies: - dependency-name: androidx.webkit:webkit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- WordPress/build.gradle | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 6b85e13e1d23..8ebe0ab44ae4 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -350,7 +350,7 @@ static def addBuildConfigFieldsFromPrefixedProperties(variant, properties, prefi } dependencies { - implementation 'androidx.webkit:webkit:1.10.0' + implementation 'androidx.webkit:webkit:1.11.0' implementation "androidx.navigation:navigation-compose:$androidxComposeNavigationVersion" compileOnly project(path: ':libs:annotations') ksp project(':libs:processors') diff --git a/build.gradle b/build.gradle index c56c5d175942..26d8c4a7e88d 100644 --- a/build.gradle +++ b/build.gradle @@ -60,7 +60,7 @@ ext { androidxSwipeToRefreshVersion = '1.1.0' androidxViewpager2Version = '1.0.0' androidxWorkManagerVersion = "2.9.0" - androidxWebkitVersion = '1.10.0' + androidxWebkitVersion = '1.11.0' androidxComposeMaterial3Version = '1.1.1' apacheCommonsTextVersion = '1.10.0' coilComposeVersion = '2.4.0'