Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Voice to Content] Launch edit post with AI content #20992

Merged
merged 15 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,26 @@ public static void viewBlogAdmin(Context context, SiteModel site) {
openUrlExternal(context, site.getAdminUrl());
}

public static void addNewPostWithContentFromAIForResult(
Activity activity,
SiteModel site,
boolean isPromo,
PagePostCreationSourcesDetail source,
final String content
) {
if (site == null) {
return;
}

Intent intent = new Intent(activity, EditPostActivity.class);
intent.putExtra(WordPress.SITE, site);
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PAGE, false);
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PROMO, isPromo);
intent.putExtra(AnalyticsUtils.EXTRA_CREATION_SOURCE_DETAIL, source);
intent.putExtra(EditPostActivityConstants.EXTRA_VOICE_CONTENT, content);
activity.startActivityForResult(intent, RequestCodes.EDIT_POST);
}

public static void addNewPostForResult(
Activity activity,
SiteModel site,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
private var postLoadingState: PostLoadingState = PostLoadingState.NONE
private var isXPostsCapable: Boolean? = null
private var onGetSuggestionResult: Consumer<String?>? = null
private var isVoiceContentSet = false

// For opening the context menu after permissions have been granted
private var menuView: View? = null
Expand Down Expand Up @@ -717,6 +718,7 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
}

isNewPost = state.getBoolean(EditPostActivityConstants.STATE_KEY_IS_NEW_POST, false)
isVoiceContentSet = state.getBoolean(EditPostActivityConstants.STATE_KEY_IS_NEW_POST, false)
irfano marked this conversation as resolved.
Show resolved Hide resolved
updatePostLoadingAndDialogState(
fromInt(
state.getInt(EditPostActivityConstants.STATE_KEY_POST_LOADING_STATE, 0)
Expand Down Expand Up @@ -1185,6 +1187,7 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
}
outState.putInt(EditPostActivityConstants.STATE_KEY_POST_LOADING_STATE, postLoadingState.value)
outState.putBoolean(EditPostActivityConstants.STATE_KEY_IS_NEW_POST, isNewPost)
outState.putBoolean(EditPostActivityConstants.STATE_KEY_IS_VOICE_CONTENT_SET, isVoiceContentSet)
outState.putBoolean(
EditPostActivityConstants.STATE_KEY_IS_PHOTO_PICKER_VISIBLE,
editorPhotoPicker?.isPhotoPickerShowing() ?: false
Expand Down Expand Up @@ -3526,6 +3529,20 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
// Start VM, load prompt and populate Editor with content after edit IS ready.
val promptId: Int = intent.getIntExtra(EditPostActivityConstants.EXTRA_PROMPT_ID, -1)
editorBloggingPromptsViewModel.start(siteModel, promptId)

updateVoiceContentIfNeeded()
}

private fun updateVoiceContentIfNeeded() {
// Check if voice content exists and this is a new post for a Gutenberg editor fragment
val content = intent.getStringExtra(EditPostActivityConstants.EXTRA_VOICE_CONTENT)
if (isNewPost && content != null && !isVoiceContentSet) {
irfano marked this conversation as resolved.
Show resolved Hide resolved
val gutenbergFragment = editorFragment as? GutenbergEditorFragment
gutenbergFragment?.let {
isVoiceContentSet = true
it.updateContent(content)
}
}
}

private fun logTemplateSelection() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ object EditPostActivityConstants{
const val EXTRA_PAGE_TEMPLATE = "pageTemplate"
const val EXTRA_PROMPT_ID = "extraPromptId"
const val EXTRA_ENTRY_POINT = "extraEntryPoint"
const val EXTRA_VOICE_CONTENT = "extra_voice_content"
const val STATE_KEY_EDITOR_FRAGMENT = "editorFragment"
const val STATE_KEY_DROPPED_MEDIA_URIS = "stateKeyDroppedMediaUri"
const val STATE_KEY_POST_LOCAL_ID = "stateKeyPostModelLocalId"
Expand All @@ -40,4 +41,5 @@ object EditPostActivityConstants{
const val STATE_KEY_MEDIA_CAPTURE_PATH = "stateKeyMediaCapturePath"
const val STATE_KEY_UNDO = "stateKeyUndo"
const val STATE_KEY_REDO = "stateKeyRedo"
const val STATE_KEY_IS_VOICE_CONTENT_SET = "stateKeyIsVoiceContentSet"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.wordpress.android.ui.voicetocontent

import org.wordpress.android.fluxc.model.SiteModel

sealed class VoiceToContentActionEvent {
data object Dismiss: VoiceToContentActionEvent()
data class LaunchEditPost(val site: SiteModel, val content: String) : VoiceToContentActionEvent()
data class LaunchExternalBrowser(val url: String) : VoiceToContentActionEvent()
data object RequestPermission : VoiceToContentActionEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,30 @@ import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.viewModels
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint
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
import com.google.android.material.bottomsheet.BottomSheetDialog
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.ActivityNavigator
import org.wordpress.android.ui.PagePostCreationSourcesDetail
import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.Dismiss
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.LaunchEditPost
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.LaunchExternalBrowser
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.RequestPermission
import org.wordpress.android.util.audio.IAudioRecorder.Companion.REQUIRED_RECORDING_PERMISSIONS
import javax.inject.Inject

@AndroidEntryPoint
Expand Down Expand Up @@ -78,17 +83,16 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
}

override fun onSlide(bottomSheet: View, slideOffset: Float) {
// Handle the slide offset if needed
// no op
}
})

// Disable touch interception by the bottom sheet to allow nested scrolling
// Disable touch interception by the bottom sheet to allow nested scrolling for landscape and small screens
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")
irfano marked this conversation as resolved.
Show resolved Hide resolved
dialog.setCanceledOnTouchOutside(cancelable)
}

Expand All @@ -105,16 +109,13 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
}

private fun observeViewModel() {
viewModel.requestPermission.observe(viewLifecycleOwner) {
requestAllPermissionsForRecording()
}

viewModel.dismiss.observe(viewLifecycleOwner) {
dismiss()
}

viewModel.onIneligibleForVoiceToContent.observe(viewLifecycleOwner) { url ->
launchIneligibleForVoiceToContent(url)
viewModel.actionEvent.observe(viewLifecycleOwner) { actionEvent ->
when(actionEvent) {
is LaunchEditPost -> launchEditPost(actionEvent)
is LaunchExternalBrowser -> launchIneligibleForVoiceToContent(actionEvent)
is RequestPermission -> requestAllPermissionsForRecording()
is Dismiss -> dismiss()
}
}
}

Expand Down Expand Up @@ -151,9 +152,21 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
.show()
}

private fun launchIneligibleForVoiceToContent(url: String) {
private fun launchIneligibleForVoiceToContent(event: LaunchExternalBrowser) {
context?.let {
activityNavigator.openIneligibleForVoiceToContent(it, url)
activityNavigator.openIneligibleForVoiceToContent(it, event.url)
}
}

private fun launchEditPost(event: LaunchEditPost) {
activity?.let {
ActivityLauncher.addNewPostWithContentFromAIForResult(
it,
event.site,
false,
PagePostCreationSourcesDetail.POST_FROM_MY_SITE,
event.content
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.wordpress.android.ui.voicetocontent

import android.content.pm.PackageManager
import android.util.Log
import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
Expand All @@ -17,6 +16,10 @@ 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.ui.voicetocontent.VoiceToContentActionEvent.Dismiss
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.LaunchEditPost
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.LaunchExternalBrowser
import org.wordpress.android.ui.voicetocontent.VoiceToContentActionEvent.RequestPermission
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
Expand Down Expand Up @@ -45,20 +48,14 @@ class VoiceToContentViewModel @Inject constructor(
private val prepareVoiceToContentUseCase: PrepareVoiceToContentUseCase,
private val logger: VoiceToContentTelemetry
) : ScopedViewModel(mainDispatcher) {
private val _requestPermission = MutableLiveData<Unit>()
val requestPermission = _requestPermission as LiveData<Unit>

private val _dismiss = MutableLiveData<Unit>()
val dismiss = _dismiss as LiveData<Unit>

private val _recordingUpdate = MutableLiveData<RecordingUpdate>()
val recordingUpdate: LiveData<RecordingUpdate> get() = _recordingUpdate

private val _onIneligibleForVoiceToContent = MutableLiveData<String>()
val onIneligibleForVoiceToContent = _onIneligibleForVoiceToContent as LiveData<String>
val recordingUpdate = _recordingUpdate as LiveData<RecordingUpdate>

private val _isCancelableOutsideTouch = MutableLiveData(true)
val isCancelableOutsideTouch: LiveData<Boolean> get() = _isCancelableOutsideTouch
val isCancelableOutsideTouch = _isCancelableOutsideTouch as LiveData<Boolean>

private val _actionEvent = MutableLiveData<VoiceToContentActionEvent>()
val actionEvent = _actionEvent as LiveData<VoiceToContentActionEvent>

private var isStarted = false

Expand Down Expand Up @@ -124,8 +121,6 @@ class VoiceToContentViewModel @Inject constructor(
stopRecording()
} else {
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")
}
}
}
Expand Down Expand Up @@ -182,16 +177,16 @@ class VoiceToContentViewModel @Inject constructor(
when (val result = voiceToContentUseCase.execute(site, file)) {
is VoiceToContentResult.Failure -> result.transitionToError()
is VoiceToContentResult.Success ->
Log.i(javaClass.simpleName, "***=> result is ${result.content}")
_actionEvent.postValue(LaunchEditPost(site, result.content))
}
_dismiss.postValue(Unit)
_actionEvent.postValue(Dismiss)
}
}

// Permissions
private fun onRequestPermission() {
logger.track(Stat.VOICE_TO_CONTENT_BUTTON_START_RECORDING_TAPPED)
_requestPermission.postValue(Unit)
_actionEvent.postValue(RequestPermission)
}

private fun hasAllPermissionsForRecording(): Boolean {
Expand Down Expand Up @@ -221,7 +216,7 @@ class VoiceToContentViewModel @Inject constructor(
private fun onClose() {
logger.track(Stat.VOICE_TO_CONTENT_BUTTON_CLOSE_TAPPED)
recordingUseCase.endRecordingSession()
_dismiss.postValue(Unit)
_actionEvent.postValue(Dismiss)
}

private fun onRetryTap() {
Expand All @@ -232,7 +227,7 @@ class VoiceToContentViewModel @Inject constructor(
private fun onLinkTap(url: String?) {
logger.track(Stat.VOICE_TO_CONTENT_BUTTON_UPGRADE_TAPPED)
url?.let {
_onIneligibleForVoiceToContent.postValue(it)
_actionEvent.postValue(LaunchExternalBrowser(it))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1690,6 +1690,11 @@ public void showNotice(String message) {
@Override public void onRedoPressed() {
}

@Override
public void updateContent(@Nullable CharSequence text) {
// not implemented for Aztec
}

private void onMediaTapped(@NonNull final AztecAttributes attrs, int naturalWidth, int naturalHeight,
final MediaType mediaType) {
if (mediaType == null || !isAdded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public abstract Pair<CharSequence, CharSequence> getTitleAndContent(CharSequence
public abstract void onUndoPressed();

public abstract void onRedoPressed();
public abstract void updateContent(CharSequence text);


public enum MediaType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,17 @@ public void setContent(CharSequence text) {
getGutenbergContainerFragment().setContent(postContent);
}

@Override
public void updateContent(@Nullable CharSequence text) {
if (text == null) {
text = "";
}

if (getGutenbergContainerFragment() != null) {
getGutenbergContainerFragment().onContentUpdate(text.toString());
}
}

public void setJetpackSsoEnabled(boolean jetpackSsoEnabled) {
mIsJetpackSsoEnabled = jetpackSsoEnabled;
}
Expand Down
Loading