From 7f4912ae2c3efce336546fc2ef3dd8bc12cf492c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 10 Sep 2020 14:32:49 -0300 Subject: [PATCH 001/343] added WPANdroid bridge interface OnStoryCreatorRequestListener --- .../wordpress/android/ui/posts/EditPostActivity.java | 11 +++++++++++ .../android/editor/EditorFragmentAbstract.java | 1 + .../editor/gutenberg/GutenbergContainerFragment.java | 3 +++ .../editor/gutenberg/GutenbergEditorFragment.java | 6 ++++++ libs/gutenberg-mobile | 2 +- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 48b48466de6c..b2a0c76c4b74 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -110,6 +110,7 @@ import org.wordpress.android.ui.ActivityId; import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.LocaleAwareActivity; +import org.wordpress.android.ui.PagePostCreationSourcesDetail; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog.PrivateAtCookieProgressDialogOnDismissListener; import org.wordpress.android.ui.RequestCodes; @@ -2952,6 +2953,16 @@ public void onTrackableEvent(TrackableEvent event, Map propertie mEditorTracker.trackEditorEvent(event, mEditorFragment.getEditorName(), properties); } + @Override public void onStoryComposerLoaderRequested(int postId) { + // TODO here trigger the StoryCreator in the listener, figure out which media ids the + // story block contains, etc. + + // ActivityLauncher.addNewStoryWithMediaIdsForResult + // TODO we'll create a new ActivityLauncher method that passes the actual block content for the Story, + // after having found it and deserialized from local repository + ActivityLauncher.addNewStoryForResult(this, getSite(), PagePostCreationSourcesDetail.NO_DETAIL); + } + // FluxC events @SuppressWarnings("unused") diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java index 84ddb0b4dc7a..fd8403e95d40 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java @@ -206,6 +206,7 @@ public interface EditorFragmentListener { void onGutenbergEditorSetStarterPageTemplatesTooltipShown(boolean tooltipShown); boolean onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); String getErrorMessageFromMedia(int mediaId); + void onStoryComposerLoaderRequested(int postId); } /** diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 97483ba500fd..33fdf0efa525 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -25,6 +25,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaEditorListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorRequestListener; import java.util.ArrayList; @@ -62,6 +63,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidRequestUnsupportedBlockFallbackListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onSPTTooltipShownEventListener, + OnStoryCreatorRequestListener onStoryCreatorRequestListener, boolean isDarkMode) { mWPAndroidGlueCode.attachToContainer( viewGroup, @@ -77,6 +79,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidRequestUnsupportedBlockFallbackListener, addMentionUtil, onSPTTooltipShownEventListener, + onStoryCreatorRequestListener, isDarkMode); } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 9d75659dead9..10e38c6c87e9 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -62,6 +62,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorRequestListener; import java.util.ArrayList; import java.util.HashMap; @@ -337,6 +338,11 @@ public boolean onRequestStarterPageTemplatesTooltipShown() { return mEditorFragmentListener.onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); } }, + new OnStoryCreatorRequestListener() { + @Override public void onRequestStoryCreatorLoad(int postId) { + mEditorFragmentListener.onStoryComposerLoaderRequested(postId); + } + }, GutenbergUtils.isDarkMode(getActivity())); // request dependency injection. Do this after setting min/max dimensions diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index e4825b2569e2..60088f818f04 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit e4825b2569e25b962f2527596580814a6c05ee59 +Subproject commit 60088f818f04a6a3eefca661da5220e7e2d0dbe0 From 0735ef64e1e51b6fa8834d2ae94f27cbfb435a50 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 11 Sep 2020 17:43:48 -0300 Subject: [PATCH 002/343] adapted the gutenberg bridge usage to receive Story block's mediaFiles and clientId from Gutenberg when block editing is requested --- .../android/ui/ActivityLauncher.java | 15 ++++++++++++++ .../wordpress/android/ui/RequestCodes.java | 1 + .../android/ui/posts/EditPostActivity.java | 13 +++++++++--- .../editor/EditorFragmentAbstract.java | 2 +- .../gutenberg/GutenbergContainerFragment.java | 8 ++++++-- .../gutenberg/GutenbergEditorFragment.java | 20 ++++++++++++++----- libs/gutenberg-mobile | 2 +- 7 files changed, 49 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index aabd6ed392c1..c24181cf873d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -724,6 +724,21 @@ public static void addNewStoryWithMediaUrisForResult( activity.startActivityForResult(intent, RequestCodes.CREATE_STORY); } + public static void editStoryWithMediaIdsForResult( + Activity activity, + SiteModel site, + long[] mediaIds + ) { + if (site == null) { + return; + } + + Intent intent = new Intent(activity, StoryComposerActivity.class); + intent.putExtra(WordPress.SITE, site); + intent.putExtra(MediaBrowserActivity.RESULT_IDS, mediaIds); + activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); + } + public static void editPostOrPageForResult(Activity activity, SiteModel site, PostModel post) { editPostOrPageForResult(new Intent(activity, EditPostActivity.class), activity, site, post.getId(), false); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/RequestCodes.java b/WordPress/src/main/java/org/wordpress/android/ui/RequestCodes.java index f41c0a7350a4..e7b9add340e8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/RequestCodes.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/RequestCodes.java @@ -63,4 +63,5 @@ public class RequestCodes { // Story creator public static final int CREATE_STORY = 8000; + public static final int EDIT_STORY = 8001; } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index b2a0c76c4b74..b40bfeb75692 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -2070,7 +2070,8 @@ public Fragment getItem(int position) { isSiteUsingWpComRestApi, WordPress.getUserAgent(), mTenorFeatureConfig.isEnabled(), - gutenbergPropsBuilder + gutenbergPropsBuilder, + RequestCodes.EDIT_STORY ); } else { // If gutenberg editor is not selected, default to Aztec. @@ -2953,14 +2954,20 @@ public void onTrackableEvent(TrackableEvent event, Map propertie mEditorTracker.trackEditorEvent(event, mEditorFragment.getEditorName(), properties); } - @Override public void onStoryComposerLoaderRequested(int postId) { + @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { // TODO here trigger the StoryCreator in the listener, figure out which media ids the // story block contains, etc. // ActivityLauncher.addNewStoryWithMediaIdsForResult // TODO we'll create a new ActivityLauncher method that passes the actual block content for the Story, // after having found it and deserialized from local repository - ActivityLauncher.addNewStoryForResult(this, getSite(), PagePostCreationSourcesDetail.NO_DETAIL); + + ArrayList tmpMediaIds = new ArrayList<>(); + for (Object mediaFile : mediaFiles) { + long mediaId = new Double(((HashMap)mediaFile).get("id").toString()).longValue(); + tmpMediaIds.add(mediaId); + } + ActivityLauncher.editStoryWithMediaIdsForResult(this, getSite(), ListUtils.toLongArray(tmpMediaIds)); } // FluxC events diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java index fd8403e95d40..b023d94911ee 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java @@ -206,7 +206,7 @@ public interface EditorFragmentListener { void onGutenbergEditorSetStarterPageTemplatesTooltipShown(boolean tooltipShown); boolean onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); String getErrorMessageFromMedia(int mediaId); - void onStoryComposerLoaderRequested(int postId); + void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId); } /** diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 33fdf0efa525..03f7d66228d9 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -25,7 +25,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaEditorListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; import java.util.ArrayList; @@ -63,7 +63,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidRequestUnsupportedBlockFallbackListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onSPTTooltipShownEventListener, - OnStoryCreatorRequestListener onStoryCreatorRequestListener, + OnStoryCreatorLoadRequestListener onStoryCreatorRequestListener, boolean isDarkMode) { mWPAndroidGlueCode.attachToContainer( viewGroup, @@ -205,6 +205,10 @@ public void replaceUnsupportedBlock(String content, String blockId) { mWPAndroidGlueCode.replaceUnsupportedBlock(content, blockId); } + public void replaceStoryEditedBlock(String mediaFiles, String blockId) { + mWPAndroidGlueCode.replaceStoryEditedBlock(mediaFiles, blockId); + } + public void updateTheme(Bundle editorTheme) { mWPAndroidGlueCode.updateTheme(editorTheme); } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 10e38c6c87e9..1051bbc35ce5 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -62,7 +62,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; import java.util.ArrayList; import java.util.HashMap; @@ -91,6 +91,7 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements private static final String ARG_SITE_USER_AGENT = "param_user_agent"; private static final String ARG_TENOR_ENABLED = "param_tenor_enabled"; private static final String ARG_GUTENBERG_PROPS_BUILDER = "param_gutenberg_props_builder"; + private static final String ARG_STORY_EDITOR_REQUEST_CODE = "param_sory_editor_request_code"; private static final int CAPTURE_PHOTO_PERMISSION_REQUEST_CODE = 101; private static final int CAPTURE_VIDEO_PERMISSION_REQUEST_CODE = 102; @@ -108,6 +109,7 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements private Runnable mInvalidateOptionsRunnable; private LiveTextWatcher mTextWatcher = new LiveTextWatcher(); + private int mStoryBlockEditRequestCode; // pointer (to the Gutenberg container fragment) that outlives this fragment's Android lifecycle. The retained // fragment can be alive and accessible even before it gets attached to an activity. @@ -135,7 +137,8 @@ public static GutenbergEditorFragment newInstance(String title, boolean isSiteUsingWpComRestApi, String userAgent, boolean tenorEnabled, - GutenbergPropsBuilder gutenbergPropsBuilder) { + GutenbergPropsBuilder gutenbergPropsBuilder, + int storyBlockEditRequestCode) { GutenbergEditorFragment fragment = new GutenbergEditorFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM_TITLE, title); @@ -151,6 +154,7 @@ public static GutenbergEditorFragment newInstance(String title, args.putString(ARG_SITE_USER_AGENT, userAgent); args.putBoolean(ARG_TENOR_ENABLED, tenorEnabled); args.putParcelable(ARG_GUTENBERG_PROPS_BUILDER, gutenbergPropsBuilder); + args.putInt(ARG_STORY_EDITOR_REQUEST_CODE, storyBlockEditRequestCode); fragment.setArguments(args); return fragment; } @@ -197,6 +201,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa if (getArguments() != null) { mIsNewPost = getArguments().getBoolean(ARG_IS_NEW_POST); + mStoryBlockEditRequestCode = getArguments().getInt(ARG_STORY_EDITOR_REQUEST_CODE); } ViewGroup gutenbergContainer = view.findViewById(R.id.gutenberg_container); @@ -338,9 +343,9 @@ public boolean onRequestStarterPageTemplatesTooltipShown() { return mEditorFragmentListener.onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); } }, - new OnStoryCreatorRequestListener() { - @Override public void onRequestStoryCreatorLoad(int postId) { - mEditorFragmentListener.onStoryComposerLoaderRequested(postId); + new OnStoryCreatorLoadRequestListener() { + @Override public void onRequestStoryCreatorLoad(ArrayList mediaFiles, String blockId) { + mEditorFragmentListener.onStoryComposerLoadRequested(mediaFiles, blockId); } }, GutenbergUtils.isDarkMode(getActivity())); @@ -425,6 +430,11 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d } else { trackWebViewClosed("dismiss"); } + } else if (requestCode == mStoryBlockEditRequestCode) { + // handle edited block content + String blockId = data.getStringExtra(WPGutenbergWebViewActivity.ARG_BLOCK_ID); + String content = data.getStringExtra(WPGutenbergWebViewActivity.ARG_BLOCK_CONTENT); + getGutenbergContainerFragment().replaceStoryEditedBlock(content, blockId); } } diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 60088f818f04..4def44feb730 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 60088f818f04a6a3eefca661da5220e7e2d0dbe0 +Subproject commit 4def44feb7302a36eda62f6a459e51d08449750c From ae9a0c3cf617d2da1ded3c2daf93e924654f7738 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 15 Sep 2020 08:25:51 -0300 Subject: [PATCH 003/343] introducing StoriesPrefs to save serialized Stories slides so these can be retrieved and edited later Gutenberg --- .../android/ui/posts/EditPostActivity.java | 16 +++- .../media/AddLocalMediaToPostUseCase.kt | 8 ++ .../posts/editor/media/EditorMediaListener.kt | 3 + .../stories/SaveStoryGutenbergBlockUseCase.kt | 25 +++++ .../ui/stories/StoryComposerActivity.kt | 5 + .../media/StoryMediaSaveUploadBridge.kt | 27 ++++++ .../android/ui/stories/prefs/StoriesPrefs.kt | 96 +++++++++++++++++++ libs/stories-android | 2 +- 8 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index b40bfeb75692..0fc0c12b7135 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -110,7 +110,6 @@ import org.wordpress.android.ui.ActivityId; import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.LocaleAwareActivity; -import org.wordpress.android.ui.PagePostCreationSourcesDetail; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog.PrivateAtCookieProgressDialogOnDismissListener; import org.wordpress.android.ui.RequestCodes; @@ -157,6 +156,7 @@ import org.wordpress.android.ui.prefs.AppPrefs; import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; +import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.ui.uploads.PostEvents; import org.wordpress.android.ui.uploads.UploadService; import org.wordpress.android.ui.uploads.UploadUtils; @@ -2963,10 +2963,17 @@ public void onTrackableEvent(TrackableEvent event, Map propertie // after having found it and deserialized from local repository ArrayList tmpMediaIds = new ArrayList<>(); + boolean allStorySlidesAreEditable = true; for (Object mediaFile : mediaFiles) { - long mediaId = new Double(((HashMap)mediaFile).get("id").toString()).longValue(); + long mediaId = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); + if (allStorySlidesAreEditable + && !StoriesPrefs.isValidSlide(this, getImmutablePost().getLocalSiteId(), mediaId)) { + // flag this as soon as we find one media item not being really editable + allStorySlidesAreEditable = false; + } tmpMediaIds.add(mediaId); } + // TODO pass the allStorySlidesAreEditable boolean flag make sure to show the warning dialog ActivityLauncher.editStoryWithMediaIdsForResult(this, getSite(), ListUtils.toLongArray(tmpMediaIds)); } @@ -3196,6 +3203,11 @@ public void syncPostObjectWithUiAndSaveIt(@Nullable OnPostUpdatedFromUIListener WPMediaUtils.advertiseImageOptimization(this, listener::invoke); } + @Override + public void onMediaModelsCreatedFromOptimizedUris(@NotNull Map oldUriToMediaModels) { + // no op - we're not doing any special handling on MediaModels in EditPostActivity + } + @Override public Consumer getExceptionLogger() { return (Exception e) -> AppLog.e(T.EDITOR, e); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt index 06129dd43457..ccb97c3de05c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt @@ -67,6 +67,14 @@ class AddLocalMediaToPostUseCase @Inject constructor( optimizeMediaResult.optimizedMediaUris ) + // here we pass a map of "old" (before optimisation) Uris to the new MediaModels which contain + // both the mediaModel ids and the optimized media URLs. + // this way, the listener will be able to process from other models potining to the old URLs + // and make any needed updates + editorMediaListener.onMediaModelsCreatedFromOptimizedUris( + uriList.zip(createMediaModelsResult.mediaModels).toMap() + ) + // Add media to editor and optionally initiate upload addToEditorAndOptionallyUpload(createMediaModelsResult.mediaModels, editorMediaListener, doUploadAfterAdding) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt index bb8254f726d7..e9fadf48d011 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt @@ -1,5 +1,7 @@ package org.wordpress.android.ui.posts.editor.media +import android.net.Uri +import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel import org.wordpress.android.ui.posts.EditPostActivity.OnPostUpdatedFromUIListener import org.wordpress.android.util.helpers.MediaFile @@ -8,5 +10,6 @@ interface EditorMediaListener { fun appendMediaFiles(mediaFiles: Map) fun syncPostObjectWithUiAndSaveIt(listener: OnPostUpdatedFromUIListener? = null) fun advertiseImageOptimization(listener: () -> Unit) + fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) fun getImmutablePost(): PostImmutableModel } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index beb2107a6659..64d8976bb0ef 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -1,8 +1,10 @@ package org.wordpress.android.ui.stories import com.google.gson.Gson +import org.wordpress.android.WordPress import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.ui.posts.EditPostRepository +import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -57,6 +59,29 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { id = mediaFile.mediaId.toInt() link = mediaFile.fileURL url = mediaFile.fileURL + + // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. + val localIdKey = mediaFile.id.toLong() + val remoteIdKey = mediaFile.mediaId.toLong() + val localSiteId = post.localSiteId.toLong() + StoriesPrefs.getSlide( + WordPress.getContext(), + localSiteId, + localIdKey + )?.let { + StoriesPrefs.saveSlide( + WordPress.getContext(), + localSiteId, + remoteIdKey, // use the new mediaId as key + it + ) + // now delete the old entry + StoriesPrefs.deleteSlide( + WordPress.getContext(), + localSiteId, + localIdKey + ) + } } post.setContent(createGBStoryBlockStringFromJson(requireNotNull(storyBlockData))) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 1feab1a932e4..c1881f931c31 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -26,6 +26,7 @@ import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.PostStore @@ -343,6 +344,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), WPMediaUtils.advertiseImageOptimization(this) { listener.invoke() } } + override fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) { + // no op - we're not doing any special handling while composing, only when saving in the UploadBridge + } + private fun updateAddingMediaToStoryComposerProgressDialogState(uiState: ProgressDialogUiState) { addingMediaToEditorProgressDialog = progressDialogHelper .updateProgressDialogState(this, addingMediaToEditorProgressDialog, uiState, uiHelpers) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 1b16d1c9dc9b..b3c65c86c1f7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.launch import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.WordPress +import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.modules.UI_THREAD @@ -28,6 +29,7 @@ import org.wordpress.android.ui.posts.editor.media.EditorMediaListener import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase import org.wordpress.android.ui.stories.StoriesTrackerHelper import org.wordpress.android.ui.stories.StoryComposerActivity +import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.ui.uploads.UploadServiceFacade import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.NetworkUtilsWrapper @@ -170,4 +172,29 @@ class StoryMediaSaveUploadBridge @Inject constructor( override fun advertiseImageOptimization(listener: () -> Unit) { // no op } + + override fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) { + // in order to support Story editing capabilities, we save a serialized version of the Story slides + // after their composedFrameFiles have been processed. + + // here we change the ids on the actual StoryFrameItems, and also update the flattened / composed image + // urls with the new URLs which may have been replaced after image optimization + for (story in StoryRepository.getImmutableStories()) { + // find the MediaModel for a given Uri from composedFrameFile + for (frame in story.frames) { + // if the old URI in frame.composedFrameFile exists as a key in the passed map, then update that + // value with the new (probably optimized) URL and also keep track of the new id. + val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) + mediaModel?.let { + StoriesPrefs.saveSlide( + appContext, + mediaModel.localSiteId.toLong(), + mediaModel.id.toLong(), // use the local id to save the original, will be replaced later + // with mediaModel.mediaId after uploading to the remote site + frame + ) + } + } + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt new file mode 100644 index 000000000000..ae0c94f55187 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -0,0 +1,96 @@ +package org.wordpress.android.ui.stories.prefs + +import android.content.Context +import android.net.Uri +import androidx.preference.PreferenceManager +import com.wordpress.stories.compose.story.StoryFrameItem +import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource +import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource +import com.wordpress.stories.compose.story.StorySerializerUtils + +object StoriesPrefs { + private const val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" + + private fun buildSlideKey(siteId: Long, mediaId: Long): String { + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + mediaId.toString() + } + + private fun checkSlideIdExists(context: Context, siteId: Long, mediaId: Long): Boolean { + val slideIdKey = buildSlideKey(siteId, mediaId) + return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) + } + + private fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: Long): Boolean { + val storyFrameItem: StoryFrameItem? = getSlide(context, siteId, mediaId) + storyFrameItem?.let { frame -> + // now check the background media exists or is accessible on this device + frame.source.let { source -> + if (source is FileBackgroundSource) { + source.file?.let { + return it.exists() + } ?: return false + } else if (source is UriBackgroundSource) { + source.contentUri?.let { + return isUriAccessible(it, context) + } ?: return false + } + } + } + return false + } + + private fun isUriAccessible(uri: Uri, context: Context): Boolean { + if (uri.toString().startsWith("http")) { + // TODO: assume it'll be accessible - we'll figure out later + // potentially force external download using MediaUtils.downloadExternalMedia() here to ensure + return true + } + try { + val inputStream = context.contentResolver.openInputStream(uri) + if (inputStream != null) { + inputStream.close() + return true + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return false + } + + private fun saveSlide(context: Context, slideIdKey: String, storySlideJson: String) { + val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() + editor.putString(slideIdKey, storySlideJson) + editor.apply() + } + + @JvmStatic + fun isValidSlide(context: Context, siteId: Long, mediaId: Long): Boolean { + return checkSlideIdExists(context, siteId, mediaId) && + checkSlideOriginalBackgroundMediaExists(context, siteId, mediaId) + } + + private fun getSlideJson(context: Context, siteId: Long, mediaId: Long): String? { + val slideIdKey = buildSlideKey(siteId, mediaId) + return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) + } + + fun getSlide(context: Context, siteId: Long, mediaId: Long): StoryFrameItem? { + val jsonSlide = getSlideJson(context, siteId, mediaId) + jsonSlide?.let { + return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) + } ?: return null + } + + fun saveSlide(context: Context, siteId: Long, mediaId: Long, storyFrameItem: StoryFrameItem) { + val slideIdKey = buildSlideKey(siteId, mediaId) +// storyFrameItem.id = slideIdKey + saveSlide(context, slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) + } + + fun deleteSlide(context: Context, siteId: Long, mediaId: Long) { + val slideIdKey = buildSlideKey(siteId, mediaId) + val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() + editor.remove(slideIdKey) + editor.apply() + } +} diff --git a/libs/stories-android b/libs/stories-android index 03a40da7be5a..c9ffa96cb84a 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 03a40da7be5ab7da8fce7c4895227c193e04b9b9 +Subproject commit c9ffa96cb84ae77b43f32ca87d77d2909d78f201 From 3298f038c0e6e6e0cf330db48b223bed244a8909 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 15 Sep 2020 15:56:22 -0300 Subject: [PATCH 004/343] added logic in onStoryComposerLoadRequested() to load a Story in the composer after tapping on a Story block for editing --- WordPress/src/main/AndroidManifest.xml | 1 + .../android/ui/ActivityLauncher.java | 26 ++++++++- .../android/ui/posts/EditPostActivity.java | 55 ++++++++++++++----- .../stories/SaveStoryGutenbergBlockUseCase.kt | 1 + .../ui/stories/StoryComposerActivity.kt | 11 +++- .../ui/stories/StoryComposerViewModel.kt | 8 ++- .../media/StoryMediaSaveUploadBridge.kt | 5 +- .../android/ui/stories/prefs/StoriesPrefs.kt | 8 ++- libs/stories-android | 2 +- 9 files changed, 93 insertions(+), 24 deletions(-) diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 1e800e40ad02..c9eba1929991 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -227,6 +227,7 @@ android:name=".ui.stories.StoryComposerActivity" android:label="@string/app_name" android:screenOrientation="portrait" + android:launchMode="singleTop" android:theme="@style/WordPress.Stories.Immersive"> diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index 5e4a0ac6225f..725acb28a390 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -13,6 +13,8 @@ import androidx.core.app.TaskStackBuilder; import androidx.fragment.app.Fragment; +import com.wordpress.stories.compose.ComposeLoopFrameActivity; + import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; @@ -93,6 +95,7 @@ import java.util.List; import java.util.Map; +import static com.wordpress.stories.util.BundleUtilsKt.KEY_STORY_INDEX; import static org.wordpress.android.analytics.AnalyticsTracker.ACTIVITY_LOG_ACTIVITY_ID_KEY; import static org.wordpress.android.analytics.AnalyticsTracker.Stat.POST_LIST_ACCESS_ERROR; import static org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_ARTICLE_DETAIL_REBLOGGED; @@ -102,6 +105,7 @@ import static org.wordpress.android.login.LoginMode.WPCOM_LOGIN_ONLY; import static org.wordpress.android.ui.media.MediaBrowserActivity.ARG_BROWSER_TYPE; import static org.wordpress.android.ui.pages.PagesActivityKt.EXTRA_PAGE_REMOTE_ID_KEY; +import static org.wordpress.android.ui.stories.StoryComposerActivity.KEY_LAUNCHED_FROM_GUTENBERG; import static org.wordpress.android.viewmodel.activitylog.ActivityLogDetailViewModelKt.ACTIVITY_LOG_ID_KEY; public class ActivityLauncher { @@ -723,7 +727,8 @@ public static void addNewStoryWithMediaUrisForResult( public static void editStoryWithMediaIdsForResult( Activity activity, SiteModel site, - long[] mediaIds + long[] mediaIds, + boolean launchingFromGutenberg ) { if (site == null) { return; @@ -735,6 +740,25 @@ public static void editStoryWithMediaIdsForResult( activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); } + public static void editStoryForResult( + Activity activity, + SiteModel site, + int storyIndex, + boolean allStorySlidesAreEditable, + boolean launchedFromGutenberg + ) { + if (site == null) { + return; + } + + // TODO pass the allStorySlidesAreEditable boolean flag make sure to show the warning dialog + Intent intent = new Intent(activity, StoryComposerActivity.class); + intent.putExtra(WordPress.SITE, site); + intent.putExtra(KEY_STORY_INDEX, storyIndex); + intent.putExtra(KEY_LAUNCHED_FROM_GUTENBERG, launchedFromGutenberg); + activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); + } + public static void editPostOrPageForResult(Activity activity, SiteModel site, PostModel post) { editPostOrPageForResult(new Intent(activity, EditPostActivity.class), activity, site, post.getId(), false); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index efbe227936a6..58aabbe17e88 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -40,6 +40,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; +import com.wordpress.stories.compose.story.StoryFrameItem; +import com.wordpress.stories.compose.story.StoryRepository; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -2999,26 +3001,53 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - // TODO here trigger the StoryCreator in the listener, figure out which media ids the - // story block contains, etc. - - // ActivityLauncher.addNewStoryWithMediaIdsForResult - // TODO we'll create a new ActivityLauncher method that passes the actual block content for the Story, - // after having found it and deserialized from local repository - - ArrayList tmpMediaIds = new ArrayList<>(); + ArrayList tmpMediaIdsLong = new ArrayList<>(); + ArrayList tmpMediaIdsString = new ArrayList<>(); boolean allStorySlidesAreEditable = true; for (Object mediaFile : mediaFiles) { - long mediaId = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); + long mediaIdLong = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); + String mediaIdString = String.valueOf(mediaIdLong); // convert back after stripping the decimals in original Double if (allStorySlidesAreEditable - && !StoriesPrefs.isValidSlide(this, getImmutablePost().getLocalSiteId(), mediaId)) { + && !StoriesPrefs.isValidSlide(this, mSite.getSiteId(), mediaIdLong)) { // flag this as soon as we find one media item not being really editable allStorySlidesAreEditable = false; } - tmpMediaIds.add(mediaId); + tmpMediaIdsLong.add(mediaIdLong); + tmpMediaIdsString.add(mediaIdString); + } + + // now look for a Story in the StoryRepository that has all these frames and, if not found, let's + // just build the Story object ourselves to keep these files arrangement + int storyIndex = StoryRepository.findStoryContainingStoryFrameItemsByIds(tmpMediaIdsString); + if (storyIndex == StoryRepository.DEFAULT_NONE_SELECTED) { + // the StoryRepository didn' have it but we have editable serialized slides so, + // create a new Story from scratch with these deserialized StoryFrameItems + StoryRepository.loadStory(storyIndex); + storyIndex = StoryRepository.currentStoryIndex; + for (String mediaId : tmpMediaIdsString) { + StoryFrameItem storyFrameItem = StoriesPrefs.getSlide(this, mSite.getSiteId(), Long.parseLong(mediaId)); + if (storyFrameItem != null) { + StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); + } else { + allStorySlidesAreEditable = false; + // create a new frame using the actual uploaded flattened media as a background + List mediaModelList = mMediaStore.getSiteMediaWithIds(mSite, tmpMediaIdsLong); + for (MediaModel mediaModel : mediaModelList) { + storyFrameItem = StoryFrameItem.Companion.getNewStoryFrameItemFromUri( + Uri.parse(mediaModel.getUrl()), + mediaModel.isVideo() + ); + storyFrameItem.setId(String.valueOf(mediaModel.getMediaId())); + StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); + } + } + // Story instance re-created! Load it + ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); + } + } else { + // Story found! Load it + ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); } - // TODO pass the allStorySlidesAreEditable boolean flag make sure to show the warning dialog - ActivityLauncher.editStoryWithMediaIdsForResult(this, getSite(), ListUtils.toLongArray(tmpMediaIds)); } // FluxC events diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 64d8976bb0ef..6ff3b070d04e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -69,6 +69,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { localSiteId, localIdKey )?.let { + it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID StoriesPrefs.saveSlide( WordPress.getContext(), localSiteId, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index a418eae79202..2201e797d251 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -1,5 +1,6 @@ package org.wordpress.android.ui.stories +import android.app.Activity import android.app.PendingIntent import android.app.ProgressDialog import android.content.Intent @@ -26,6 +27,7 @@ import org.wordpress.android.R.id import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED +import org.wordpress.android.editor.EditorImageMetaData import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel @@ -101,6 +103,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), const val STATE_KEY_POST_LOCAL_ID = "state_key_post_model_local_id" const val STATE_KEY_EDITOR_SESSION_DATA = "stateKeyEditorSessionData" const val KEY_POST_LOCAL_ID = "key_post_model_local_id" + const val KEY_LAUNCHED_FROM_GUTENBERG = "key_launched_from_gutenberg" const val UNUSED_KEY = "unused_key" const val BASE_FRAME_MEDIA_ERROR_NOTIFICATION_ID: Int = 72300 } @@ -396,7 +399,13 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } override fun onStoryDiscarded() { - viewModel.onStoryDiscarded() + val launchedFromGutenberg = intent.getBooleanExtra(KEY_LAUNCHED_FROM_GUTENBERG, false) + viewModel.onStoryDiscarded(!launchedFromGutenberg) + + if (launchedFromGutenberg) { + setResult(Activity.RESULT_CANCELED) + finish() + } } private fun openPrepublishingBottomSheet() { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerViewModel.kt index eefab234efb8..0e79012021b3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerViewModel.kt @@ -112,9 +112,11 @@ class StoryComposerViewModel @Inject constructor( outState.putSerializable(StoryComposerActivity.STATE_KEY_EDITOR_SESSION_DATA, postEditorAnalyticsSession) } - fun onStoryDiscarded() { - // delete empty post from database - dispatcher.dispatch(PostActionBuilder.newRemovePostAction(editPostRepository.getEditablePost())) + fun onStoryDiscarded(deleteDiscardedPost: Boolean) { + if (deleteDiscardedPost) { + // delete empty post from database + dispatcher.dispatch(PostActionBuilder.newRemovePostAction(editPostRepository.getEditablePost())) + } postEditorAnalyticsSession.setOutcome(CANCEL) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index b3c65c86c1f7..a1829d2daf96 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -186,10 +186,11 @@ class StoryMediaSaveUploadBridge @Inject constructor( // value with the new (probably optimized) URL and also keep track of the new id. val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) mediaModel?.let { + frame.id = it.id.toString() StoriesPrefs.saveSlide( appContext, - mediaModel.localSiteId.toLong(), - mediaModel.id.toLong(), // use the local id to save the original, will be replaced later + it.localSiteId.toLong(), + it.id.toLong(), // use the local id to save the original, will be replaced later // with mediaModel.mediaId after uploading to the remote site frame ) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index ae0c94f55187..fbd94db60fb2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -15,12 +15,14 @@ object StoriesPrefs { return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + mediaId.toString() } - private fun checkSlideIdExists(context: Context, siteId: Long, mediaId: Long): Boolean { + @JvmStatic + fun checkSlideIdExists(context: Context, siteId: Long, mediaId: Long): Boolean { val slideIdKey = buildSlideKey(siteId, mediaId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - private fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: Long): Boolean { + @JvmStatic + fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: Long): Boolean { val storyFrameItem: StoryFrameItem? = getSlide(context, siteId, mediaId) storyFrameItem?.let { frame -> // now check the background media exists or is accessible on this device @@ -74,6 +76,7 @@ object StoriesPrefs { return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) } + @JvmStatic fun getSlide(context: Context, siteId: Long, mediaId: Long): StoryFrameItem? { val jsonSlide = getSlideJson(context, siteId, mediaId) jsonSlide?.let { @@ -83,7 +86,6 @@ object StoriesPrefs { fun saveSlide(context: Context, siteId: Long, mediaId: Long, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) -// storyFrameItem.id = slideIdKey saveSlide(context, slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } diff --git a/libs/stories-android b/libs/stories-android index ae06c31785a2..22b8d57c9ae5 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit ae06c31785a2e124566a20c9c8639bc62552856f +Subproject commit 22b8d57c9ae5d48849f7f54f986dfa392e8c43a8 From d5ba9a3ad7b127700e2b2e81f5d72a4af3d08d76 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 15 Sep 2020 15:58:55 -0300 Subject: [PATCH 005/343] removed unused import --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 2201e797d251..4f827f055e92 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -27,7 +27,6 @@ import org.wordpress.android.R.id import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED -import org.wordpress.android.editor.EditorImageMetaData import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel From 556c7ccccd0bef6a529ae7a426679a131f7cf6fb Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 15 Sep 2020 23:46:02 -0300 Subject: [PATCH 006/343] fixed bug that had the Activity start as many times as slides where added --- WordPress/src/main/AndroidManifest.xml | 1 - .../org/wordpress/android/ui/posts/EditPostActivity.java | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index c9eba1929991..1e800e40ad02 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -227,7 +227,6 @@ android:name=".ui.stories.StoryComposerActivity" android:label="@string/app_name" android:screenOrientation="portrait" - android:launchMode="singleTop" android:theme="@style/WordPress.Stories.Immersive"> diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 58aabbe17e88..0c1917ffff87 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3041,13 +3041,10 @@ public void onTrackableEvent(TrackableEvent event, Map propertie StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); } } - // Story instance re-created! Load it - ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); } - } else { - // Story found! Load it - ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); } + // Story instance loaded or re-created! Load it + ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); } // FluxC events From 667a8dbeee64a34e79ec43b76fbdc6c7400b4165 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 08:32:45 -0300 Subject: [PATCH 007/343] removed unused import, removed comment --- .../main/java/org/wordpress/android/ui/ActivityLauncher.java | 2 -- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index 725acb28a390..df9c7833fae5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -13,8 +13,6 @@ import androidx.core.app.TaskStackBuilder; import androidx.fragment.app.Fragment; -import com.wordpress.stories.compose.ComposeLoopFrameActivity; - import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 0c1917ffff87..a21b95426895 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3006,7 +3006,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie boolean allStorySlidesAreEditable = true; for (Object mediaFile : mediaFiles) { long mediaIdLong = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); - String mediaIdString = String.valueOf(mediaIdLong); // convert back after stripping the decimals in original Double + String mediaIdString = String.valueOf(mediaIdLong); if (allStorySlidesAreEditable && !StoriesPrefs.isValidSlide(this, mSite.getSiteId(), mediaIdLong)) { // flag this as soon as we find one media item not being really editable From 34f858e6cb6dcbe248cfc56400d1a7729f41ad5b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 09:00:55 -0300 Subject: [PATCH 008/343] fixed loading flattened slides for editing --- .../org/wordpress/android/ui/posts/EditPostActivity.java | 7 ++++--- libs/stories-android | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index a21b95426895..c96cfe4dc250 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3001,7 +3001,6 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - ArrayList tmpMediaIdsLong = new ArrayList<>(); ArrayList tmpMediaIdsString = new ArrayList<>(); boolean allStorySlidesAreEditable = true; for (Object mediaFile : mediaFiles) { @@ -3012,7 +3011,6 @@ public void onTrackableEvent(TrackableEvent event, Map propertie // flag this as soon as we find one media item not being really editable allStorySlidesAreEditable = false; } - tmpMediaIdsLong.add(mediaIdLong); tmpMediaIdsString.add(mediaIdString); } @@ -3030,7 +3028,10 @@ public void onTrackableEvent(TrackableEvent event, Map propertie StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); } else { allStorySlidesAreEditable = false; - // create a new frame using the actual uploaded flattened media as a background + + // for this missing frame we'll create a new frame using the actual uploaded flattened media + ArrayList tmpMediaIdsLong = new ArrayList<>(); + tmpMediaIdsLong.add(Long.parseLong(mediaId)); List mediaModelList = mMediaStore.getSiteMediaWithIds(mSite, tmpMediaIdsLong); for (MediaModel mediaModel : mediaModelList) { storyFrameItem = StoryFrameItem.Companion.getNewStoryFrameItemFromUri( diff --git a/libs/stories-android b/libs/stories-android index 22b8d57c9ae5..821a8cd96c89 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 22b8d57c9ae5d48849f7f54f986dfa392e8c43a8 +Subproject commit 821a8cd96c8991533476ce5b6b11c720009d808f From e27fea5c6213bf8669b6b1c50b4fc8b7d2340adb Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 09:45:49 -0300 Subject: [PATCH 009/343] fixed and added new test for onStoryDiscarded --- .../ui/stories/StoryComposerViewModelTest.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/StoryComposerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/StoryComposerViewModelTest.kt index 33127f3c799a..e10f3519b2cc 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/StoryComposerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/StoryComposerViewModelTest.kt @@ -192,15 +192,25 @@ class StoryComposerViewModelTest : BaseUnitTest() { } @Test - fun `If onStoryDiscarded is called then the post is removed with the dispatcher`() { + fun `If onStoryDiscarded is called then the post is removed with the dispatcher when deleteDiscardedPost true `() { // act viewModel.start(site, editPostRepository, LocalId(0), mock(), mock()) - viewModel.onStoryDiscarded() + viewModel.onStoryDiscarded(deleteDiscardedPost = true) // assert verify(dispatcher, times(1)).dispatch(any>()) } + @Test + fun `If onStoryDiscarded is called then the post is not removed when deleteDiscardedPost false `() { + // act + viewModel.start(site, editPostRepository, LocalId(0), mock(), mock()) + viewModel.onStoryDiscarded(deleteDiscardedPost = false) + + // assert + verify(dispatcher, times(0)).dispatch(any>()) + } + @Test fun `verify that triggering onStorySaveButtonPressed will trigger the associated openPrepublishingBottomSheet`() { // act From a7be9d3a5553e7ef1f7c34ac4381baf348b60aa3 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 10:22:48 -0300 Subject: [PATCH 010/343] updated stories lib hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 821a8cd96c89..add3c28aaf69 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 821a8cd96c8991533476ce5b6b11c720009d808f +Subproject commit add3c28aaf69ebb401a8036eeae019923be00adc From 2bc41c93b6dd67629e90cdfbb23366ffae564129 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 10:53:17 -0300 Subject: [PATCH 011/343] using site localid for slide retrieval from StoriesPrefs --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index c96cfe4dc250..74fc61e9dd2a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3007,7 +3007,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie long mediaIdLong = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); String mediaIdString = String.valueOf(mediaIdLong); if (allStorySlidesAreEditable - && !StoriesPrefs.isValidSlide(this, mSite.getSiteId(), mediaIdLong)) { + && !StoriesPrefs.isValidSlide(this, mSite.getId(), mediaIdLong)) { // flag this as soon as we find one media item not being really editable allStorySlidesAreEditable = false; } @@ -3023,7 +3023,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie StoryRepository.loadStory(storyIndex); storyIndex = StoryRepository.currentStoryIndex; for (String mediaId : tmpMediaIdsString) { - StoryFrameItem storyFrameItem = StoriesPrefs.getSlide(this, mSite.getSiteId(), Long.parseLong(mediaId)); + StoryFrameItem storyFrameItem = StoriesPrefs.getSlide(this, mSite.getId(), Long.parseLong(mediaId)); if (storyFrameItem != null) { StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); } else { From 9ad4a87d2039d2ee48b112697c1112a9e7f8fde9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 11:10:21 -0300 Subject: [PATCH 012/343] updated stories lib commit hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index add3c28aaf69..56deb4537e1d 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit add3c28aaf69ebb401a8036eeae019923be00adc +Subproject commit 56deb4537e1dabc8551231fb5fc00b94e7df9131 From e755a7413e419ef461e5d8f0aa8e6057e2880b64 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 11:45:03 -0300 Subject: [PATCH 013/343] introducing LocalMediaId and RemoteMediaId data classes to enforce meaning, plus using a differentiated prefix when buildng keys to avoid the chance of local/remote overlapping ids --- .../android/ui/posts/EditPostActivity.java | 9 +++- .../stories/SaveStoryGutenbergBlockUseCase.kt | 14 ++--- .../media/StoryMediaSaveUploadBridge.kt | 6 ++- .../android/ui/stories/prefs/StoriesPrefs.kt | 52 ++++++++++++++----- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 74fc61e9dd2a..aa8a9b8068ed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -161,6 +161,7 @@ import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.prefs.StoriesPrefs; +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId; import org.wordpress.android.ui.uploads.PostEvents; import org.wordpress.android.ui.uploads.UploadService; import org.wordpress.android.ui.uploads.UploadUtils; @@ -3007,7 +3008,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie long mediaIdLong = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); String mediaIdString = String.valueOf(mediaIdLong); if (allStorySlidesAreEditable - && !StoriesPrefs.isValidSlide(this, mSite.getId(), mediaIdLong)) { + && !StoriesPrefs.isValidSlide(this, mSite.getId(), new RemoteMediaId(mediaIdLong))) { // flag this as soon as we find one media item not being really editable allStorySlidesAreEditable = false; } @@ -3023,7 +3024,11 @@ public void onTrackableEvent(TrackableEvent event, Map propertie StoryRepository.loadStory(storyIndex); storyIndex = StoryRepository.currentStoryIndex; for (String mediaId : tmpMediaIdsString) { - StoryFrameItem storyFrameItem = StoriesPrefs.getSlide(this, mSite.getId(), Long.parseLong(mediaId)); + StoryFrameItem storyFrameItem = StoriesPrefs.getSlideWithRemoteId( + this, + mSite.getId(), + new RemoteMediaId(Long.parseLong(mediaId)) + ); if (storyFrameItem != null) { StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); } else { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 6ff3b070d04e..e9c22e279c03 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -5,6 +5,8 @@ import org.wordpress.android.WordPress import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -64,23 +66,23 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { val localIdKey = mediaFile.id.toLong() val remoteIdKey = mediaFile.mediaId.toLong() val localSiteId = post.localSiteId.toLong() - StoriesPrefs.getSlide( + StoriesPrefs.getSlideWithLocalId( WordPress.getContext(), localSiteId, - localIdKey + LocalMediaId(localIdKey) )?.let { it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID - StoriesPrefs.saveSlide( + StoriesPrefs.saveSlideWithRemoteId( WordPress.getContext(), localSiteId, - remoteIdKey, // use the new mediaId as key + RemoteMediaId(remoteIdKey), // use the new mediaId as key it ) // now delete the old entry - StoriesPrefs.deleteSlide( + StoriesPrefs.deleteSlideWithLocalId( WordPress.getContext(), localSiteId, - localIdKey + LocalMediaId(localIdKey) ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index a1829d2daf96..d45c809556d7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -30,6 +30,7 @@ import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase import org.wordpress.android.ui.stories.StoriesTrackerHelper import org.wordpress.android.ui.stories.StoryComposerActivity import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId import org.wordpress.android.ui.uploads.UploadServiceFacade import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.NetworkUtilsWrapper @@ -187,11 +188,12 @@ class StoryMediaSaveUploadBridge @Inject constructor( val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) mediaModel?.let { frame.id = it.id.toString() - StoriesPrefs.saveSlide( + StoriesPrefs.saveSlideWithLocalId( appContext, it.localSiteId.toLong(), - it.id.toLong(), // use the local id to save the original, will be replaced later + // use the local id to save the original, will be replaced later // with mediaModel.mediaId after uploading to the remote site + LocalMediaId(it.id.toLong()), frame ) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index fbd94db60fb2..4dc3a8b850b8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -10,20 +10,26 @@ import com.wordpress.stories.compose.story.StorySerializerUtils object StoriesPrefs { private const val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" + private const val KEY_PREFIX_LOCAL_MEDIA_ID = "l-" + private const val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" - private fun buildSlideKey(siteId: Long, mediaId: Long): String { - return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + mediaId.toString() + private fun buildSlideKey(siteId: Long, mediaId: RemoteMediaId): String { + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.toString() + } + + private fun buildSlideKey(siteId: Long, mediaId: LocalMediaId): String { + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.toString() } @JvmStatic - fun checkSlideIdExists(context: Context, siteId: Long, mediaId: Long): Boolean { + fun checkSlideIdExists(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { val slideIdKey = buildSlideKey(siteId, mediaId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } @JvmStatic - fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: Long): Boolean { - val storyFrameItem: StoryFrameItem? = getSlide(context, siteId, mediaId) + fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { + val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(context, siteId, mediaId) storyFrameItem?.let { frame -> // now check the background media exists or is accessible on this device frame.source.let { source -> @@ -66,33 +72,55 @@ object StoriesPrefs { } @JvmStatic - fun isValidSlide(context: Context, siteId: Long, mediaId: Long): Boolean { + fun isValidSlide(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { return checkSlideIdExists(context, siteId, mediaId) && checkSlideOriginalBackgroundMediaExists(context, siteId, mediaId) } - private fun getSlideJson(context: Context, siteId: Long, mediaId: Long): String? { - val slideIdKey = buildSlideKey(siteId, mediaId) + private fun getSlideJson(context: Context, slideIdKey: String): String? { return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) } @JvmStatic - fun getSlide(context: Context, siteId: Long, mediaId: Long): StoryFrameItem? { - val jsonSlide = getSlideJson(context, siteId, mediaId) + fun getSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId): StoryFrameItem? { + val jsonSlide = getSlideJson(context, buildSlideKey(siteId, mediaId)) + jsonSlide?.let { + return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) + } ?: return null + } + + @JvmStatic + fun getSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId): StoryFrameItem? { + val jsonSlide = getSlideJson(context, buildSlideKey(siteId, mediaId)) jsonSlide?.let { return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) } ?: return null } - fun saveSlide(context: Context, siteId: Long, mediaId: Long, storyFrameItem: StoryFrameItem) { + fun saveSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId, storyFrameItem: StoryFrameItem) { + val slideIdKey = buildSlideKey(siteId, mediaId) + saveSlide(context, slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) + } + + fun saveSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) saveSlide(context, slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } - fun deleteSlide(context: Context, siteId: Long, mediaId: Long) { + fun deleteSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId) { + val slideIdKey = buildSlideKey(siteId, mediaId) + val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() + editor.remove(slideIdKey) + editor.apply() + } + + fun deleteSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId) { val slideIdKey = buildSlideKey(siteId, mediaId) val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() editor.remove(slideIdKey) editor.apply() } + + data class RemoteMediaId(val mediaId: Long) + data class LocalMediaId(val id: Long) } From 9c032f6710f87d986cad2487490429f2a428680b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 13:05:24 -0300 Subject: [PATCH 014/343] using setUseTempCaptureFile on the stories library to keep captured media --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 1 + libs/stories-android | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 4f827f055e92..ca0da66f8e22 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -119,6 +119,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), setNotificationTrackerProvider((application as WordPress).getStoryNotificationTrackerProvider()) setPrepublishingEventProvider(this) setPermissionDialogProvider(this) + setUseTempCaptureFile(false) // we need to keep the captured files for later Story editing initViewModel(savedInstanceState) } diff --git a/libs/stories-android b/libs/stories-android index 56deb4537e1d..5b9460ac6a86 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 56deb4537e1dabc8551231fb5fc00b94e7df9131 +Subproject commit 5b9460ac6a86ab7c1f8f1f5b43a62d57d2aa994e From 0232b7c4111839daec1ac0b8ab20c333c6530f88 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 14:18:52 -0300 Subject: [PATCH 015/343] converting intent extra KEY_LAUNCHED_FROM_GUTENBERG to stories KEY_STORY_EDIT_MODE so we can show discard dialog messaging properly --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 3 +++ libs/stories-android | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index ca0da66f8e22..697367f752ad 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -21,6 +21,7 @@ import com.wordpress.stories.compose.SnackbarProvider import com.wordpress.stories.compose.StoryDiscardListener import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult import com.wordpress.stories.compose.story.StoryIndex +import com.wordpress.stories.util.KEY_STORY_EDIT_MODE import com.wordpress.stories.util.KEY_STORY_INDEX import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT import org.wordpress.android.R.id @@ -108,6 +109,8 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } override fun onCreate(savedInstanceState: Bundle?) { + // convert our WPAndroid KEY_LAUNCHED_FROM_GUTENBERG flag into Stories general purpose EDIT_MODE flag + intent.putExtra(KEY_STORY_EDIT_MODE, intent.getBooleanExtra(KEY_LAUNCHED_FROM_GUTENBERG, false)) super.onCreate(savedInstanceState) (application as WordPress).component().inject(this) setSnackbarProvider(this) diff --git a/libs/stories-android b/libs/stories-android index 5b9460ac6a86..78b87d4cd5cc 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 5b9460ac6a86ab7c1f8f1f5b43a62d57d2aa994e +Subproject commit 78b87d4cd5cc1dab9a3eb22ab902b66143f4b08d From a35d6c236eec486265ea54187f10dd4fe62b377b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 15:31:07 -0300 Subject: [PATCH 016/343] implemented the GenericAnnouncementDialogProvider interface and showing the limited editing dialog when a Story slide to be eidted hasnt been found in StoriesPrefs --- .../android/ui/ActivityLauncher.java | 3 ++- .../ui/stories/StoryComposerActivity.kt | 24 ++++++++++++++++--- WordPress/src/main/res/values/strings.xml | 3 +++ libs/stories-android | 2 +- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index df9c7833fae5..28d25cf97060 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -103,6 +103,7 @@ import static org.wordpress.android.login.LoginMode.WPCOM_LOGIN_ONLY; import static org.wordpress.android.ui.media.MediaBrowserActivity.ARG_BROWSER_TYPE; import static org.wordpress.android.ui.pages.PagesActivityKt.EXTRA_PAGE_REMOTE_ID_KEY; +import static org.wordpress.android.ui.stories.StoryComposerActivity.KEY_ALL_UNFLATTENED_LOADED_SLIDES; import static org.wordpress.android.ui.stories.StoryComposerActivity.KEY_LAUNCHED_FROM_GUTENBERG; import static org.wordpress.android.viewmodel.activitylog.ActivityLogDetailViewModelKt.ACTIVITY_LOG_ID_KEY; @@ -749,11 +750,11 @@ public static void editStoryForResult( return; } - // TODO pass the allStorySlidesAreEditable boolean flag make sure to show the warning dialog Intent intent = new Intent(activity, StoryComposerActivity.class); intent.putExtra(WordPress.SITE, site); intent.putExtra(KEY_STORY_INDEX, storyIndex); intent.putExtra(KEY_LAUNCHED_FROM_GUTENBERG, launchedFromGutenberg); + intent.putExtra(KEY_ALL_UNFLATTENED_LOADED_SLIDES, allStorySlidesAreEditable); activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 697367f752ad..ff44aa5e536a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -12,6 +12,8 @@ import androidx.lifecycle.ViewModelProviders import com.google.android.material.snackbar.Snackbar import com.wordpress.stories.compose.AuthenticationHeadersProvider import com.wordpress.stories.compose.ComposeLoopFrameActivity +import com.wordpress.stories.compose.FrameSaveErrorDialog +import com.wordpress.stories.compose.GenericAnnouncementDialogProvider import com.wordpress.stories.compose.MediaPickerProvider import com.wordpress.stories.compose.MetadataProvider import com.wordpress.stories.compose.NotificationIntentLoader @@ -24,7 +26,7 @@ import com.wordpress.stories.compose.story.StoryIndex import com.wordpress.stories.util.KEY_STORY_EDIT_MODE import com.wordpress.stories.util.KEY_STORY_INDEX import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT -import org.wordpress.android.R.id +import org.wordpress.android.R import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED @@ -79,7 +81,8 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), EditPostActivityHook, PrepublishingEventProvider, PrepublishingBottomSheetListener, - PermanentPermissionDenialDialogProvider { + PermanentPermissionDenialDialogProvider, + GenericAnnouncementDialogProvider { private var site: SiteModel? = null @Inject lateinit var storyEditorMedia: StoryEditorMedia @@ -100,10 +103,12 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), override fun getEditPostRepository() = editPostRepository companion object { + protected const val FRAGMENT_ANNOUNCEMENT_DIALOG = "story_announcement_dialog" const val STATE_KEY_POST_LOCAL_ID = "state_key_post_model_local_id" const val STATE_KEY_EDITOR_SESSION_DATA = "stateKeyEditorSessionData" const val KEY_POST_LOCAL_ID = "key_post_model_local_id" const val KEY_LAUNCHED_FROM_GUTENBERG = "key_launched_from_gutenberg" + const val KEY_ALL_UNFLATTENED_LOADED_SLIDES = "key_all_unflattened_laoded_slides" const val UNUSED_KEY = "unused_key" const val BASE_FRAME_MEDIA_ERROR_NOTIFICATION_ID: Int = 72300 } @@ -122,6 +127,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), setNotificationTrackerProvider((application as WordPress).getStoryNotificationTrackerProvider()) setPrepublishingEventProvider(this) setPermissionDialogProvider(this) + setGenericAnnouncementDialogProvider(this) setUseTempCaptureFile(false) // we need to keep the captured files for later Story editing initViewModel(savedInstanceState) @@ -324,7 +330,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), if (messageHolder != null) { WPSnackbar .make( - findViewById(id.editor_activity), + findViewById(org.wordpress.android.R.id.editor_activity), uiHelpers.getTextOfUiString(this, messageHolder.message), Snackbar.LENGTH_SHORT ) @@ -434,4 +440,16 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), override fun showPermissionPermanentlyDeniedDialog(permission: String) { WPPermissionUtils.showPermissionAlwaysDeniedDialog(this, permission) } + + override fun showGenericAnnouncementDialog() { + if (intent.getBooleanExtra(KEY_LAUNCHED_FROM_GUTENBERG, false)) { + if (!intent.getBooleanExtra(KEY_ALL_UNFLATTENED_LOADED_SLIDES, false)) { + // not all slides in this Story could be unflattened so, show the warning informative dialog + FrameSaveErrorDialog.newInstance( + title = getString(R.string.dialog_edit_story_limited_title), + message = getString(R.string.dialog_edit_story_limited_message) + ).show(supportFragmentManager, FRAGMENT_ANNOUNCEMENT_DIALOG) + } + } + } } diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index fb2454bca40c..4d066ad74c38 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2871,6 +2871,9 @@ %s selected %s + + Limited Story Editing + This story was edited on a different device and the ability to edit certain objects may be limited. Capture Flip camera Flash diff --git a/libs/stories-android b/libs/stories-android index 78b87d4cd5cc..44deeb3a86c0 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 78b87d4cd5cc1dab9a3eb22ab902b66143f4b08d +Subproject commit 44deeb3a86c0b37220e6aa606753a7cdae775543 From 3f5da8d2394c5ce36fd929b661927d2666101caa Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 16 Sep 2020 18:23:12 -0300 Subject: [PATCH 017/343] updated stories lib hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 44deeb3a86c0..b9322a3f440b 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 44deeb3a86c0b37220e6aa606753a7cdae775543 +Subproject commit b9322a3f440bd33b6b5d919e67617c60dacea36b From e370ac2a0e3856f95e83816b26081a81b88dfd2a Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 17 Sep 2020 09:34:29 +0200 Subject: [PATCH 018/343] Update Kotlin, Coroutines and Serialization across projects --- WordPress/build.gradle | 2 +- build.gradle | 9 +++++---- libs/WordPressAnnotations/build.gradle | 4 ++-- libs/utils/WordPressUtils/build.gradle | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/WordPress/build.gradle b/WordPress/build.gradle index e32fe6a573df..13ecc7a4c070 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -254,7 +254,7 @@ dependencies { testImplementation "org.mockito:mockito-core:$mockitoCoreVersion" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$nhaarmanMockitoVersion" testImplementation "org.assertj:assertj-core:$assertJVersion" - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.1' + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" testImplementation 'androidx.test:core:1.2.0' androidTestImplementation 'org.mockito:mockito-android:2.27.0' diff --git a/build.gradle b/build.gradle index 1e72fb75be23..9c9c117a5eb5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,9 @@ buildscript { - ext.kotlinVersion = '1.3.61' - ext.serializationVersion = '0.14.0' + ext.kotlinVersion = '1.4.10' + ext.serializationVersion = '1.0-M1-1.4.0-rc' ext.navComponentVersion = '2.0.0' - ext.kotlin_coroutines_version = '1.3.3' + ext.kotlin_coroutines_version = '1.3.9' + ext.coroutinesVersion = '1.3.9' ext.kotlin_ktx_version = '1.2.0' ext.androidx_work_version = "2.0.1" ext.buildGutenbergMobileJSBundle = 1 @@ -129,7 +130,7 @@ ext { minSdkVersion = 21 targetSdkVersion = 28 - coroutinesVersion = '1.3.3' + coroutinesVersion = '1.3.9' androidxWorkVersion = "2.0.1" daggerVersion = '2.22.1' diff --git a/libs/WordPressAnnotations/build.gradle b/libs/WordPressAnnotations/build.gradle index f827f544e24a..dbdee96d9592 100644 --- a/libs/WordPressAnnotations/build.gradle +++ b/libs/WordPressAnnotations/build.gradle @@ -1,9 +1,9 @@ apply plugin: 'kotlin' dependencies { - ext.kotlinVersion = '1.3.61' + ext.kotlinVersion = '1.4.10' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" } sourceCompatibility = "7" diff --git a/libs/utils/WordPressUtils/build.gradle b/libs/utils/WordPressUtils/build.gradle index d182429e1547..eb5131dbb469 100644 --- a/libs/utils/WordPressUtils/build.gradle +++ b/libs/utils/WordPressUtils/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlinVersion = '1.3.61' + ext.kotlinVersion = '1.4.10' ext.kotlin_ktx_version = '1.2.0' repositories { From 0c54203e0b13a8ab9d49cd2294fb76e2c321c8be Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 17 Sep 2020 09:47:48 +0200 Subject: [PATCH 019/343] Remove ExperimentalCoroutinesApi annotation when not necessary --- .../test/java/org/wordpress/android/CoroutinesUtils.kt | 3 +-- .../android/ui/posts/PostListMainViewModelTest.kt | 2 -- .../discover/ReaderPostCardActionsHandlerTest.kt | 2 -- .../repository/ReaderDiscoverDataProviderTest.kt | 10 ---------- .../android/ui/stats/refresh/StatsViewModelTest.kt | 1 - .../ui/stats/refresh/lists/UiModelMapperTest.kt | 1 - 6 files changed, 1 insertion(+), 18 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/CoroutinesUtils.kt b/WordPress/src/test/java/org/wordpress/android/CoroutinesUtils.kt index 09805610db47..9c7ffb0fa0ac 100644 --- a/WordPress/src/test/java/org/wordpress/android/CoroutinesUtils.kt +++ b/WordPress/src/test/java/org/wordpress/android/CoroutinesUtils.kt @@ -6,7 +6,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Delay import kotlinx.coroutines.Dispatchers.Unconfined -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.runBlocking import org.mockito.Mockito @@ -24,7 +23,7 @@ fun KStubbing.onBlocking(methodCall: suspend T.() -> R): Ongoing return runBlocking { Mockito.`when`(mock.methodCall()) } } -@ExperimentalCoroutinesApi val TEST_SCOPE = CoroutineScope(Unconfined) +val TEST_SCOPE = CoroutineScope(Unconfined) @InternalCoroutinesApi val TEST_DISPATCHER: CoroutineDispatcher = TestDispatcher() @InternalCoroutinesApi diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/PostListMainViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/PostListMainViewModelTest.kt index 0658af6c6500..32686979359b 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/PostListMainViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/PostListMainViewModelTest.kt @@ -9,7 +9,6 @@ import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Before @@ -39,7 +38,6 @@ class PostListMainViewModelTest : BaseUnitTest() { private lateinit var viewModel: PostListMainViewModel @InternalCoroutinesApi - @UseExperimental(ExperimentalCoroutinesApi::class) @Before fun setUp() { val prefs = mock { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderPostCardActionsHandlerTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderPostCardActionsHandlerTest.kt index 823f675e8846..58393319aa4a 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderPostCardActionsHandlerTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderPostCardActionsHandlerTest.kt @@ -6,7 +6,6 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.flowOf import org.assertj.core.api.Assertions.assertThat @@ -72,7 +71,6 @@ import org.wordpress.android.ui.utils.HtmlMessageUtils import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.viewmodel.ResourceProvider -@ExperimentalCoroutinesApi @InternalCoroutinesApi @RunWith(MockitoJUnitRunner::class) class ReaderPostCardActionsHandlerTest { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt index e484a79f4ca7..856e31a184db 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt @@ -68,7 +68,6 @@ class ReaderDiscoverDataProviderTest { ) } - @ExperimentalCoroutinesApi @Test fun `when refreshCards is requested started gets posted on comm channel`() = test { whenever(fetchDiscoverCardsUseCase.fetch(REQUEST_FIRST_PAGE)).thenReturn(Started(REQUEST_FIRST_PAGE)) @@ -81,7 +80,6 @@ class ReaderDiscoverDataProviderTest { .isEqualTo(Started(REQUEST_FIRST_PAGE)) } - @ExperimentalCoroutinesApi @Test fun `when fetch request fails then failure gets posted to comm channel`() = test { // Arrange @@ -95,7 +93,6 @@ class ReaderDiscoverDataProviderTest { .isEqualTo(RemoteRequestFailure(REQUEST_FIRST_PAGE)) } - @ExperimentalCoroutinesApi @Test fun `when fetch request succeeds success gets posted to comm channel`() = test { // Arrange @@ -109,7 +106,6 @@ class ReaderDiscoverDataProviderTest { .isEqualTo(Success(REQUEST_FIRST_PAGE)) } - @ExperimentalCoroutinesApi @Test fun `when fetch request unchanged success gets posted to comm channel`() = test { // Arrange @@ -123,7 +119,6 @@ class ReaderDiscoverDataProviderTest { .isEqualTo(Success(REQUEST_FIRST_PAGE)) } - @ExperimentalCoroutinesApi @Test fun `on cards updated has new the data gets posted to discover feed`() = test { // Arrange @@ -141,7 +136,6 @@ class ReaderDiscoverDataProviderTest { .isEqualTo(NUMBER_OF_ITEMS) } - @ExperimentalCoroutinesApi @Test fun `when loadMoreRequest in progress another started not posted to comm channel`() = test { whenever(fetchDiscoverCardsUseCase.fetch(REQUEST_MORE)).thenReturn(Started(REQUEST_MORE)) @@ -166,7 +160,6 @@ class ReaderDiscoverDataProviderTest { } // The following test the loadData(), which is kicked off when discoverFeed obtains observers - @ExperimentalCoroutinesApi @Test fun `when loadData with refresh request is started and posted to comm channel`() = test { whenever(fetchDiscoverCardsUseCase.fetch(REQUEST_FIRST_PAGE)).thenReturn(Started(REQUEST_FIRST_PAGE)) @@ -180,7 +173,6 @@ class ReaderDiscoverDataProviderTest { Assertions.assertThat(requireNotNull(started)).isEqualTo(Started(REQUEST_FIRST_PAGE)) } - @ExperimentalCoroutinesApi @Test fun `when loadData without refresh no start message posted to comm channel`() = test { whenever(getDiscoverCardsUseCase.get()).thenReturn(createDummyReaderCardsList()) @@ -193,7 +185,6 @@ class ReaderDiscoverDataProviderTest { Assertions.assertThat(started).isNull() } - @ExperimentalCoroutinesApi @Test fun `when loadData with forceReload true data posted to discover channel`() = test { whenever(getDiscoverCardsUseCase.get()).thenReturn(createDummyReaderCardsList()) @@ -209,7 +200,6 @@ class ReaderDiscoverDataProviderTest { Assertions.assertThat(data).isNotNull } - @ExperimentalCoroutinesApi @Test fun `when loadData with existsInMemory data posted to discover feed`() = test { val discoverFeedObserver = Observer { } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt index 8cfa3cf6d9a4..85dac5a078c2 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt @@ -48,7 +48,6 @@ class StatsViewModelTest : BaseUnitTest() { private lateinit var viewModel: StatsViewModel private val _liveSelectedSection = MutableLiveData() private val liveSelectedSection: LiveData = _liveSelectedSection - @ExperimentalCoroutinesApi @Before fun setUp() { whenever(baseListUseCase.snackbarMessage).thenReturn(MutableLiveData()) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt index da954058cbca..37c35922badd 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt @@ -18,7 +18,6 @@ import org.wordpress.android.util.NetworkUtilsWrapper class UiModelMapperTest : BaseUnitTest() { @Mock lateinit var networkUtilsWrapper: NetworkUtilsWrapper private lateinit var mapper: UiModelMapper - @ExperimentalCoroutinesApi @Before fun setUp() { mapper = UiModelMapper(networkUtilsWrapper) From c3ff46206d557981c4bee37498c47cccd10658d6 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 17 Sep 2020 10:01:07 +0200 Subject: [PATCH 020/343] Remove unused imports --- .../ui/reader/repository/ReaderDiscoverDataProviderTest.kt | 1 - .../org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt | 1 - .../android/ui/stats/refresh/lists/UiModelMapperTest.kt | 1 - 3 files changed, 3 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt index 856e31a184db..f81af73acdeb 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt @@ -3,7 +3,6 @@ package org.wordpress.android.ui.reader.repository import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import com.nhaarman.mockitokotlin2.whenever -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import org.assertj.core.api.Assertions import org.junit.Before diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt index 85dac5a078c2..453b36f92b5f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/StatsViewModelTest.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt index 37c35922badd..ca5c168e988f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/UiModelMapperTest.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.stats.refresh.lists -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test From 3ba13aa853a1d96392aa3296f40df38867eab8a9 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 17 Sep 2020 10:01:53 +0200 Subject: [PATCH 021/343] Updates to stories-android --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 81d9fe073010..9abf0acd33c3 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 81d9fe073010346d244b402541ea552dbdb36f8b +Subproject commit 9abf0acd33c3132edb86c861b8bc29a3db424a1c From af781d34c977a6365edae7a5b70ff3fc1e53a19e Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 17 Sep 2020 10:03:46 +0200 Subject: [PATCH 022/343] Revert "Updates to stories-android" This reverts commit 3ba13aa853a1d96392aa3296f40df38867eab8a9. --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 9abf0acd33c3..81d9fe073010 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 9abf0acd33c3132edb86c861b8bc29a3db424a1c +Subproject commit 81d9fe073010346d244b402541ea552dbdb36f8b From c21e3e3bb25ad70d54a92f8862d0e1d2f2323739 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 17 Sep 2020 07:45:28 -0300 Subject: [PATCH 023/343] passing blockId, mediaFiles and state in result --- .../android/ui/ActivityLauncher.java | 5 ++++- .../android/ui/posts/EditPostActivity.java | 11 +++++++++-- .../stories/SaveStoryGutenbergBlockUseCase.kt | 12 ++++++++++++ .../ui/stories/StoryComposerActivity.kt | 19 ++++++++++++++++++- .../gutenberg/GutenbergEditorFragment.java | 16 ++++++++++++---- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index 28d25cf97060..bfeca6f88b35 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -99,6 +99,7 @@ import static org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_ARTICLE_DETAIL_REBLOGGED; import static org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_ARTICLE_REBLOGGED; import static org.wordpress.android.analytics.AnalyticsTracker.Stat.STATS_ACCESS_ERROR; +import static org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_ID; import static org.wordpress.android.imageeditor.preview.PreviewImageFragment.ARG_EDIT_IMAGE_DATA; import static org.wordpress.android.login.LoginMode.WPCOM_LOGIN_ONLY; import static org.wordpress.android.ui.media.MediaBrowserActivity.ARG_BROWSER_TYPE; @@ -744,7 +745,8 @@ public static void editStoryForResult( SiteModel site, int storyIndex, boolean allStorySlidesAreEditable, - boolean launchedFromGutenberg + boolean launchedFromGutenberg, + String storyBlockId ) { if (site == null) { return; @@ -755,6 +757,7 @@ public static void editStoryForResult( intent.putExtra(KEY_STORY_INDEX, storyIndex); intent.putExtra(KEY_LAUNCHED_FROM_GUTENBERG, launchedFromGutenberg); intent.putExtra(KEY_ALL_UNFLATTENED_LOADED_SLIDES, allStorySlidesAreEditable); + intent.putExtra(ARG_STORY_BLOCK_ID, storyBlockId); activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index aa8a9b8068ed..fd657dcefb53 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3049,8 +3049,15 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } } } - // Story instance loaded or re-created! Load it - ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); + // Story instance loaded or re-created! Load it onto the StoryComposer for editing now + ActivityLauncher.editStoryForResult( + this, + mSite, + storyIndex, + allStorySlidesAreEditable, + true, + blockId + ); } // FluxC events diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index e9c22e279c03..e485d05eaa54 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -29,6 +29,18 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { } } + fun buildJetpackStoryBlockMediaFilesJsonString( + mediaFiles: Map + ): String { + val jsonArrayMediaFiles = ArrayList() // holds media files + for (entry in mediaFiles.entries) { + jsonArrayMediaFiles.add(buildMediaFileData(entry.value)) + } + val storyBlock = StoryBlockData(mediaFiles = jsonArrayMediaFiles) + val gson = Gson() + return gson.toJson(storyBlock) + } + private fun buildMediaFileData(mediaFile: MediaFile): StoryMediaFileData { return StoryMediaFileData( alt = "", diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index ff44aa5e536a..7dd3b7df72d7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -30,6 +30,9 @@ import org.wordpress.android.R import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED +import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_ID +import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_MEDIA_FILES +import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel @@ -95,6 +98,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), @Inject lateinit var analyticsUtilsWrapper: AnalyticsUtilsWrapper @Inject internal lateinit var viewModelFactory: ViewModelProvider.Factory @Inject internal lateinit var mediaPickerLauncher: MediaPickerLauncher + @Inject lateinit var saveStoryGutenbergBlockUseCase: SaveStoryGutenbergBlockUseCase private lateinit var viewModel: StoryComposerViewModel private var addingMediaToEditorProgressDialog: ProgressDialog? = null @@ -430,7 +434,20 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } override fun onStorySaveButtonPressed() { - viewModel.onStorySaveButtonPressed() + if (intent.getBooleanExtra(KEY_LAUNCHED_FROM_GUTENBERG, false)) { + val savedContentIntent = Intent() + val blockId = intent.extras.getString(ARG_STORY_BLOCK_ID) + savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) + // TODO + // here take the StoryFrameItems from the current Story, and build a + // val mediaFiles= saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockMediaFilesJsonString() + // savedContentIntent.putExtra(ARG_STORY_BLOCK_MEDIA_FILES, mediaFiles) + savedContentIntent.putExtra(ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA, true) + setResult(Activity.RESULT_OK, savedContentIntent) + finish() + } else { + viewModel.onStorySaveButtonPressed() + } } override fun onSubmitButtonClicked(publishPost: PublishPost) { diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 2285f3f9c8d6..2938472628ee 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -85,6 +85,9 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements private static final String ARG_TENOR_ENABLED = "param_tenor_enabled"; private static final String ARG_GUTENBERG_PROPS_BUILDER = "param_gutenberg_props_builder"; private static final String ARG_STORY_EDITOR_REQUEST_CODE = "param_sory_editor_request_code"; + public static final String ARG_STORY_BLOCK_ID = "block_id"; + public static final String ARG_STORY_BLOCK_MEDIA_FILES = "block_media_files"; + public static final String ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA = "status_wait_for_flattened_media"; private static final int CAPTURE_PHOTO_PERMISSION_REQUEST_CODE = 101; private static final int CAPTURE_VIDEO_PERMISSION_REQUEST_CODE = 102; @@ -407,10 +410,15 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d trackWebViewClosed("dismiss"); } } else if (requestCode == mStoryBlockEditRequestCode) { - // handle edited block content - String blockId = data.getStringExtra(WPGutenbergWebViewActivity.ARG_BLOCK_ID); - String content = data.getStringExtra(WPGutenbergWebViewActivity.ARG_BLOCK_CONTENT); - getGutenbergContainerFragment().replaceStoryEditedBlock(content, blockId); + if (resultCode == Activity.RESULT_OK) { + // handle edited block content + String blockId = data.getStringExtra(ARG_STORY_BLOCK_ID); + String mediaFiles = data.getStringExtra(ARG_STORY_BLOCK_MEDIA_FILES); + getGutenbergContainerFragment().replaceStoryEditedBlock(mediaFiles, blockId); + // TODO maybe we need to track something here? + } else { + // TODO maybe we need to track something here? + } } } From b9212ef71d275c1e1cf0ef064382973b3095568c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 17 Sep 2020 08:02:59 -0300 Subject: [PATCH 024/343] connect onActivityResult in EditPostActivity to call GutenbergEditorFragment's --- .../org/wordpress/android/ui/posts/EditPostActivity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index fd657dcefb53..983c022d8ec1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -2396,6 +2396,13 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } } + if (requestCode == RequestCodes.EDIT_STORY) { + if (mEditorFragment instanceof GutenbergEditorFragment) { + mEditorFragment.onActivityResult(requestCode, resultCode, data); + return; + } + } + if (data != null || ((requestCode == RequestCodes.TAKE_PHOTO || requestCode == RequestCodes.TAKE_VIDEO || requestCode == RequestCodes.PHOTO_PICKER))) { switch (requestCode) { From acf2ef85dec828467e9f2eca4ba82b051c5b3924 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 17 Sep 2020 20:55:57 -0300 Subject: [PATCH 025/343] refactor to reuse block mediaFiles building --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index e485d05eaa54..e86876a16d80 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -14,31 +14,23 @@ import javax.inject.Inject class SaveStoryGutenbergBlockUseCase @Inject constructor() { fun buildJetpackStoryBlockInPost( editPostRepository: EditPostRepository, - mediaFiles: Map + mediaFiles: ArrayList ) { - val jsonArrayMediaFiles = ArrayList() // holds media files - for (entry in mediaFiles.entries) { - jsonArrayMediaFiles.add(buildMediaFileData(entry.value)) - } - - val storyBlock = StoryBlockData(mediaFiles = jsonArrayMediaFiles) - editPostRepository.update { postModel: PostModel -> - postModel.setContent(createGBStoryBlockStringFromJson(storyBlock)) + postModel.setContent(buildJetpackStoryBlockString(mediaFiles)) true } } - fun buildJetpackStoryBlockMediaFilesJsonString( - mediaFiles: Map + fun buildJetpackStoryBlockString( + mediaFiles: List ): String { val jsonArrayMediaFiles = ArrayList() // holds media files - for (entry in mediaFiles.entries) { - jsonArrayMediaFiles.add(buildMediaFileData(entry.value)) + for (mediaFile in mediaFiles) { + jsonArrayMediaFiles.add(buildMediaFileData(mediaFile)) } val storyBlock = StoryBlockData(mediaFiles = jsonArrayMediaFiles) - val gson = Gson() - return gson.toJson(storyBlock) + return createGBStoryBlockStringFromJson(storyBlock) } private fun buildMediaFileData(mediaFile: MediaFile): StoryMediaFileData { From cd42ffb6f0ba4cfd6f534e41d2639ac13f06613e Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 17 Sep 2020 20:56:34 -0300 Subject: [PATCH 026/343] connected the DONE button to pass the updated content story block back to Gutenberg --- .../ui/stories/StoryComposerActivity.kt | 37 +++++++++++++++++-- .../media/StoryMediaSaveUploadBridge.kt | 5 ++- .../gutenberg/GutenbergEditorFragment.java | 8 ++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 7dd3b7df72d7..3a735bc19964 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -23,6 +23,8 @@ import com.wordpress.stories.compose.SnackbarProvider import com.wordpress.stories.compose.StoryDiscardListener import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult import com.wordpress.stories.compose.story.StoryIndex +import com.wordpress.stories.compose.story.StoryRepository +import com.wordpress.stories.compose.story.StoryRepository.DEFAULT_NONE_SELECTED import com.wordpress.stories.util.KEY_STORY_EDIT_MODE import com.wordpress.stories.util.KEY_STORY_INDEX import com.wordpress.stories.util.KEY_STORY_SAVE_RESULT @@ -31,12 +33,13 @@ import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_ID -import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_MEDIA_FILES +import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_UPDATED_CONTENT import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.fluxc.store.PostStore import org.wordpress.android.push.NotificationType import org.wordpress.android.push.NotificationsProcessingService @@ -62,6 +65,7 @@ import org.wordpress.android.ui.stories.media.StoryEditorMedia import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState import org.wordpress.android.ui.utils.AuthenticationUtils import org.wordpress.android.ui.utils.UiHelpers +import org.wordpress.android.util.FluxCUtilsWrapper import org.wordpress.android.util.ListUtils import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils @@ -99,6 +103,9 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), @Inject internal lateinit var viewModelFactory: ViewModelProvider.Factory @Inject internal lateinit var mediaPickerLauncher: MediaPickerLauncher @Inject lateinit var saveStoryGutenbergBlockUseCase: SaveStoryGutenbergBlockUseCase + @Inject lateinit var mediaStore: MediaStore + @Inject lateinit var fluxCUtilsWrapper: FluxCUtilsWrapper + private lateinit var viewModel: StoryComposerViewModel private var addingMediaToEditorProgressDialog: ProgressDialog? = null @@ -440,16 +447,40 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) // TODO // here take the StoryFrameItems from the current Story, and build a - // val mediaFiles= saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockMediaFilesJsonString() - // savedContentIntent.putExtra(ARG_STORY_BLOCK_MEDIA_FILES, mediaFiles) + val storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) + val updatedStoryBlock= + saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockString( + getMediaFilesFromBackgroundMediaIdsInStory(storyIndex) + ) + savedContentIntent.putExtra(ARG_STORY_BLOCK_UPDATED_CONTENT, updatedStoryBlock) savedContentIntent.putExtra(ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA, true) setResult(Activity.RESULT_OK, savedContentIntent) finish() } else { + // assume this is a new Post, and proceed to PrePublish bottom sheet viewModel.onStorySaveButtonPressed() } } + private fun getMediaFilesFromBackgroundMediaIdsInStory(storyIdx: StoryIndex) : List { + val mediaFiles = ArrayList() + val story = StoryRepository.getStoryAtIndex(storyIdx) + for (frame in story.frames) { + frame.id?.let { + // WARNING: we assume the only path to creation is by using the Story Creator (not mobile gutenberg + // by inserting a new block from the block picker) so, we ALWAYS should have a remote mediaId + // here. If things change, we'll need to cover the situation for finding a MediaModel from both + // a local ID (if not yet uploaded to the site) and a remote ID (after uploading to the site). + val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) + val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) + mediaFile?.let { + mediaFiles.add(it) + } + } + } + return mediaFiles + } + override fun onSubmitButtonClicked(publishPost: PublishPost) { viewModel.onSubmitButtonClicked() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index d45c809556d7..7592c889bc16 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -157,7 +157,10 @@ class StoryMediaSaveUploadBridge @Inject constructor( } override fun appendMediaFiles(mediaFiles: Map) { - saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost(editPostRepository, mediaFiles) + saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( + editPostRepository, + ArrayList(mediaFiles.values) + ) } override fun getImmutablePost(): PostImmutableModel { diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 2938472628ee..0d18afdec9e6 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -85,8 +85,8 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements private static final String ARG_TENOR_ENABLED = "param_tenor_enabled"; private static final String ARG_GUTENBERG_PROPS_BUILDER = "param_gutenberg_props_builder"; private static final String ARG_STORY_EDITOR_REQUEST_CODE = "param_sory_editor_request_code"; - public static final String ARG_STORY_BLOCK_ID = "block_id"; - public static final String ARG_STORY_BLOCK_MEDIA_FILES = "block_media_files"; + public static final String ARG_STORY_BLOCK_ID = "story_block_id"; + public static final String ARG_STORY_BLOCK_UPDATED_CONTENT = "story_block_updated_content"; public static final String ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA = "status_wait_for_flattened_media"; private static final int CAPTURE_PHOTO_PERMISSION_REQUEST_CODE = 101; @@ -413,8 +413,8 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d if (resultCode == Activity.RESULT_OK) { // handle edited block content String blockId = data.getStringExtra(ARG_STORY_BLOCK_ID); - String mediaFiles = data.getStringExtra(ARG_STORY_BLOCK_MEDIA_FILES); - getGutenbergContainerFragment().replaceStoryEditedBlock(mediaFiles, blockId); + String updatedBlockContent = data.getStringExtra(ARG_STORY_BLOCK_UPDATED_CONTENT); + getGutenbergContainerFragment().replaceStoryEditedBlock(updatedBlockContent, blockId); // TODO maybe we need to track something here? } else { // TODO maybe we need to track something here? From 8fa059d29563eafbe53ea48c379fdd4232ab545b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 07:47:31 -0300 Subject: [PATCH 027/343] initializing request codes right away in onCreate before calling super() to make sure these can be properly evaluated on creation --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index ff44aa5e536a..90a0c67e01fe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -116,10 +116,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), override fun onCreate(savedInstanceState: Bundle?) { // convert our WPAndroid KEY_LAUNCHED_FROM_GUTENBERG flag into Stories general purpose EDIT_MODE flag intent.putExtra(KEY_STORY_EDIT_MODE, intent.getBooleanExtra(KEY_LAUNCHED_FROM_GUTENBERG, false)) + setMediaPickerProvider(this) super.onCreate(savedInstanceState) (application as WordPress).component().inject(this) setSnackbarProvider(this) - setMediaPickerProvider(this) setAuthenticationProvider(this) setNotificationExtrasLoader(this) setMetadataProvider(this) From df91b54a0b87a32c9c0ceb383d28b9979b9ebdef Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 09:05:44 -0300 Subject: [PATCH 028/343] refactored code, introducing LoadStoryFromStoriesPrefsUseCase --- .../android/ui/posts/EditPostActivity.java | 60 ++++-------- .../ui/stories/StoryRepositoryWrapper.kt | 5 + .../android/ui/stories/prefs/StoriesPrefs.kt | 4 +- .../LoadStoryFromStoriesPrefsUseCase.kt | 91 +++++++++++++++++++ libs/stories-android | 2 +- 5 files changed, 119 insertions(+), 43 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index aa8a9b8068ed..9987050575c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -160,8 +160,11 @@ import org.wordpress.android.ui.prefs.SiteSettingsInterface; import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; +import org.wordpress.android.ui.stories.StoryRepositoryWrapper; import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId; +import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; +import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; import org.wordpress.android.ui.uploads.UploadService; import org.wordpress.android.ui.uploads.UploadUtils; @@ -3002,52 +3005,29 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - ArrayList tmpMediaIdsString = new ArrayList<>(); - boolean allStorySlidesAreEditable = true; - for (Object mediaFile : mediaFiles) { - long mediaIdLong = new Double(((HashMap) mediaFile).get("id").toString()).longValue(); - String mediaIdString = String.valueOf(mediaIdLong); - if (allStorySlidesAreEditable - && !StoriesPrefs.isValidSlide(this, mSite.getId(), new RemoteMediaId(mediaIdLong))) { - // flag this as soon as we find one media item not being really editable - allStorySlidesAreEditable = false; - } - tmpMediaIdsString.add(mediaIdString); - } + LoadStoryFromStoriesPrefsUseCase loadStoryFromStoriesPrefsUseCase = new LoadStoryFromStoriesPrefsUseCase( + new StoryRepositoryWrapper(), + mSite, + mMediaStore, + this + ); + ArrayList mediaIds = + loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles); + boolean allStorySlidesAreEditable = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(mSite, mediaIds); // now look for a Story in the StoryRepository that has all these frames and, if not found, let's // just build the Story object ourselves to keep these files arrangement - int storyIndex = StoryRepository.findStoryContainingStoryFrameItemsByIds(tmpMediaIdsString); + int storyIndex = StoryRepository.findStoryContainingStoryFrameItemsByIds(mediaIds); if (storyIndex == StoryRepository.DEFAULT_NONE_SELECTED) { - // the StoryRepository didn' have it but we have editable serialized slides so, + // the StoryRepository didn't have it but we have editable serialized slides so, // create a new Story from scratch with these deserialized StoryFrameItems - StoryRepository.loadStory(storyIndex); - storyIndex = StoryRepository.currentStoryIndex; - for (String mediaId : tmpMediaIdsString) { - StoryFrameItem storyFrameItem = StoriesPrefs.getSlideWithRemoteId( - this, - mSite.getId(), - new RemoteMediaId(Long.parseLong(mediaId)) - ); - if (storyFrameItem != null) { - StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); - } else { - allStorySlidesAreEditable = false; - - // for this missing frame we'll create a new frame using the actual uploaded flattened media - ArrayList tmpMediaIdsLong = new ArrayList<>(); - tmpMediaIdsLong.add(Long.parseLong(mediaId)); - List mediaModelList = mMediaStore.getSiteMediaWithIds(mSite, tmpMediaIdsLong); - for (MediaModel mediaModel : mediaModelList) { - storyFrameItem = StoryFrameItem.Companion.getNewStoryFrameItemFromUri( - Uri.parse(mediaModel.getUrl()), - mediaModel.isVideo() - ); - storyFrameItem.setId(String.valueOf(mediaModel.getMediaId())); - StoryRepository.addStoryFrameItemToCurrentStory(storyFrameItem); - } - } + ReCreateStoryResult result = + loadStoryFromStoriesPrefsUseCase.loadOrReCreateStoryFromStoriesPrefs(mediaIds); + if (allStorySlidesAreEditable) { + // double check and override if we found at least one couldn't be inflated + allStorySlidesAreEditable = result.getAllStorySlidesAreEditable(); } + storyIndex = result.getStoryIndex(); } // Story instance loaded or re-created! Load it ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt index ee6deefcb3eb..7b89cea9bbda 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt @@ -1,5 +1,7 @@ package org.wordpress.android.ui.stories +import com.wordpress.stories.compose.story.StoryFrameItem +import com.wordpress.stories.compose.story.StoryIndex import com.wordpress.stories.compose.story.StoryRepository import javax.inject.Inject @@ -7,4 +9,7 @@ class StoryRepositoryWrapper @Inject constructor() { fun setCurrentStoryTitle(title: String) = StoryRepository.setCurrentStoryTitle(title) fun getCurrentStoryThumbnailUrl() = StoryRepository.getCurrentStoryThumbnailUrl() fun getCurrentStoryTitle() = StoryRepository.getCurrentStoryTitle() + fun getCurrentStoryIndex(): StoryIndex = StoryRepository.currentStoryIndex + fun loadStory(storyIndex: StoryIndex) = StoryRepository.loadStory(storyIndex) + fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = StoryRepository.addStoryFrameItemToCurrentStory(item) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 4dc3a8b850b8..82b924756a21 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -14,11 +14,11 @@ object StoriesPrefs { private const val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" private fun buildSlideKey(siteId: Long, mediaId: RemoteMediaId): String { - return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.toString() + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.mediaId.toString() } private fun buildSlideKey(siteId: Long, mediaId: LocalMediaId): String { - return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.toString() + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.id.toString() } @JvmStatic diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt new file mode 100644 index 000000000000..def87ba9aa2b --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -0,0 +1,91 @@ +package org.wordpress.android.ui.stories.usecase + +import android.content.Context +import android.net.Uri +import com.wordpress.stories.compose.story.StoryFrameItem +import com.wordpress.stories.compose.story.StoryIndex +import com.wordpress.stories.compose.story.StoryRepository +import dagger.Reusable +import org.wordpress.android.fluxc.model.MediaModel +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.ui.stories.StoryRepositoryWrapper +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.getSlideWithRemoteId +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.isValidSlide +import java.util.ArrayList +import java.util.HashMap +import javax.inject.Inject + +@Reusable +class LoadStoryFromStoriesPrefsUseCase @Inject constructor( + private val storyRepositoryWrapper: StoryRepositoryWrapper, + private val site: SiteModel, + private val mediaStore: MediaStore, + private val context: Context +) { + fun getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles: ArrayList): ArrayList { + val mediaIds = ArrayList() + for (mediaFile in mediaFiles) { + val mediaIdLong = (mediaFile as HashMap)["id"] + .toString() + .toDouble() // this conversion is needed to strip off decimals that can come from RN + .toLong() + val mediaIdString = mediaIdLong.toString() + mediaIds.add(mediaIdString) + } + return mediaIds + } + + fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { + for (mediaId in mediaIds) { + if (!isValidSlide(context, site.getId().toLong(), RemoteMediaId(mediaId.toLong()))) { + return false + } + } + return true + } + + fun loadOrReCreateStoryFromStoriesPrefs(mediaIds: ArrayList): ReCreateStoryResult { + // the StoryRepository didn't have it but we have editable serialized slides so, + // create a new Story from scratch with these deserialized StoryFrameItems + var allStorySlidesAreEditable: Boolean = true + var storyIndex = StoryRepository.DEFAULT_NONE_SELECTED + storyRepositoryWrapper.loadStory(storyIndex) + storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() + for (mediaId in mediaIds) { + var storyFrameItem = getSlideWithRemoteId( + context, + site.getId().toLong(), + RemoteMediaId(mediaId.toLong()) + ) + if (storyFrameItem != null) { + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + } else { + allStorySlidesAreEditable = false + + // for this missing frame we'll create a new frame using the actual uploaded flattened media + val tmpMediaIdsLong = ArrayList() + tmpMediaIdsLong.add(mediaId.toLong()) + val mediaModelList: List = mediaStore.getSiteMediaWithIds( + site, + tmpMediaIdsLong + ) + for (mediaModel in mediaModelList) { + storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( + Uri.parse(mediaModel.url), + mediaModel.isVideo + ) + storyFrameItem.id = mediaModel.mediaId.toString() + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + } + } + } + + return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable) + } + + data class ReCreateStoryResult(val storyIndex: StoryIndex, val allStorySlidesAreEditable: Boolean) +} + + diff --git a/libs/stories-android b/libs/stories-android index b9322a3f440b..68fc2ec21ccc 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit b9322a3f440bd33b6b5d919e67617c60dacea36b +Subproject commit 68fc2ec21ccce9af40f89bca3dcc57a914dd291e From 448dffd9005b1be3c91d1af38e711b62f409ad25 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 09:56:48 -0300 Subject: [PATCH 029/343] added path for story media not fetched locally --- .../android/ui/posts/EditPostActivity.java | 50 +++++++++++++++++-- .../LoadStoryFromStoriesPrefsUseCase.kt | 26 ++++++---- WordPress/src/main/res/values/strings.xml | 2 + 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 9987050575c7..ac5871c852fc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -40,7 +40,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; -import com.wordpress.stories.compose.story.StoryFrameItem; import com.wordpress.stories.compose.story.StoryRepository; import org.greenrobot.eventbus.EventBus; @@ -75,6 +74,7 @@ import org.wordpress.android.fluxc.action.AccountAction; import org.wordpress.android.fluxc.generated.AccountActionBuilder; import org.wordpress.android.fluxc.generated.EditorThemeActionBuilder; +import org.wordpress.android.fluxc.generated.MediaActionBuilder; import org.wordpress.android.fluxc.generated.PostActionBuilder; import org.wordpress.android.fluxc.generated.SiteActionBuilder; import org.wordpress.android.fluxc.model.AccountModel; @@ -95,9 +95,11 @@ import org.wordpress.android.fluxc.store.EditorThemeStore.FetchEditorThemePayload; import org.wordpress.android.fluxc.store.EditorThemeStore.OnEditorThemeChanged; import org.wordpress.android.fluxc.store.MediaStore; +import org.wordpress.android.fluxc.store.MediaStore.FetchMediaListPayload; import org.wordpress.android.fluxc.store.MediaStore.MediaError; import org.wordpress.android.fluxc.store.MediaStore.MediaErrorType; import org.wordpress.android.fluxc.store.MediaStore.OnMediaChanged; +import org.wordpress.android.fluxc.store.MediaStore.OnMediaListFetched; import org.wordpress.android.fluxc.store.MediaStore.OnMediaUploaded; import org.wordpress.android.fluxc.store.PostStore; import org.wordpress.android.fluxc.store.PostStore.OnPostChanged; @@ -185,6 +187,7 @@ import org.wordpress.android.util.LocaleManager; import org.wordpress.android.util.LocaleManagerWrapper; import org.wordpress.android.util.MediaUtils; +import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.PermissionUtils; import org.wordpress.android.util.ReblogUtils; import org.wordpress.android.util.ShortcutUtils; @@ -611,6 +614,13 @@ protected void onCreate(Bundle savedInstanceState) { } if (!mIsNewPost) { + // if we are opening an existing Post, and it contains a Story block, pre-fetch the media in case + // the user wants to edit the block (we'll need to download it first if the slides images weren't + // created on this device) + if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getPost().getContent())) { + fetchMediaList(); + } + // if we are opening a Post for which an error notification exists, we need to remove it from the dashboard // to prevent the user from tapping RETRY on a Post that is being currently edited UploadService.cancelFinalNotification(this, mEditPostRepository.getPost()); @@ -3014,6 +3024,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie ArrayList mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles); boolean allStorySlidesAreEditable = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(mSite, mediaIds); + boolean failedLoadingOrReCreatingStory = false; // now look for a Story in the StoryRepository that has all these frames and, if not found, let's // just build the Story object ourselves to keep these files arrangement @@ -3023,14 +3034,29 @@ public void onTrackableEvent(TrackableEvent event, Map propertie // create a new Story from scratch with these deserialized StoryFrameItems ReCreateStoryResult result = loadStoryFromStoriesPrefsUseCase.loadOrReCreateStoryFromStoriesPrefs(mediaIds); + failedLoadingOrReCreatingStory = result.getNoSlidesLoaded(); if (allStorySlidesAreEditable) { // double check and override if we found at least one couldn't be inflated allStorySlidesAreEditable = result.getAllStorySlidesAreEditable(); } storyIndex = result.getStoryIndex(); } - // Story instance loaded or re-created! Load it - ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); + + if (!failedLoadingOrReCreatingStory) { + // Story instance loaded or re-created! Load it + ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); + } else { + // unfortunately we couldn't even load the remote media Ids indicated by the StoryBLock so we can't allow + // editing at this time :( + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); + builder.setMessage(getString(R.string.dialog_edit_story_unavailable_message)); + builder.setPositiveButton(R.string.yes, (dialog, id) -> { + dialog.dismiss(); + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } } // FluxC events @@ -3065,6 +3091,13 @@ public void onMediaUploaded(OnMediaUploaded event) { // FluxC events + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onMediaListFetched(OnMediaListFetched event) { + // no op - we don't need to check anything just now, but declaring the method so it's + // clear we make a request to FetchMedia in this class. + } + @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onPostChanged(OnPostChanged event) { @@ -3190,6 +3223,17 @@ private void refreshEditorTheme() { mDispatcher.dispatch(EditorThemeActionBuilder.newFetchEditorThemeAction(payload)); } + private void fetchMediaList() { + // do not refresh if there is no network + if (!NetworkUtils.isNetworkAvailable(this)) { + return; + } + FetchMediaListPayload payload = + new FetchMediaListPayload(mSite, MediaStore.DEFAULT_NUM_MEDIA_PER_FETCH, false); + mDispatcher.dispatch(MediaActionBuilder.newFetchMediaListAction(payload)); + } + + @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN_ORDERED) public void onEditorThemeChanged(OnEditorThemeChanged event) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index def87ba9aa2b..07ad84298e8d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -50,6 +50,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( // the StoryRepository didn't have it but we have editable serialized slides so, // create a new Story from scratch with these deserialized StoryFrameItems var allStorySlidesAreEditable: Boolean = true + var noSlidesLoaded = false var storyIndex = StoryRepository.DEFAULT_NONE_SELECTED storyRepositoryWrapper.loadStory(storyIndex) storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() @@ -71,21 +72,28 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( site, tmpMediaIdsLong ) - for (mediaModel in mediaModelList) { - storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( - Uri.parse(mediaModel.url), - mediaModel.isVideo - ) - storyFrameItem.id = mediaModel.mediaId.toString() - storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + if (mediaModelList.size == 0) { + noSlidesLoaded = true + } else { + for (mediaModel in mediaModelList) { + storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( + Uri.parse(mediaModel.url), + mediaModel.isVideo + ) + storyFrameItem.id = mediaModel.mediaId.toString() + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + } } } } - return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable) + return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable, noSlidesLoaded) } - data class ReCreateStoryResult(val storyIndex: StoryIndex, val allStorySlidesAreEditable: Boolean) + data class ReCreateStoryResult( + val storyIndex: StoryIndex, + val allStorySlidesAreEditable: Boolean, + val noSlidesLoaded: Boolean) } diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 4d066ad74c38..a4dc6e748ade 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2874,6 +2874,8 @@ Limited Story Editing This story was edited on a different device and the ability to edit certain objects may be limited. + Can\'t edit Story + This story was created on a different device and can\'t be edited at this moment. Capture Flip camera Flash From 190b1b7a6f4d71a441dfb8e0744cdf3dcfb4ed79 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 10:39:15 -0300 Subject: [PATCH 030/343] updated stories lib hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 68fc2ec21ccc..a44d8e2937d9 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 68fc2ec21ccce9af40f89bca3dcc57a914dd291e +Subproject commit a44d8e2937d97ac1a04670add18869c99304b6a0 From 4698adefd2cc5a7eb8a880fda0d3b13110972d56 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 10:51:14 -0300 Subject: [PATCH 031/343] removed unused imports --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index ac5871c852fc..e1ea792be876 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -163,8 +163,6 @@ import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.StoryRepositoryWrapper; -import org.wordpress.android.ui.stories.prefs.StoriesPrefs; -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; From 143eb2e0d1e14ebea4e5961374c909711b660177 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 10:55:44 -0300 Subject: [PATCH 032/343] updated dialog OK button label --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index e1ea792be876..ecda0a9db427 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3049,7 +3049,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); builder.setMessage(getString(R.string.dialog_edit_story_unavailable_message)); - builder.setPositiveButton(R.string.yes, (dialog, id) -> { + builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { dialog.dismiss(); }); AlertDialog dialog = builder.create(); From aacfac78edae8c2b9bd3ffd962b1140d4dad7c07 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 10:57:18 -0300 Subject: [PATCH 033/343] fixed merge conflict, moving call within failedLoadingOrReCreatingStory check --- .../android/ui/posts/EditPostActivity.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index b902bfb94aa9..6ac488e243a6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3048,8 +3048,15 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } if (!failedLoadingOrReCreatingStory) { - // Story instance loaded or re-created! Load it - ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); + // Story instance loaded or re-created! Load it onto the StoryComposer for editing now + ActivityLauncher.editStoryForResult( + this, + mSite, + storyIndex, + allStorySlidesAreEditable, + true, + blockId + ); } else { // unfortunately we couldn't even load the remote media Ids indicated by the StoryBLock so we can't allow // editing at this time :( @@ -3062,16 +3069,6 @@ public void onTrackableEvent(TrackableEvent event, Map propertie AlertDialog dialog = builder.create(); dialog.show(); } - - // Story instance loaded or re-created! Load it onto the StoryComposer for editing now - ActivityLauncher.editStoryForResult( - this, - mSite, - storyIndex, - allStorySlidesAreEditable, - true, - blockId - ); } // FluxC events From 587960f7252af94373e4c687779b4be2aff1b973 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 11:15:30 -0300 Subject: [PATCH 034/343] fixed lint warnings --- .../org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 6 ++++-- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 82b924756a21..b2c88bb789c2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -14,11 +14,13 @@ object StoriesPrefs { private const val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" private fun buildSlideKey(siteId: Long, mediaId: RemoteMediaId): String { - return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.mediaId.toString() + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + + KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.mediaId.toString() } private fun buildSlideKey(siteId: Long, mediaId: LocalMediaId): String { - return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.id.toString() + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + + KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.id.toString() } @JvmStatic diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 07ad84298e8d..ca3fe97647a5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -93,7 +93,6 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( data class ReCreateStoryResult( val storyIndex: StoryIndex, val allStorySlidesAreEditable: Boolean, - val noSlidesLoaded: Boolean) + val noSlidesLoaded: Boolean + ) } - - From 9ef826de822f84173ad66caa078755ac6e1a8b1b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 18 Sep 2020 11:43:50 -0300 Subject: [PATCH 035/343] fixed lint warnings --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 9d2be45cd60a..8a85a273d29c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -448,7 +448,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), // TODO // here take the StoryFrameItems from the current Story, and build a val storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) - val updatedStoryBlock= + val updatedStoryBlock = saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockString( getMediaFilesFromBackgroundMediaIdsInStory(storyIndex) ) @@ -462,7 +462,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } } - private fun getMediaFilesFromBackgroundMediaIdsInStory(storyIdx: StoryIndex) : List { + private fun getMediaFilesFromBackgroundMediaIdsInStory(storyIdx: StoryIndex): List { val mediaFiles = ArrayList() val story = StoryRepository.getStoryAtIndex(storyIdx) for (frame in story.frames) { From d7884c8b370d223eac776a57162000c371ba006c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 21 Sep 2020 13:15:54 -0300 Subject: [PATCH 036/343] modified the bridge to have instances of EditorMedialListener so these know if this is an edit mode operation for each SaveResult --- .../media/StoryMediaSaveUploadBridge.kt | 110 +++++++++--------- 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 7592c889bc16..2ccba4300b4a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -55,7 +55,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( private val postUtils: PostUtilsWrapper, private val eventBusWrapper: EventBusWrapper, @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher -) : CoroutineScope, LifecycleObserver, EditorMediaListener { +) : CoroutineScope, LifecycleObserver { // region Fields private var job: Job = Job() private lateinit var appContext: Context @@ -92,17 +92,71 @@ class StoryMediaSaveUploadBridge @Inject constructor( // let's invoke the UploadService and enqueue all the files that were saved by the FrameSaveService val frames = StoryRepository.getStoryAtIndex(saveResult.storyIndex).frames val uriList = frames.map { Uri.fromFile(it.composedFrameFile) } - addNewMediaItemsToPostAsync(site, uriList) + addNewMediaItemsToPostAsync(site, uriList, saveResult.isEditMode) } - private fun addNewMediaItemsToPostAsync(site: SiteModel, uriList: List) { + private fun addNewMediaItemsToPostAsync(site: SiteModel, uriList: List, isEditMode: Boolean) { // this is similar to addNewMediaItemsToEditorAsync in EditorMedia launch { + val localEditorMediaListener = object: EditorMediaListener { + override fun appendMediaFiles(mediaFiles: Map) { + if (!isEditMode) { + saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( + editPostRepository, + ArrayList(mediaFiles.values) + ) + } else { + // no op: in edit mode, we're handling replacing of the block's mediaFiles in Gutenberg + } + } + + override fun getImmutablePost(): PostImmutableModel { + return editPostRepository.getPost()!! + } + + override fun syncPostObjectWithUiAndSaveIt(listener: OnPostUpdatedFromUIListener?) { + // no op + // WARNING: don't remove this, we need to call the listener no matter what, so save & upload actually happen + listener?.onPostUpdatedFromUI(null) + } + + override fun advertiseImageOptimization(listener: () -> Unit) { + // no op + } + + override fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) { + // in order to support Story editing capabilities, we save a serialized version of the Story slides + // after their composedFrameFiles have been processed. + + // here we change the ids on the actual StoryFrameItems, and also update the flattened / composed image + // urls with the new URLs which may have been replaced after image optimization + for (story in StoryRepository.getImmutableStories()) { + // find the MediaModel for a given Uri from composedFrameFile + for (frame in story.frames) { + // if the old URI in frame.composedFrameFile exists as a key in the passed map, then update that + // value with the new (probably optimized) URL and also keep track of the new id. + val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) + mediaModel?.let { + frame.id = it.id.toString() + StoriesPrefs.saveSlideWithLocalId( + appContext, + it.localSiteId.toLong(), + // use the local id to save the original, will be replaced later + // with mediaModel.mediaId after uploading to the remote site + LocalMediaId(it.id.toLong()), + frame + ) + } + } + } + } + } + addLocalMediaToPostUseCase.addNewMediaToEditorAsync( uriList, site, freshlyTaken = false, // we don't care about this - editorMediaListener = this@StoryMediaSaveUploadBridge, + editorMediaListener = localEditorMediaListener, doUploadAfterAdding = true ) postUtils.preparePostForPublish(requireNotNull(editPostRepository.getEditablePost()), site) @@ -155,52 +209,4 @@ class StoryMediaSaveUploadBridge @Inject constructor( return (event.isSuccess() && event.frameSaveResult.size == StoryRepository.getStoryAtIndex(event.storyIndex).frames.size) } - - override fun appendMediaFiles(mediaFiles: Map) { - saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( - editPostRepository, - ArrayList(mediaFiles.values) - ) - } - - override fun getImmutablePost(): PostImmutableModel { - return editPostRepository.getPost()!! - } - - override fun syncPostObjectWithUiAndSaveIt(listener: OnPostUpdatedFromUIListener?) { - // no op - // WARNING: don't remove this, we need to call the listener no matter what, so save & upload actually happen - listener?.onPostUpdatedFromUI(null) - } - - override fun advertiseImageOptimization(listener: () -> Unit) { - // no op - } - - override fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) { - // in order to support Story editing capabilities, we save a serialized version of the Story slides - // after their composedFrameFiles have been processed. - - // here we change the ids on the actual StoryFrameItems, and also update the flattened / composed image - // urls with the new URLs which may have been replaced after image optimization - for (story in StoryRepository.getImmutableStories()) { - // find the MediaModel for a given Uri from composedFrameFile - for (frame in story.frames) { - // if the old URI in frame.composedFrameFile exists as a key in the passed map, then update that - // value with the new (probably optimized) URL and also keep track of the new id. - val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) - mediaModel?.let { - frame.id = it.id.toString() - StoriesPrefs.saveSlideWithLocalId( - appContext, - it.localSiteId.toLong(), - // use the local id to save the original, will be replaced later - // with mediaModel.mediaId after uploading to the remote site - LocalMediaId(it.id.toLong()), - frame - ) - } - } - } - } } From 7f5453b98d305154b2054c32f356a639b27d3f13 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 21 Sep 2020 15:15:31 -0300 Subject: [PATCH 037/343] udpated stories lib hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index a44d8e2937d9..ffc024efceca 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit a44d8e2937d97ac1a04670add18869c99304b6a0 +Subproject commit ffc024efcecaafe5055925ac9fb92e50e4fce453 From 1e41de1bcc1e60c159d03313d4ff24bb5250d89c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 21 Sep 2020 18:53:28 -0300 Subject: [PATCH 038/343] added all listeners to handle Story Save progress when editing a Story in Gutenberg --- .../android/ui/posts/EditPostActivity.java | 10 ++++++ .../media/AddLocalMediaToPostUseCase.kt | 2 +- .../ui/stories/StoryComposerActivity.kt | 7 ++-- .../media/StoryEditorMediaSaveListener.kt | 10 ++++++ .../gutenberg/GutenbergContainerFragment.java | 19 +++++++++++ .../gutenberg/GutenbergEditorFragment.java | 34 ++++++++++++++++++- .../gutenberg/StorySaveMediaListener.java | 10 ++++++ libs/gutenberg-mobile | 2 +- 8 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt create mode 100644 libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 6ac488e243a6..0e5e833bd541 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -163,6 +163,7 @@ import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.StoryRepositoryWrapper; +import org.wordpress.android.ui.stories.media.StoryEditorMediaSaveListener; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; @@ -329,6 +330,7 @@ enum RestartEditorOptions { private EditorFragmentAbstract mEditorFragment; private EditPostSettingsFragment mEditPostSettingsFragment; private EditorMediaUploadListener mEditorMediaUploadListener; + private StoryEditorMediaSaveListener mStoryEditorMediaSaveListener; private EditorPhotoPicker mEditorPhotoPicker; private ProgressDialog mProgressDialog; @@ -563,6 +565,10 @@ protected void onCreate(Bundle savedInstanceState) { if (mEditorFragment instanceof EditorMediaUploadListener) { mEditorMediaUploadListener = (EditorMediaUploadListener) mEditorFragment; } + + if (mEditorFragment instanceof StoryEditorMediaSaveListener) { + mStoryEditorMediaSaveListener = (StoryEditorMediaSaveListener) mEditorFragment; + } } if (mSite == null) { @@ -2142,6 +2148,10 @@ public Fragment getItem(int position) { reattachUploadingMediaForAztec(); } + + if (mEditorFragment instanceof StoryEditorMediaSaveListener) { + mStoryEditorMediaSaveListener = (StoryEditorMediaSaveListener) mEditorFragment; + } break; case PAGE_SETTINGS: mEditPostSettingsFragment = (EditPostSettingsFragment) fragment; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt index ccb97c3de05c..6deae0e3139f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt @@ -69,7 +69,7 @@ class AddLocalMediaToPostUseCase @Inject constructor( // here we pass a map of "old" (before optimisation) Uris to the new MediaModels which contain // both the mediaModel ids and the optimized media URLs. - // this way, the listener will be able to process from other models potining to the old URLs + // this way, the listener will be able to process from other models pointing to the old URLs // and make any needed updates editorMediaListener.onMediaModelsCreatedFromOptimizedUris( uriList.zip(createMediaModelsResult.mediaModels).toMap() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 8a85a273d29c..f43cb7361806 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -445,8 +445,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val savedContentIntent = Intent() val blockId = intent.extras.getString(ARG_STORY_BLOCK_ID) savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) - // TODO - // here take the StoryFrameItems from the current Story, and build a + val storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) val updatedStoryBlock = saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockString( @@ -455,6 +454,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), savedContentIntent.putExtra(ARG_STORY_BLOCK_UPDATED_CONTENT, updatedStoryBlock) savedContentIntent.putExtra(ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA, true) setResult(Activity.RESULT_OK, savedContentIntent) + + // TODO add tracks + processStorySaving() + finish() } else { // assume this is a new Post, and proceed to PrePublish bottom sheet diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt new file mode 100644 index 000000000000..04715ef2cafd --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt @@ -0,0 +1,10 @@ +package org.wordpress.android.ui.stories.media + +import org.wordpress.android.util.helpers.MediaFile + +interface StoryEditorMediaSaveListener { + fun onMediaSaveReattached(localId: String?, currentProgress: Float) + fun onMediaSaveSucceeded(localId: String?, mediaFile: MediaFile?) + fun onMediaSaveProgress(localId: String?, progress: Float) + fun onMediaSaveFailed(localId: String?) +} diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 944f7f0b1aed..690a76110e06 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -27,6 +27,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStorySavingReattachQueryListener; import java.util.ArrayList; @@ -53,6 +54,7 @@ public boolean hasReceivedAnyContent() { public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onMediaLibraryButtonListener, OnReattachQueryListener onReattachQueryListener, + OnStorySavingReattachQueryListener onStorySavingReattachQueryListener, OnEditorMountListener onEditorMountListener, OnEditorAutosaveListener onEditorAutosaveListener, OnAuthHeaderRequestedListener onAuthHeaderRequestedListener, @@ -70,6 +72,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener viewGroup, onMediaLibraryButtonListener, onReattachQueryListener, + onStorySavingReattachQueryListener, onEditorMountListener, onEditorAutosaveListener, onAuthHeaderRequestedListener, @@ -218,4 +221,20 @@ public void updateCapabilities(GutenbergPropsBuilder gutenbergPropsBuilder) { GutenbergProps gutenbergProps = gutenbergPropsBuilder.build(getActivity(), mHtmlModeEnabled); mWPAndroidGlueCode.updateCapabilities(gutenbergProps); } + + public void clearFileSaveStatus(final int mediaId) { + mWPAndroidGlueCode.clearFileSaveStatus(mediaId); + } + + public void mediaFileSaveProgress(final int mediaId, final float progress) { + mWPAndroidGlueCode.mediaFileSaveProgress(mediaId, progress); + } + + public void mediaFileSaveFailed(final int mediaId) { + mWPAndroidGlueCode.mediaFileSaveFailed(mediaId); + } + + public void mediaFileSaveSucceeded(final int mediaId, final String mediaUrl, final int serverMediaId) { + mWPAndroidGlueCode.mediaFileSaveSucceeded(mediaId, mediaUrl, serverMediaId); + } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 0d18afdec9e6..3fe5f0b178e7 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -63,6 +63,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStorySavingReattachQueryListener; import java.util.ArrayList; import java.util.HashMap; @@ -76,7 +77,8 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements EditorMediaUploadListener, IHistoryListener, - EditorThemeUpdateListener { + EditorThemeUpdateListener, + StorySaveMediaListener { private static final String GUTENBERG_EDITOR_NAME = "gutenberg"; private static final String KEY_HTML_MODE_ENABLED = "KEY_HTML_MODE_ENABLED"; private static final String KEY_EDITOR_DID_MOUNT = "KEY_EDITOR_DID_MOUNT"; @@ -270,6 +272,14 @@ public void onQueryCurrentProgressForUploadingMedia() { updateMediaProgress(); } }, + new OnStorySavingReattachQueryListener() { + @Override public void onQueryCurrentProgressForStoryMediaSaving() { + // TODO: probably go through mFailedMediaIds, and see if any block in the post content + // has these mediaFIleIds. If there's a match, mark such a block in FAILED state. + updateFailedMediaState(); + updateMediaProgress(); + } + }, new OnEditorMountListener() { @Override public void onEditorDidMount(ArrayList unsupportedBlocks) { @@ -997,4 +1007,26 @@ public void onGalleryMediaUploadSucceeded(final long galleryId, long remoteMedia public void onEditorThemeUpdated(Bundle editorTheme) { getGutenbergContainerFragment().updateTheme(editorTheme); } + + @Override public void onMediaSaveReattached(String localId, float currentProgress) { + mUploadingMediaProgressMax.put(localId, currentProgress); + getGutenbergContainerFragment().mediaFileSaveProgress(Integer.valueOf(localId), currentProgress); + } + + @Override public void onMediaSaveSucceeded(String localId, MediaFile mediaFile) { + mUploadingMediaProgressMax.remove(localId); + getGutenbergContainerFragment().mediaFileSaveSucceeded(Integer.valueOf(localId), mediaFile.getFileURL(), + Integer.valueOf(mediaFile.getMediaId())); + } + + @Override public void onMediaSaveProgress(String localId, float progress) { + mUploadingMediaProgressMax.put(localId, progress); + getGutenbergContainerFragment().mediaFileSaveProgress(Integer.valueOf(localId), progress); + } + + @Override public void onMediaSaveFailed(String localId) { + getGutenbergContainerFragment().mediaFileSaveFailed(Integer.valueOf(localId)); + mFailedMediaIds.add(localId); + mUploadingMediaProgressMax.remove(localId); + } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java new file mode 100644 index 000000000000..a7fa806713cb --- /dev/null +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java @@ -0,0 +1,10 @@ +package org.wordpress.android.editor.gutenberg; + +import org.wordpress.android.util.helpers.MediaFile; + +public interface StorySaveMediaListener { + void onMediaSaveReattached(String localId, float currentProgress); + void onMediaSaveSucceeded(String localId, MediaFile mediaFile); + void onMediaSaveProgress(String localId, float progress); + void onMediaSaveFailed(String localId); +} diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 00a0cc0b7e84..0dcfe93057eb 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 00a0cc0b7e849521595d428d5161dea557aa29e9 +Subproject commit 0dcfe93057eb868ced7edd5c4029ec95d2c05c8d From e532633ee37f282b810e01032a47a286fbcc8d41 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 21 Sep 2020 21:11:07 -0300 Subject: [PATCH 039/343] updated stories library commit hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index ffc024efceca..676da24d18e5 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit ffc024efcecaafe5055925ac9fb92e50e4fce453 +Subproject commit 676da24d18e560d609170e51893031c18a5f2a0e From 2a74a3580f8838a26b2100e91ba9283b859c7f4b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 22 Sep 2020 09:06:07 -0300 Subject: [PATCH 040/343] removed unused param constant key --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 2 -- .../android/editor/gutenberg/GutenbergEditorFragment.java | 1 - 2 files changed, 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index f43cb7361806..ed5ee07be6e7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -34,7 +34,6 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.analytics.AnalyticsTracker.Stat.PREPUBLISHING_BOTTOM_SHEET_OPENED import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_ID import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_BLOCK_UPDATED_CONTENT -import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment.ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel @@ -452,7 +451,6 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), getMediaFilesFromBackgroundMediaIdsInStory(storyIndex) ) savedContentIntent.putExtra(ARG_STORY_BLOCK_UPDATED_CONTENT, updatedStoryBlock) - savedContentIntent.putExtra(ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA, true) setResult(Activity.RESULT_OK, savedContentIntent) // TODO add tracks diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 3fe5f0b178e7..e2877088165d 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -89,7 +89,6 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements private static final String ARG_STORY_EDITOR_REQUEST_CODE = "param_sory_editor_request_code"; public static final String ARG_STORY_BLOCK_ID = "story_block_id"; public static final String ARG_STORY_BLOCK_UPDATED_CONTENT = "story_block_updated_content"; - public static final String ARG_STORY_STATUS_WAIT_FOR_FLATTENED_MEDIA = "status_wait_for_flattened_media"; private static final int CAPTURE_PHOTO_PERMISSION_REQUEST_CODE = 101; private static final int CAPTURE_VIDEO_PERMISSION_REQUEST_CODE = 102; From b545212b018425cb00993cfebc95f0f223d8a419 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 22 Sep 2020 09:06:56 -0300 Subject: [PATCH 041/343] added FrameSaveXyz status EventBus events for Story frame flattened media being saved --- .../android/ui/posts/EditPostActivity.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 0e5e833bd541..21e5b30f267a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -40,6 +40,10 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted; +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed; +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress; +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart; import com.wordpress.stories.compose.story.StoryRepository; import org.greenrobot.eventbus.EventBus; @@ -3081,6 +3085,59 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } } + // Story Frame Save Service events + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStoryFrameSaveStart(FrameSaveStart event) { + if (isFinishing()) { + return; + } + String localMediaId = String.valueOf(event.getFrameId()); + if (mStoryEditorMediaSaveListener != null) { + mStoryEditorMediaSaveListener.onMediaSaveReattached(localMediaId, 0.0f); + } + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStoryFrameSaveProgress(FrameSaveProgress event) { + if (isFinishing()) { + return; + } + String localMediaId = String.valueOf(event.getFrameId()); + if (mStoryEditorMediaSaveListener != null) { + mStoryEditorMediaSaveListener.onMediaSaveProgress(localMediaId, event.getProgress()); + } + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { + if (isFinishing()) { + return; + } + String localMediaId = String.valueOf(event.getFrameId()); + if (mStoryEditorMediaSaveListener != null) { + MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); + if (mediaModel != null) { + MediaFile mediaFile = FluxCUtils.mediaFileFromMediaModel(mediaModel); + mStoryEditorMediaSaveListener.onMediaSaveSucceeded(localMediaId, mediaFile); + } + } + } + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStoryFrameSaveFailed(FrameSaveFailed event) { + if (isFinishing()) { + return; + } + String localMediaId = String.valueOf(event.getFrameId()); + if (mStoryEditorMediaSaveListener != null) { + mStoryEditorMediaSaveListener.onMediaSaveFailed(localMediaId); + } + } + // FluxC events @SuppressWarnings("unused") From baaad61feece01913108e95d8aa4d77fbc1857ee Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 23 Sep 2020 21:53:51 -0300 Subject: [PATCH 042/343] using StorySaveMediaListener and removed redundant interface StoryEditorMediaSaveListener --- .../android/ui/posts/EditPostActivity.java | 28 +++++++++---------- .../media/StoryEditorMediaSaveListener.kt | 10 ------- 2 files changed, 14 insertions(+), 24 deletions(-) delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 21e5b30f267a..483c01cc6c5a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -74,6 +74,7 @@ import org.wordpress.android.editor.gutenberg.GutenbergEditorFragment; import org.wordpress.android.editor.gutenberg.GutenbergPropsBuilder; import org.wordpress.android.editor.gutenberg.GutenbergWebViewAuthorizationData; +import org.wordpress.android.editor.gutenberg.StorySaveMediaListener; import org.wordpress.android.fluxc.Dispatcher; import org.wordpress.android.fluxc.action.AccountAction; import org.wordpress.android.fluxc.generated.AccountActionBuilder; @@ -167,7 +168,6 @@ import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.StoryRepositoryWrapper; -import org.wordpress.android.ui.stories.media.StoryEditorMediaSaveListener; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; @@ -334,7 +334,7 @@ enum RestartEditorOptions { private EditorFragmentAbstract mEditorFragment; private EditPostSettingsFragment mEditPostSettingsFragment; private EditorMediaUploadListener mEditorMediaUploadListener; - private StoryEditorMediaSaveListener mStoryEditorMediaSaveListener; + private StorySaveMediaListener mStorySaveMediaListener; private EditorPhotoPicker mEditorPhotoPicker; private ProgressDialog mProgressDialog; @@ -570,8 +570,8 @@ protected void onCreate(Bundle savedInstanceState) { mEditorMediaUploadListener = (EditorMediaUploadListener) mEditorFragment; } - if (mEditorFragment instanceof StoryEditorMediaSaveListener) { - mStoryEditorMediaSaveListener = (StoryEditorMediaSaveListener) mEditorFragment; + if (mEditorFragment instanceof StorySaveMediaListener) { + mStorySaveMediaListener = (StorySaveMediaListener) mEditorFragment; } } @@ -2153,8 +2153,8 @@ public Fragment getItem(int position) { reattachUploadingMediaForAztec(); } - if (mEditorFragment instanceof StoryEditorMediaSaveListener) { - mStoryEditorMediaSaveListener = (StoryEditorMediaSaveListener) mEditorFragment; + if (mEditorFragment instanceof StorySaveMediaListener) { + mStorySaveMediaListener = (StorySaveMediaListener) mEditorFragment; } break; case PAGE_SETTINGS: @@ -3093,8 +3093,8 @@ public void onStoryFrameSaveStart(FrameSaveStart event) { return; } String localMediaId = String.valueOf(event.getFrameId()); - if (mStoryEditorMediaSaveListener != null) { - mStoryEditorMediaSaveListener.onMediaSaveReattached(localMediaId, 0.0f); + if (mStorySaveMediaListener != null) { + mStorySaveMediaListener.onMediaSaveReattached(localMediaId, 0.0f); } } @@ -3105,8 +3105,8 @@ public void onStoryFrameSaveProgress(FrameSaveProgress event) { return; } String localMediaId = String.valueOf(event.getFrameId()); - if (mStoryEditorMediaSaveListener != null) { - mStoryEditorMediaSaveListener.onMediaSaveProgress(localMediaId, event.getProgress()); + if (mStorySaveMediaListener != null) { + mStorySaveMediaListener.onMediaSaveProgress(localMediaId, event.getProgress()); } } @@ -3117,11 +3117,11 @@ public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { return; } String localMediaId = String.valueOf(event.getFrameId()); - if (mStoryEditorMediaSaveListener != null) { + if (mStorySaveMediaListener != null) { MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); if (mediaModel != null) { MediaFile mediaFile = FluxCUtils.mediaFileFromMediaModel(mediaModel); - mStoryEditorMediaSaveListener.onMediaSaveSucceeded(localMediaId, mediaFile); + mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, mediaFile); } } } @@ -3133,8 +3133,8 @@ public void onStoryFrameSaveFailed(FrameSaveFailed event) { return; } String localMediaId = String.valueOf(event.getFrameId()); - if (mStoryEditorMediaSaveListener != null) { - mStoryEditorMediaSaveListener.onMediaSaveFailed(localMediaId); + if (mStorySaveMediaListener != null) { + mStorySaveMediaListener.onMediaSaveFailed(localMediaId); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt deleted file mode 100644 index 04715ef2cafd..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMediaSaveListener.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.wordpress.android.ui.stories.media - -import org.wordpress.android.util.helpers.MediaFile - -interface StoryEditorMediaSaveListener { - fun onMediaSaveReattached(localId: String?, currentProgress: Float) - fun onMediaSaveSucceeded(localId: String?, mediaFile: MediaFile?) - fun onMediaSaveProgress(localId: String?, progress: Float) - fun onMediaSaveFailed(localId: String?) -} From a2e5aaab7d80c6fe86d39e9e2579c9f499b1b0b8 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 23 Sep 2020 21:55:21 -0300 Subject: [PATCH 043/343] using made-up temporaryIds out of StoryIndex-FrameIndex to paint save progress states on Story blocks that are currently being processsed --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 177 +++++++++++++----- .../ui/stories/StoryComposerActivity.kt | 39 ++-- .../media/StoryMediaSaveUploadBridge.kt | 7 + .../LoadStoryFromStoriesPrefsUseCase.kt | 1 + 4 files changed, 164 insertions(+), 60 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index e86876a16d80..fbd5da32f4f9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -29,14 +29,19 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { for (mediaFile in mediaFiles) { jsonArrayMediaFiles.add(buildMediaFileData(mediaFile)) } - val storyBlock = StoryBlockData(mediaFiles = jsonArrayMediaFiles) - return createGBStoryBlockStringFromJson(storyBlock) + return buildJetpackStoryBlockStringFromStoryMediaFileData(jsonArrayMediaFiles) + } + + fun buildJetpackStoryBlockStringFromStoryMediaFileData( + storyMediaFileDataList: ArrayList + ): String { + return createGBStoryBlockStringFromJson(StoryBlockData(mediaFiles = storyMediaFileDataList)) } private fun buildMediaFileData(mediaFile: MediaFile): StoryMediaFileData { return StoryMediaFileData( alt = "", - id = mediaFile.id, + id = mediaFile.id.toString(), link = StringUtils.notNullStr(mediaFile.fileURL), type = if (mediaFile.isVideo) "video" else "image", mime = StringUtils.notNullStr(mediaFile.mimeType), @@ -45,52 +50,123 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { ) } - fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(post: PostModel, mediaFile: MediaFile) { - // here we're going to first find the block header, obtain the JSON object, re-parse it, and re-build the block - // WARNING note we're assuming to have only one Story block here so, beware of that!! this will find - // the first match only, always. (shouldn't be a problem because we're always creating a new Post in the - // app, but this won't make the cut if we decide to allow editing. Which we'll do by integrating with - // the gutenberg parser / validator anyway. - val content = post.content - val jsonString: String = content.substring( - content.indexOf(HEADING_START) + HEADING_START.length, - content.indexOf(HEADING_END) + fun buildMediaFileDataWithTemporaryId(mediaFile: MediaFile, temporaryId: String): StoryMediaFileData { + return StoryMediaFileData( + alt = "", // notation abuse: we're repurposing this field to + // hold a temporaryId + id = TEMPORARY_ID_PREFIX + temporaryId, // mediaFile.id, + link = StringUtils.notNullStr(mediaFile.fileURL), + type = if (mediaFile.isVideo) "video" else "image", + mime = StringUtils.notNullStr(mediaFile.mimeType), + caption = "", + url = StringUtils.notNullStr(mediaFile.fileURL) ) - val gson = Gson() - val storyBlockData: StoryBlockData? = gson.fromJson(jsonString, StoryBlockData::class.java) - - val localMediaId = mediaFile.id - // now replace matching localMediaId with remoteMediaId in the mediaFileObjects, obtain the URLs and replace - storyBlockData?.mediaFiles?.filter { it.id == localMediaId }?.get(0)?.apply { - id = mediaFile.mediaId.toInt() - link = mediaFile.fileURL - url = mediaFile.fileURL - - // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. - val localIdKey = mediaFile.id.toLong() - val remoteIdKey = mediaFile.mediaId.toLong() - val localSiteId = post.localSiteId.toLong() - StoriesPrefs.getSlideWithLocalId( - WordPress.getContext(), - localSiteId, - LocalMediaId(localIdKey) - )?.let { - it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID - StoriesPrefs.saveSlideWithRemoteId( - WordPress.getContext(), - localSiteId, - RemoteMediaId(remoteIdKey), // use the new mediaId as key - it - ) - // now delete the old entry - StoriesPrefs.deleteSlideWithLocalId( - WordPress.getContext(), - localSiteId, - LocalMediaId(localIdKey) - ) + } + + fun cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost(editPostRepository: EditPostRepository) { + editPostRepository.update { postModel: PostModel -> + val gson = Gson() + findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( + postModel, + object : DoWithMediaFilesListener { + override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { + var processedContent = content + val storyBlockData: StoryBlockData? + = gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) + storyBlockData?.let { + if (hasTemporaryIdsInStoryData(it)) { + // here remove the whole mediaFiles attribute + processedContent = content.replace(mediaFilesJsonString, "") + } + } + return processedContent + } + } + ) + true + } + } + + private fun hasTemporaryIdsInStoryData(storyBlockData: StoryBlockData): Boolean { + val temporaryIds = storyBlockData.mediaFiles.filter { + it.alt.startsWith(TEMPORARY_ID_PREFIX) + } + return temporaryIds.size > 0 + } + + fun findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( + postModel: PostModel, + listener: DoWithMediaFilesListener + ) { + var content = postModel.content + // val contentMutable = StringBuilder(postModel.content) + + // find next Story Block + // evaluate if this has a temporary id mediafile + // --> remove mediaFiles entirely + // set start index and go up. + var storyBlockStartIndex = 0 + while (storyBlockStartIndex > -1 && storyBlockStartIndex < content.length) { + storyBlockStartIndex = content.indexOf(HEADING_START, storyBlockStartIndex) + if (storyBlockStartIndex > -1) { + val jsonString: String = content.substring( + storyBlockStartIndex + HEADING_START.length, + content.indexOf(HEADING_END)) + content = listener.doWithMediaFilesJsonString(content, jsonString) + storyBlockStartIndex += HEADING_START.length } } - post.setContent(createGBStoryBlockStringFromJson(requireNotNull(storyBlockData))) + + postModel.setContent(content) + } + + fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(postModel: PostModel, mediaFile: MediaFile) { + val gson = Gson() + findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( + postModel, + object : DoWithMediaFilesListener { + override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { + var processedContent = content + val storyBlockData: StoryBlockData? + = gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) + storyBlockData?.let { storyBlockDataNonNull -> + val localMediaId = mediaFile.id.toString() + // now replace matching localMediaId with remoteMediaId in the mediaFileObjects, obtain the URLs and replace + storyBlockDataNonNull.mediaFiles?.filter { it.id == localMediaId }?.get(0)?.apply { + id = mediaFile.mediaId + link = mediaFile.fileURL + url = mediaFile.fileURL + + // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. + val localIdKey = mediaFile.id.toLong() + val remoteIdKey = mediaFile.mediaId.toLong() + val localSiteId = postModel.localSiteId.toLong() + StoriesPrefs.getSlideWithLocalId( + WordPress.getContext(), + localSiteId, + LocalMediaId(localIdKey) + )?.let { + it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID + StoriesPrefs.saveSlideWithRemoteId( + WordPress.getContext(), + localSiteId, + RemoteMediaId(remoteIdKey), // use the new mediaId as key + it + ) + // now delete the old entry + StoriesPrefs.deleteSlideWithLocalId( + WordPress.getContext(), + localSiteId, + LocalMediaId(localIdKey) + ) + } + } + processedContent = content.replace(mediaFilesJsonString, gson.toJson(storyBlockDataNonNull)) + } + return processedContent + } + } + ) } private fun createGBStoryBlockStringFromJson(storyBlock: StoryBlockData): String { @@ -98,13 +174,17 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { return HEADING_START + gson.toJson(storyBlock) + HEADING_END + DIV_PART + CLOSING_TAG } + interface DoWithMediaFilesListener { + fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String + } + data class StoryBlockData( val mediaFiles: List ) data class StoryMediaFileData( - val alt: String, - var id: Int, + var alt: String, + var id: String, var link: String, val type: String, val mime: String, @@ -113,6 +193,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { ) companion object { + const val TEMPORARY_ID_PREFIX = "tempid-" const val HEADING_START = "\n" const val DIV_PART = "
\n" diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index ed5ee07be6e7..6efdc7a8f549 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -22,6 +22,7 @@ import com.wordpress.stories.compose.PrepublishingEventProvider import com.wordpress.stories.compose.SnackbarProvider import com.wordpress.stories.compose.StoryDiscardListener import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult +import com.wordpress.stories.compose.story.StoryFrameItemType.VIDEO import com.wordpress.stories.compose.story.StoryIndex import com.wordpress.stories.compose.story.StoryRepository import com.wordpress.stories.compose.story.StoryRepository.DEFAULT_NONE_SELECTED @@ -60,12 +61,14 @@ import org.wordpress.android.ui.posts.PublishPost import org.wordpress.android.ui.posts.editor.media.AddExistingMediaSource.WP_MEDIA_LIBRARY import org.wordpress.android.ui.posts.editor.media.EditorMediaListener import org.wordpress.android.ui.posts.prepublishing.PrepublishingBottomSheetListener +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.StoryMediaFileData import org.wordpress.android.ui.stories.media.StoryEditorMedia import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState import org.wordpress.android.ui.utils.AuthenticationUtils import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.FluxCUtilsWrapper import org.wordpress.android.util.ListUtils +import org.wordpress.android.util.StringUtils import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper @@ -446,10 +449,18 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) val storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) + // if we are editing this Story Block, then the id is assured to be a remote media file id, but + // the frame no longer points to such media Id on the site given we are just about to save a + // new flattened media. Hence, we need to set a new temporary Id we can use to identify + // this frame within the Gutenberg Story block inside a Post, and match it to an existing Story frame in + // our StoryRepository. + // All of this while still keeping a valid "old" remote URl and mediaId so the block is still + // rendered as non-empty on mobile gutenberg while the actual flattening happens on the service. val updatedStoryBlock = - saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockString( - getMediaFilesFromBackgroundMediaIdsInStory(storyIndex) + saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockStringFromStoryMediaFileData( + buildStoryMediaFileDataListFromStoryFrameIndexes(storyIndex) ) + savedContentIntent.putExtra(ARG_STORY_BLOCK_UPDATED_CONTENT, updatedStoryBlock) setResult(Activity.RESULT_OK, savedContentIntent) @@ -463,23 +474,27 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } } - private fun getMediaFilesFromBackgroundMediaIdsInStory(storyIdx: StoryIndex): List { - val mediaFiles = ArrayList() - val story = StoryRepository.getStoryAtIndex(storyIdx) - for (frame in story.frames) { + private fun buildStoryMediaFileDataListFromStoryFrameIndexes( + storyIndex: StoryIndex + ): ArrayList { + val storyMediaFileDataList = ArrayList() // holds media files + val story = StoryRepository.getStoryAtIndex(storyIndex) + for ((frameIndex, frame) in story.frames.withIndex()) { frame.id?.let { - // WARNING: we assume the only path to creation is by using the Story Creator (not mobile gutenberg - // by inserting a new block from the block picker) so, we ALWAYS should have a remote mediaId - // here. If things change, we'll need to cover the situation for finding a MediaModel from both - // a local ID (if not yet uploaded to the site) and a remote ID (after uploading to the site). val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) mediaFile?.let { - mediaFiles.add(it) + val storyMediaFileData = + saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( + mediaFile = it, + temporaryId = "$storyIndex-$frameIndex" + ) + frame.id = storyMediaFileData.id + storyMediaFileDataList.add(storyMediaFileData) } } } - return mediaFiles + return storyMediaFileDataList } override fun onSubmitButtonClicked(publishPost: PublishPost) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 2ccba4300b4a..5d859c59ee2d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -199,6 +199,13 @@ class StoryMediaSaveUploadBridge @Inject constructor( event.metadata?.let { val site = it.getSerializable(WordPress.SITE) as SiteModel editPostRepository.loadPostByLocalPostId(it.getInt(StoryComposerActivity.KEY_POST_LOCAL_ID)) + if (event.isEditMode) { + // we're done using the temporary ids, let's clean mediaFiles attribute from the blocks that have + // those + saveStoryGutenbergBlockUseCase.cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost( + editPostRepository + ) + } // media upload tracking already in addLocalMediaToPostUseCase.addNewMediaToEditorAsync addNewStoryFrameMediaItemsToPostAndUploadAsync(site, event) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index ca3fe97647a5..a4521e1cd8bc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -9,6 +9,7 @@ import dagger.Reusable import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import org.wordpress.android.ui.stories.prefs.StoriesPrefs.getSlideWithRemoteId From e5adabf6d0ac27dbf04771ff312ea0b380f34ad3 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 24 Sep 2020 08:14:35 -0300 Subject: [PATCH 044/343] made changes to support String mediaIds as opposed to ints, so we can repurpose them and also hold temporary-prefixed mediaIds while saving --- .../android/ui/posts/EditPostActivity.java | 26 ++++++++++++++----- .../ui/stories/StoryRepositoryWrapper.kt | 1 + .../gutenberg/GutenbergContainerFragment.java | 10 +++---- .../gutenberg/GutenbergEditorFragment.java | 11 ++++---- .../gutenberg/StorySaveMediaListener.java | 4 +-- libs/gutenberg-mobile | 2 +- 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 483c01cc6c5a..77a14621b2e5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -44,6 +44,7 @@ import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart; +import com.wordpress.stories.compose.story.StoryFrameItem; import com.wordpress.stories.compose.story.StoryRepository; import org.greenrobot.eventbus.EventBus; @@ -236,6 +237,7 @@ import static org.wordpress.android.analytics.AnalyticsTracker.Stat.APP_REVIEWS_EVENT_INCREMENTED_BY_PUBLISHING_POST_OR_PAGE; import static org.wordpress.android.imageeditor.preview.PreviewImageFragment.PREVIEW_IMAGE_REDUCED_SIZE_FACTOR; import static org.wordpress.android.ui.history.HistoryDetailContainerFragment.KEY_REVISION; +import static org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.TEMPORARY_ID_PREFIX; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -391,6 +393,7 @@ enum RestartEditorOptions { @Inject ModalLayoutPickerFeatureConfig mModalLayoutPickerFeatureConfig; @Inject CrashLogging mCrashLogging; @Inject MediaPickerLauncher mMediaPickerLauncher; + @Inject StoryRepositoryWrapper mStoryRepositoryWrapper; private StorePostViewModel mViewModel; @@ -3035,7 +3038,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { LoadStoryFromStoriesPrefsUseCase loadStoryFromStoriesPrefsUseCase = new LoadStoryFromStoriesPrefsUseCase( - new StoryRepositoryWrapper(), + mStoryRepositoryWrapper, mSite, mMediaStore, this @@ -3116,12 +3119,23 @@ public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { if (isFinishing()) { return; } - String localMediaId = String.valueOf(event.getFrameId()); + String localMediaId = event.getFrameId(); if (mStorySaveMediaListener != null) { - MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); - if (mediaModel != null) { - MediaFile mediaFile = FluxCUtils.mediaFileFromMediaModel(mediaModel); - mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, mediaFile); + // check whether this is a temporary file being just saved (so we don't have a proper local MediaModel yet) + // catch ( NumberFormatException e) + if (localMediaId.startsWith(TEMPORARY_ID_PREFIX)) { + StoryFrameItem frame = + mStoryRepositoryWrapper.getStoryAtIndex( + event.getStoryIndex()).getFrames().get(event.getFrameIndex() + ); + mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, + frame.getComposedFrameFile().getAbsolutePath()); + } else { + MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); + if (mediaModel != null) { + MediaFile mediaFile = FluxCUtils.mediaFileFromMediaModel(mediaModel); + mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, mediaFile.getFileURL()); + } } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt index 7b89cea9bbda..f735e71a50d7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt @@ -12,4 +12,5 @@ class StoryRepositoryWrapper @Inject constructor() { fun getCurrentStoryIndex(): StoryIndex = StoryRepository.currentStoryIndex fun loadStory(storyIndex: StoryIndex) = StoryRepository.loadStory(storyIndex) fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = StoryRepository.addStoryFrameItemToCurrentStory(item) + fun getStoryAtIndex(index: StoryIndex) = StoryRepository.getStoryAtIndex(index) } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 690a76110e06..4bf0e1b587df 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -222,19 +222,19 @@ public void updateCapabilities(GutenbergPropsBuilder gutenbergPropsBuilder) { mWPAndroidGlueCode.updateCapabilities(gutenbergProps); } - public void clearFileSaveStatus(final int mediaId) { + public void clearFileSaveStatus(final String mediaId) { mWPAndroidGlueCode.clearFileSaveStatus(mediaId); } - public void mediaFileSaveProgress(final int mediaId, final float progress) { + public void mediaFileSaveProgress(final String mediaId, final float progress) { mWPAndroidGlueCode.mediaFileSaveProgress(mediaId, progress); } - public void mediaFileSaveFailed(final int mediaId) { + public void mediaFileSaveFailed(final String mediaId) { mWPAndroidGlueCode.mediaFileSaveFailed(mediaId); } - public void mediaFileSaveSucceeded(final int mediaId, final String mediaUrl, final int serverMediaId) { - mWPAndroidGlueCode.mediaFileSaveSucceeded(mediaId, mediaUrl, serverMediaId); + public void mediaFileSaveSucceeded(final String mediaId, final String mediaUrl) { + mWPAndroidGlueCode.mediaFileSaveSucceeded(mediaId, mediaUrl); } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index e2877088165d..f6834f4e8415 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -1009,22 +1009,21 @@ public void onEditorThemeUpdated(Bundle editorTheme) { @Override public void onMediaSaveReattached(String localId, float currentProgress) { mUploadingMediaProgressMax.put(localId, currentProgress); - getGutenbergContainerFragment().mediaFileSaveProgress(Integer.valueOf(localId), currentProgress); + getGutenbergContainerFragment().mediaFileSaveProgress(localId, currentProgress); } - @Override public void onMediaSaveSucceeded(String localId, MediaFile mediaFile) { + @Override public void onMediaSaveSucceeded(String localId, String mediaUrl) { mUploadingMediaProgressMax.remove(localId); - getGutenbergContainerFragment().mediaFileSaveSucceeded(Integer.valueOf(localId), mediaFile.getFileURL(), - Integer.valueOf(mediaFile.getMediaId())); + getGutenbergContainerFragment().mediaFileSaveSucceeded(localId, mediaUrl); } @Override public void onMediaSaveProgress(String localId, float progress) { mUploadingMediaProgressMax.put(localId, progress); - getGutenbergContainerFragment().mediaFileSaveProgress(Integer.valueOf(localId), progress); + getGutenbergContainerFragment().mediaFileSaveProgress(localId, progress); } @Override public void onMediaSaveFailed(String localId) { - getGutenbergContainerFragment().mediaFileSaveFailed(Integer.valueOf(localId)); + getGutenbergContainerFragment().mediaFileSaveFailed(localId); mFailedMediaIds.add(localId); mUploadingMediaProgressMax.remove(localId); } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java index a7fa806713cb..0872141119bd 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java @@ -1,10 +1,8 @@ package org.wordpress.android.editor.gutenberg; -import org.wordpress.android.util.helpers.MediaFile; - public interface StorySaveMediaListener { void onMediaSaveReattached(String localId, float currentProgress); - void onMediaSaveSucceeded(String localId, MediaFile mediaFile); + void onMediaSaveSucceeded(String localId, String mediaUrl); void onMediaSaveProgress(String localId, float progress); void onMediaSaveFailed(String localId); } diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 0dcfe93057eb..1697f9f731f5 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 0dcfe93057eb868ced7edd5c4029ec95d2c05c8d +Subproject commit 1697f9f731f5f31822dac08e86c1f499f7306c34 From 3db323532dd78d6e9b5331efc39f7e3f2a37e845 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 24 Sep 2020 08:32:04 -0300 Subject: [PATCH 045/343] using StoryRepositoryWrapper so it's injectable and lets StoryMediaSaveUploadBridge in good testability state --- .../android/ui/stories/StoryRepositoryWrapper.kt | 1 + .../ui/stories/media/StoryMediaSaveUploadBridge.kt | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt index f735e71a50d7..16d21f948df1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt @@ -13,4 +13,5 @@ class StoryRepositoryWrapper @Inject constructor() { fun loadStory(storyIndex: StoryIndex) = StoryRepository.loadStory(storyIndex) fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = StoryRepository.addStoryFrameItemToCurrentStory(item) fun getStoryAtIndex(index: StoryIndex) = StoryRepository.getStoryAtIndex(index) + fun getImmutableStories() = StoryRepository.getImmutableStories() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 5d859c59ee2d..38bebbd89391 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -8,7 +8,6 @@ import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.OnLifecycleEvent import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult -import com.wordpress.stories.compose.story.StoryRepository import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -29,6 +28,7 @@ import org.wordpress.android.ui.posts.editor.media.EditorMediaListener import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase import org.wordpress.android.ui.stories.StoriesTrackerHelper import org.wordpress.android.ui.stories.StoryComposerActivity +import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId import org.wordpress.android.ui.uploads.UploadServiceFacade @@ -54,6 +54,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( private val networkUtils: NetworkUtilsWrapper, private val postUtils: PostUtilsWrapper, private val eventBusWrapper: EventBusWrapper, + private val storyRepositoryWrapper: StoryRepositoryWrapper, @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher ) : CoroutineScope, LifecycleObserver { // region Fields @@ -90,7 +91,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( // region Adding new composed / processed frames to a Story post private fun addNewStoryFrameMediaItemsToPostAndUploadAsync(site: SiteModel, saveResult: StorySaveResult) { // let's invoke the UploadService and enqueue all the files that were saved by the FrameSaveService - val frames = StoryRepository.getStoryAtIndex(saveResult.storyIndex).frames + val frames = storyRepositoryWrapper.getStoryAtIndex(saveResult.storyIndex).frames val uriList = frames.map { Uri.fromFile(it.composedFrameFile) } addNewMediaItemsToPostAsync(site, uriList, saveResult.isEditMode) } @@ -130,7 +131,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( // here we change the ids on the actual StoryFrameItems, and also update the flattened / composed image // urls with the new URLs which may have been replaced after image optimization - for (story in StoryRepository.getImmutableStories()) { + for (story in storyRepositoryWrapper.getImmutableStories()) { // find the MediaModel for a given Uri from composedFrameFile for (frame in story.frames) { // if the old URI in frame.composedFrameFile exists as a key in the passed map, then update that @@ -214,6 +215,6 @@ class StoryMediaSaveUploadBridge @Inject constructor( private fun isStorySavingComplete(event: StorySaveResult): Boolean { return (event.isSuccess() && - event.frameSaveResult.size == StoryRepository.getStoryAtIndex(event.storyIndex).frames.size) + event.frameSaveResult.size == storyRepositoryWrapper.getStoryAtIndex(event.storyIndex).frames.size) } } From d2af486bcc02a8739b95944d959e0d84d5c7a26e Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 25 Sep 2020 10:43:03 -0300 Subject: [PATCH 046/343] updated gutenberg-mobile commit hash, also passing overall story save progress calculation in progress event --- .../android/ui/posts/EditPostActivity.java | 20 +++++++++++++++---- libs/gutenberg-mobile | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 77a14621b2e5..693a40fe42b2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -40,10 +40,12 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; +import com.wordpress.stories.compose.frame.StorySaveEvents; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart; +import com.wordpress.stories.compose.story.Story; import com.wordpress.stories.compose.story.StoryFrameItem; import com.wordpress.stories.compose.story.StoryRepository; @@ -3124,12 +3126,22 @@ public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { // check whether this is a temporary file being just saved (so we don't have a proper local MediaModel yet) // catch ( NumberFormatException e) if (localMediaId.startsWith(TEMPORARY_ID_PREFIX)) { - StoryFrameItem frame = - mStoryRepositoryWrapper.getStoryAtIndex( - event.getStoryIndex()).getFrames().get(event.getFrameIndex() - ); + Story story = mStoryRepositoryWrapper.getStoryAtIndex(event.getStoryIndex()); + StoryFrameItem frame = story.getFrames().get(event.getFrameIndex()); mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, frame.getComposedFrameFile().getAbsolutePath()); + + // calculate progress and emit overall story progress update signal + int successCount = 0; + for (StoryFrameItem frameItem : story.getFrames()) { + if (frameItem.getComposedFrameFile() != null + && frameItem.getSaveResultReason() instanceof StorySaveEvents.SaveResultReason.SaveSuccess) { + successCount++; + } + } + + float totalProgress = successCount / story.getFrames().size(); + mStorySaveMediaListener.onMediaSaveProgress(localMediaId, totalProgress); } else { MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); if (mediaModel != null) { diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 1697f9f731f5..7e0dc26f505b 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 1697f9f731f5f31822dac08e86c1f499f7306c34 +Subproject commit 7e0dc26f505ba096cf8f3da27b3f806f18fb8827 From 5874a4863187b3393a3274ce9a242396e0ac55e8 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 25 Sep 2020 16:08:50 -0300 Subject: [PATCH 047/343] added progress calculation and progress calls --- .../android/ui/posts/EditPostActivity.java | 58 ++++++++++++++----- .../ui/stories/StoryRepositoryWrapper.kt | 2 + libs/stories-android | 2 +- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 693a40fe42b2..9f3578ec9ec1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -45,6 +45,7 @@ import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart; +import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult; import com.wordpress.stories.compose.story.Story; import com.wordpress.stories.compose.story.StoryFrameItem; import com.wordpress.stories.compose.story.StoryRepository; @@ -3099,7 +3100,8 @@ public void onStoryFrameSaveStart(FrameSaveStart event) { } String localMediaId = String.valueOf(event.getFrameId()); if (mStorySaveMediaListener != null) { - mStorySaveMediaListener.onMediaSaveReattached(localMediaId, 0.0f); + float progress = mStoryRepositoryWrapper.getCurrentStorySaveProgress(event.getStoryIndex(), 0.0f); + mStorySaveMediaListener.onMediaSaveReattached(localMediaId, progress); } } @@ -3111,7 +3113,11 @@ public void onStoryFrameSaveProgress(FrameSaveProgress event) { } String localMediaId = String.valueOf(event.getFrameId()); if (mStorySaveMediaListener != null) { - mStorySaveMediaListener.onMediaSaveProgress(localMediaId, event.getProgress()); + float progress = mStoryRepositoryWrapper.getCurrentStorySaveProgress( + event.getStoryIndex(), + event.getProgress() + ); + mStorySaveMediaListener.onMediaSaveProgress(localMediaId, progress); } } @@ -3127,20 +3133,14 @@ public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { // catch ( NumberFormatException e) if (localMediaId.startsWith(TEMPORARY_ID_PREFIX)) { Story story = mStoryRepositoryWrapper.getStoryAtIndex(event.getStoryIndex()); + + // first, update the media's url StoryFrameItem frame = story.getFrames().get(event.getFrameIndex()); mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, - frame.getComposedFrameFile().getAbsolutePath()); - - // calculate progress and emit overall story progress update signal - int successCount = 0; - for (StoryFrameItem frameItem : story.getFrames()) { - if (frameItem.getComposedFrameFile() != null - && frameItem.getSaveResultReason() instanceof StorySaveEvents.SaveResultReason.SaveSuccess) { - successCount++; - } - } + Uri.fromFile(frame.getComposedFrameFile()).toString()); - float totalProgress = successCount / story.getFrames().size(); + // now update progress + float totalProgress = mStoryRepositoryWrapper.getCurrentStorySaveProgress(event.getStoryIndex(), 0.0f); mStorySaveMediaListener.onMediaSaveProgress(localMediaId, totalProgress); } else { MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); @@ -3159,8 +3159,38 @@ public void onStoryFrameSaveFailed(FrameSaveFailed event) { return; } String localMediaId = String.valueOf(event.getFrameId()); +// if (mStorySaveMediaListener != null) { +// mStorySaveMediaListener.onMediaSaveFailed(localMediaId); +// } + // just update progress, we may have still some other frames in this story that need be saved. + // we will send the Failed signal once all the Story frames have been processed if (mStorySaveMediaListener != null) { - mStorySaveMediaListener.onMediaSaveFailed(localMediaId); + float progress = mStoryRepositoryWrapper.getCurrentStorySaveProgress(event.getStoryIndex(), 0.0f); + mStorySaveMediaListener.onMediaSaveReattached(localMediaId, progress); + } + } + + + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStorySaveProcessFinished(StorySaveResult event) { + if (isFinishing()) { + return; + } + + Story story = mStoryRepositoryWrapper.getStoryAtIndex(event.getStoryIndex()); + if (event.isSuccess() && event.getFrameSaveResult().size() == story.getFrames().size()) { + // take the first frame IDs and mediaUri + String localMediaId = String.valueOf(story.getFrames().get(0).getId()); + String mediaUrl = Uri.fromFile(story.getFrames().get(0).getComposedFrameFile()).toString(); + if (mStorySaveMediaListener != null) { + mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, mediaUrl); + } + } else { + String localMediaId = String.valueOf(story.getFrames().get(0).getId()); + if (mStorySaveMediaListener != null) { + mStorySaveMediaListener.onMediaSaveFailed(localMediaId); + } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt index 16d21f948df1..d5846c8652b7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt @@ -14,4 +14,6 @@ class StoryRepositoryWrapper @Inject constructor() { fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = StoryRepository.addStoryFrameItemToCurrentStory(item) fun getStoryAtIndex(index: StoryIndex) = StoryRepository.getStoryAtIndex(index) fun getImmutableStories() = StoryRepository.getImmutableStories() + fun getCurrentStorySaveProgress(storyIndex: StoryIndex, oneItemActualProgress: Float = 0.0F) + = StoryRepository.getCurrentStorySaveProgress(storyIndex, oneItemActualProgress) } diff --git a/libs/stories-android b/libs/stories-android index 676da24d18e5..823ab8aa66f0 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 676da24d18e560d609170e51893031c18a5f2a0e +Subproject commit 823ab8aa66f065f32fa69f66260569bce6ae7cee From fcef7a815c10c12ee499df0908f4231131acd1b5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 25 Sep 2020 17:15:07 -0300 Subject: [PATCH 048/343] implemented new onStorySaveResult gutenberg bridge method --- .../android/editor/gutenberg/GutenbergContainerFragment.java | 4 ++++ .../android/editor/gutenberg/GutenbergEditorFragment.java | 4 ++++ .../android/editor/gutenberg/StorySaveMediaListener.java | 1 + libs/gutenberg-mobile | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 4bf0e1b587df..c93e8d6ba4cf 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -237,4 +237,8 @@ public void mediaFileSaveFailed(final String mediaId) { public void mediaFileSaveSucceeded(final String mediaId, final String mediaUrl) { mWPAndroidGlueCode.mediaFileSaveSucceeded(mediaId, mediaUrl); } + + public void onStorySaveResult(final String storyFirstMediaId, final boolean success) { + mWPAndroidGlueCode.storySaveResult(storyFirstMediaId, success); + } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index f6834f4e8415..0cdb2daeeaea 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -1027,4 +1027,8 @@ public void onEditorThemeUpdated(Bundle editorTheme) { mFailedMediaIds.add(localId); mUploadingMediaProgressMax.remove(localId); } + + @Override public void onStorySaveResult(String storyFirstMediaId, boolean success) { + getGutenbergContainerFragment().onStorySaveResult(storyFirstMediaId, success); + } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java index 0872141119bd..bd8fe7f362bc 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java @@ -5,4 +5,5 @@ public interface StorySaveMediaListener { void onMediaSaveSucceeded(String localId, String mediaUrl); void onMediaSaveProgress(String localId, float progress); void onMediaSaveFailed(String localId); + void onStorySaveResult(String storyFirstMediaId, boolean success); } diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 7e0dc26f505b..888d6c8dd0b3 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 7e0dc26f505ba096cf8f3da27b3f806f18fb8827 +Subproject commit 888d6c8dd0b387c5058a00e109d02973dfd9eeb1 From edbc1e7bf32b430ce3202c787234501f0e2af826 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 28 Sep 2020 15:53:27 -0300 Subject: [PATCH 049/343] udpated commit hash for gutenberg mobile with stories block update --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 888d6c8dd0b3..4cae822a707c 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 888d6c8dd0b387c5058a00e109d02973dfd9eeb1 +Subproject commit 4cae822a707c65bbcda7b5b517ca2a035e90dca0 From 83a977ed91c5adaa4956c5f2b024e40ddb305028 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 28 Sep 2020 19:55:01 -0300 Subject: [PATCH 050/343] added StoryFrameMediaModelCreatedEvent event to signal Gutenberg of the new MediaModel id for a given Story frame, and implemented the new bridge method onMediaModelCreated --- .../android/ui/posts/EditPostActivity.java | 13 ++++++++++++ .../media/StoryMediaSaveUploadBridge.kt | 20 ++++++++++++++++++- .../gutenberg/GutenbergContainerFragment.java | 4 ++++ .../gutenberg/GutenbergEditorFragment.java | 4 ++++ .../gutenberg/StorySaveMediaListener.java | 1 + libs/gutenberg-mobile | 2 +- 6 files changed, 42 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 9f3578ec9ec1..3b57b8da8291 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -172,6 +172,7 @@ import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.StoryRepositoryWrapper; +import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; @@ -3152,6 +3153,18 @@ public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { } } + @SuppressWarnings("unused") + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStoryFrameMediaModelCreated(StoryFrameMediaModelCreatedEvent event) { + if (isFinishing()) { + return; + } + + if (mStorySaveMediaListener != null) { + mStorySaveMediaListener.onMediaModelCreatedForFile(event.getOldId(), event.getNewId(), event.getOldUrl()); + } + } + @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onStoryFrameSaveFailed(FrameSaveFailed event) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 38bebbd89391..ea539f176ae0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.WordPress @@ -136,8 +137,10 @@ class StoryMediaSaveUploadBridge @Inject constructor( for (frame in story.frames) { // if the old URI in frame.composedFrameFile exists as a key in the passed map, then update that // value with the new (probably optimized) URL and also keep track of the new id. - val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) + val oldUri = Uri.fromFile(frame.composedFrameFile) + val mediaModel = oldUriToMediaFiles.get(oldUri) mediaModel?.let { + val oldTemporaryId = frame.id ?: "" frame.id = it.id.toString() StoriesPrefs.saveSlideWithLocalId( appContext, @@ -147,6 +150,19 @@ class StoryMediaSaveUploadBridge @Inject constructor( LocalMediaId(it.id.toLong()), frame ) + + // for editMode, we'll need to tell the Gutenberg Editor to replace their mediaFiles + // ids with the new MediaModel local ids so, broadcasting the event. + if (isEditMode) { + // finally send the event that this frameId has changed + EventBus.getDefault().post( + StoryFrameMediaModelCreatedEvent( + oldTemporaryId, + it.id.toString(), + oldUri.toString() + ) + ) + } } } } @@ -217,4 +233,6 @@ class StoryMediaSaveUploadBridge @Inject constructor( return (event.isSuccess() && event.frameSaveResult.size == storyRepositoryWrapper.getStoryAtIndex(event.storyIndex).frames.size) } + + data class StoryFrameMediaModelCreatedEvent (val oldId: String, val newId: String, val oldUrl: String) } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index c93e8d6ba4cf..ead028c13fe2 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -241,4 +241,8 @@ public void mediaFileSaveSucceeded(final String mediaId, final String mediaUrl) public void onStorySaveResult(final String storyFirstMediaId, final boolean success) { mWPAndroidGlueCode.storySaveResult(storyFirstMediaId, success); } + + public void onMediaModelCreatedForFile(String oldId, String newId, String oldUrl) { + mWPAndroidGlueCode.mediaModelCreatedForFile(oldId, newId, oldUrl); + } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 0cdb2daeeaea..8e623b23da48 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -1031,4 +1031,8 @@ public void onEditorThemeUpdated(Bundle editorTheme) { @Override public void onStorySaveResult(String storyFirstMediaId, boolean success) { getGutenbergContainerFragment().onStorySaveResult(storyFirstMediaId, success); } + + @Override public void onMediaModelCreatedForFile(String oldId, String newId, String oldUrl) { + getGutenbergContainerFragment().onMediaModelCreatedForFile(oldId, newId, oldUrl); + } } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java index bd8fe7f362bc..40eec3651fe7 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java @@ -6,4 +6,5 @@ public interface StorySaveMediaListener { void onMediaSaveProgress(String localId, float progress); void onMediaSaveFailed(String localId); void onStorySaveResult(String storyFirstMediaId, boolean success); + void onMediaModelCreatedForFile(String oldId, String newId, String oldUrl); } diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 4cae822a707c..85bf8acadf83 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 4cae822a707c65bbcda7b5b517ca2a035e90dca0 +Subproject commit 85bf8acadf8343fde0e1e79b922ab2df5cab5e65 From a2f01118ec41ac53c0aa01854b8c148725f83683 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 28 Sep 2020 21:20:00 -0300 Subject: [PATCH 051/343] updated gutenberg hash, fixed media upload succesful mediaID and remote URl updates in Story block --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 85bf8acadf83..a7bc72c32822 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 85bf8acadf8343fde0e1e79b922ab2df5cab5e65 +Subproject commit a7bc72c32822f852f7030600d7f9388b7d7de01f From e0a108dcb4aa96d29990667e326aaa6fe94b9d02 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 07:34:27 -0300 Subject: [PATCH 052/343] making sure to pass the Post's localId so it's loaded back in the bridge when editing a Story block --- .../main/java/org/wordpress/android/ui/ActivityLauncher.java | 4 ++++ .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index bfeca6f88b35..73c730557ca5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -18,6 +18,7 @@ import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.analytics.AnalyticsTracker.Stat; import org.wordpress.android.datasets.ReaderPostTable; +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId; import org.wordpress.android.fluxc.model.PostImmutableModel; import org.wordpress.android.fluxc.model.PostModel; import org.wordpress.android.fluxc.model.SiteModel; @@ -106,6 +107,7 @@ import static org.wordpress.android.ui.pages.PagesActivityKt.EXTRA_PAGE_REMOTE_ID_KEY; import static org.wordpress.android.ui.stories.StoryComposerActivity.KEY_ALL_UNFLATTENED_LOADED_SLIDES; import static org.wordpress.android.ui.stories.StoryComposerActivity.KEY_LAUNCHED_FROM_GUTENBERG; +import static org.wordpress.android.ui.stories.StoryComposerActivity.KEY_POST_LOCAL_ID; import static org.wordpress.android.viewmodel.activitylog.ActivityLogDetailViewModelKt.ACTIVITY_LOG_ID_KEY; public class ActivityLauncher { @@ -743,6 +745,7 @@ public static void editStoryWithMediaIdsForResult( public static void editStoryForResult( Activity activity, SiteModel site, + LocalId localPostId, int storyIndex, boolean allStorySlidesAreEditable, boolean launchedFromGutenberg, @@ -754,6 +757,7 @@ public static void editStoryForResult( Intent intent = new Intent(activity, StoryComposerActivity.class); intent.putExtra(WordPress.SITE, site); + intent.putExtra(KEY_POST_LOCAL_ID, localPostId.getValue()); intent.putExtra(KEY_STORY_INDEX, storyIndex); intent.putExtra(KEY_LAUNCHED_FROM_GUTENBERG, launchedFromGutenberg); intent.putExtra(KEY_ALL_UNFLATTENED_LOADED_SLIDES, allStorySlidesAreEditable); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 3b57b8da8291..68de0ad0ef5b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -40,7 +40,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; -import com.wordpress.stories.compose.frame.StorySaveEvents; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed; import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress; @@ -91,6 +90,7 @@ import org.wordpress.android.fluxc.model.CauseOfOnPostChanged.RemoteAutoSavePost; import org.wordpress.android.fluxc.model.EditorTheme; import org.wordpress.android.fluxc.model.EditorThemeSupport; +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId; import org.wordpress.android.fluxc.model.MediaModel; import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState; import org.wordpress.android.fluxc.model.PostImmutableModel; @@ -3073,6 +3073,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie ActivityLauncher.editStoryForResult( this, mSite, + new LocalId(mEditPostRepository.getId()), storyIndex, allStorySlidesAreEditable, true, From 12b25635b3a39618cbb6f64e5c67ac3c19ef6461 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 07:54:19 -0300 Subject: [PATCH 053/343] avoid IOBException by making sure the list is non-emtpy --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index fbd5da32f4f9..cb3bdf482b0f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -132,33 +132,36 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { storyBlockData?.let { storyBlockDataNonNull -> val localMediaId = mediaFile.id.toString() // now replace matching localMediaId with remoteMediaId in the mediaFileObjects, obtain the URLs and replace - storyBlockDataNonNull.mediaFiles?.filter { it.id == localMediaId }?.get(0)?.apply { - id = mediaFile.mediaId - link = mediaFile.fileURL - url = mediaFile.fileURL - - // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. - val localIdKey = mediaFile.id.toLong() - val remoteIdKey = mediaFile.mediaId.toLong() - val localSiteId = postModel.localSiteId.toLong() - StoriesPrefs.getSlideWithLocalId( - WordPress.getContext(), - localSiteId, - LocalMediaId(localIdKey) - )?.let { - it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID - StoriesPrefs.saveSlideWithRemoteId( - WordPress.getContext(), - localSiteId, - RemoteMediaId(remoteIdKey), // use the new mediaId as key - it - ) - // now delete the old entry - StoriesPrefs.deleteSlideWithLocalId( + val mediaFiles = storyBlockDataNonNull.mediaFiles.filter { it.id == localMediaId } + if (mediaFiles.isNotEmpty()) { + mediaFiles[0].apply { + id = mediaFile.mediaId + link = mediaFile.fileURL + url = mediaFile.fileURL + + // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. + val localIdKey = mediaFile.id.toLong() + val remoteIdKey = mediaFile.mediaId.toLong() + val localSiteId = postModel.localSiteId.toLong() + StoriesPrefs.getSlideWithLocalId( WordPress.getContext(), localSiteId, LocalMediaId(localIdKey) - ) + )?.let { + it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID + StoriesPrefs.saveSlideWithRemoteId( + WordPress.getContext(), + localSiteId, + RemoteMediaId(remoteIdKey), // use the new mediaId as key + it + ) + // now delete the old entry + StoriesPrefs.deleteSlideWithLocalId( + WordPress.getContext(), + localSiteId, + LocalMediaId(localIdKey) + ) + } } } processedContent = content.replace(mediaFilesJsonString, gson.toJson(storyBlockDataNonNull)) From 77d27cffab204c6a6e0175114acd6bdf13cceb21 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 07:55:06 -0300 Subject: [PATCH 054/343] only save a post in the bridge if we are not editing it - if we are editing, let the Editor (Gutenberg) handle this --- .../media/StoryMediaSaveUploadBridge.kt | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index ea539f176ae0..327ad136c430 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -176,22 +176,27 @@ class StoryMediaSaveUploadBridge @Inject constructor( editorMediaListener = localEditorMediaListener, doUploadAfterAdding = true ) - postUtils.preparePostForPublish(requireNotNull(editPostRepository.getEditablePost()), site) - savePostToDbUseCase.savePostToDb(editPostRepository, site) - - if (networkUtils.isNetworkAvailable()) { - postUtils.trackSavePostAnalytics( - editPostRepository.getPost(), - site - ) - uploadService.uploadPost(appContext, editPostRepository.id, true) - // SAVED_ONLINE - storiesTrackerHelper.trackStoryPostSavedEvent(uriList.size, site, false) - } else { - // SAVED_LOCALLY - storiesTrackerHelper.trackStoryPostSavedEvent(uriList.size, site, true) - // no op, when network is available the offline mode in WPAndroid will gather the queued Post - // and try to upload. + + // only save this post if we're not currently in edit mode + // In edit mode, we'll let the Gutenberg editor save the edited block if / when needed. + if (!isEditMode) { + postUtils.preparePostForPublish(requireNotNull(editPostRepository.getEditablePost()), site) + savePostToDbUseCase.savePostToDb(editPostRepository, site) + + if (networkUtils.isNetworkAvailable()) { + postUtils.trackSavePostAnalytics( + editPostRepository.getPost(), + site + ) + uploadService.uploadPost(appContext, editPostRepository.id, true) + // SAVED_ONLINE + storiesTrackerHelper.trackStoryPostSavedEvent(uriList.size, site, false) + } else { + // SAVED_LOCALLY + storiesTrackerHelper.trackStoryPostSavedEvent(uriList.size, site, true) + // no op, when network is available the offline mode in WPAndroid will gather the queued Post + // and try to upload. + } } } } From 6114c61b10d6f303b469474a9131d8967a283d74 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 08:06:34 -0300 Subject: [PATCH 055/343] fixed bug, was using the 'alt' field to look for a temporary id, should have referenced 'id' instead --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index cb3bdf482b0f..148ca7ee8a28 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -89,7 +89,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { private fun hasTemporaryIdsInStoryData(storyBlockData: StoryBlockData): Boolean { val temporaryIds = storyBlockData.mediaFiles.filter { - it.alt.startsWith(TEMPORARY_ID_PREFIX) + it.id.startsWith(TEMPORARY_ID_PREFIX) } return temporaryIds.size > 0 } From 0b016d6df3a11d31abdd6f1150a68c592b53b180 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 09:42:03 -0300 Subject: [PATCH 056/343] fixed comment --- .../android/ui/stories/media/StoryMediaSaveUploadBridge.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 327ad136c430..93d34751c375 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -152,7 +152,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( ) // for editMode, we'll need to tell the Gutenberg Editor to replace their mediaFiles - // ids with the new MediaModel local ids so, broadcasting the event. + // ids with the new MediaModel local ids are created so, broadcasting the event. if (isEditMode) { // finally send the event that this frameId has changed EventBus.getDefault().post( From 23fcff53e56fae259e56200eb696bf7fa3b6d1dc Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 10:28:08 -0300 Subject: [PATCH 057/343] refactored and added method StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide() to be used both by the bridge and EditPostACtivity when a composed frame media has just been uploaded to the remote, so we keep track of edtiable slides in StoriesPrefs --- .../android/ui/posts/EditPostActivity.java | 18 ++++++++- .../stories/SaveStoryGutenbergBlockUseCase.kt | 37 +++++-------------- .../android/ui/stories/prefs/StoriesPrefs.kt | 24 ++++++++++++ .../ui/uploads/MediaUploadReadyProcessor.java | 2 +- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 68de0ad0ef5b..945a9d66fdc5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -173,6 +173,7 @@ import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.StoryRepositoryWrapper; import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent; +import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; @@ -1589,9 +1590,22 @@ private void onUploadSuccess(MediaModel media) { .getId()) { setFeaturedImageId(media.getMediaId()); } - } - + // if this is a Story media item, then make sure to keep up with the StoriesPrefs serialized slides + // this looks for the slide saved with the local id key (media.getId()), and re-converts it to + // mediaId. + // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload within + // a story block: we will only replace items for which a local-keyed frame has been created before, + // which can only happen when using the Story Creator. + if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getContent())) { + StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + this, + media.getId(), + media.getMediaId(), + mSite.getId() + ); + } + } private void onUploadProgress(MediaModel media, float progress) { String localMediaId = String.valueOf(media.getId()); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 148ca7ee8a28..9e5a69080428 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -1,12 +1,10 @@ package org.wordpress.android.ui.stories +import android.content.Context import com.google.gson.Gson -import org.wordpress.android.WordPress import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.prefs.StoriesPrefs -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -120,7 +118,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { postModel.setContent(content) } - fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(postModel: PostModel, mediaFile: MediaFile) { + fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(context: Context, postModel: PostModel, mediaFile: MediaFile) { val gson = Gson() findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( postModel, @@ -139,29 +137,14 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { link = mediaFile.fileURL url = mediaFile.fileURL - // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. - val localIdKey = mediaFile.id.toLong() - val remoteIdKey = mediaFile.mediaId.toLong() - val localSiteId = postModel.localSiteId.toLong() - StoriesPrefs.getSlideWithLocalId( - WordPress.getContext(), - localSiteId, - LocalMediaId(localIdKey) - )?.let { - it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID - StoriesPrefs.saveSlideWithRemoteId( - WordPress.getContext(), - localSiteId, - RemoteMediaId(remoteIdKey), // use the new mediaId as key - it - ) - // now delete the old entry - StoriesPrefs.deleteSlideWithLocalId( - WordPress.getContext(), - localSiteId, - LocalMediaId(localIdKey) - ) - } + // look for the slide saved with the local id key (mediaFile.id), and re-convert to + // mediaId. + StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + context, + mediaFile.id.toLong(), + mediaFile.mediaId.toLong(), + postModel.localSiteId.toLong() + ) } } processedContent = content.replace(mediaFilesJsonString, gson.toJson(storyBlockDataNonNull)) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index b2c88bb789c2..57793a39c7fa 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -123,6 +123,30 @@ object StoriesPrefs { editor.apply() } + @JvmStatic + fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide(context: Context, localIdKey: Long, remoteIdKey: Long, localSiteId: Long) { + // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. + getSlideWithLocalId( + context, + localSiteId, + LocalMediaId(localIdKey) + )?.let { + it.id = remoteIdKey.toString() // update the StoryFrameItem id to hold the same value as the remote mediaID + saveSlideWithRemoteId( + context, + localSiteId, + RemoteMediaId(remoteIdKey), // use the new mediaId as key + it + ) + // now delete the old entry + deleteSlideWithLocalId( + context, + localSiteId, + LocalMediaId(localIdKey) + ) + } + } + data class RemoteMediaId(val mediaId: Long) data class LocalMediaId(val id: Long) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java index 8ff1bbdc09d1..f047260336b9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java @@ -23,7 +23,7 @@ public PostModel replaceMediaFileWithUrlInPost(@Nullable PostModel post, String if (PostUtils.contentContainsWPStoryGutenbergBlocks(post.getContent())) { SaveStoryGutenbergBlockUseCase saveStoryGutenbergBlockUseCase = new SaveStoryGutenbergBlockUseCase(); saveStoryGutenbergBlockUseCase - .replaceLocalMediaIdsWithRemoteMediaIdsInPost(post, mediaFile); + .replaceLocalMediaIdsWithRemoteMediaIdsInPost(WordPress.getContext(), post, mediaFile); } else if (showGutenbergEditor && PostUtils.contentContainsGutenbergBlocks(post.getContent())) { post.setContent( PostUtils.replaceMediaFileWithUrlInGutenbergPost(post.getContent(), localMediaId, mediaFile, From a8bfdb3c7ef2b70c3a69758a64c732120aceb363 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 10:29:43 -0300 Subject: [PATCH 058/343] enclosed one check for media !=null needed by all other checks --- .../android/ui/posts/EditPostActivity.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 945a9d66fdc5..19d80cd3b89c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -1582,28 +1582,28 @@ private ActivityFinishState savePostOnline(boolean isFirstTimePublish) { } private void onUploadSuccess(MediaModel media) { - // TODO Should this statement check media.getLocalPostId() == mEditPostRepository.getId()? - if (media != null && !media.getMarkedLocallyAsFeatured() && mEditorMediaUploadListener != null) { - mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), - FluxCUtils.mediaFileFromMediaModel(media)); - } else if (media != null && media.getMarkedLocallyAsFeatured() && media.getLocalPostId() == mEditPostRepository - .getId()) { - setFeaturedImageId(media.getMediaId()); - } - - // if this is a Story media item, then make sure to keep up with the StoriesPrefs serialized slides - // this looks for the slide saved with the local id key (media.getId()), and re-converts it to - // mediaId. - // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload within - // a story block: we will only replace items for which a local-keyed frame has been created before, - // which can only happen when using the Story Creator. - if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getContent())) { - StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( - this, - media.getId(), - media.getMediaId(), - mSite.getId() - ); + if (media != null) { + // TODO Should this statement check media.getLocalPostId() == mEditPostRepository.getId()? + if (!media.getMarkedLocallyAsFeatured() && mEditorMediaUploadListener != null) { + mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), + FluxCUtils.mediaFileFromMediaModel(media)); + } else if (media.getMarkedLocallyAsFeatured() && media.getLocalPostId() == mEditPostRepository + .getId()) { + setFeaturedImageId(media.getMediaId()); + } else if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getContent())) { + // if this is a Story media item, then make sure to keep up with the StoriesPrefs serialized slides + // this looks for the slide saved with the local id key (media.getId()), and re-converts it to + // mediaId. + // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload within + // a story block: we will only replace items for which a local-keyed frame has been created before, + // which can only happen when using the Story Creator. + StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + this, + media.getId(), + media.getMediaId(), + mSite.getId() + ); + } } } From b0ccacd349fd54a6f998b61db854bdd27c03761d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 10:50:27 -0300 Subject: [PATCH 059/343] moved else if, wasn't ever going to run otherwise --- .../android/ui/posts/EditPostActivity.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 19d80cd3b89c..984e091b9f0b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -1587,22 +1587,23 @@ private void onUploadSuccess(MediaModel media) { if (!media.getMarkedLocallyAsFeatured() && mEditorMediaUploadListener != null) { mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), FluxCUtils.mediaFileFromMediaModel(media)); + if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getContent())) { + // if this is a Story media item, then make sure to keep up with the StoriesPrefs serialized slides + // this looks for the slide saved with the local id key (media.getId()), and re-converts it to + // mediaId. + // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload + // within a story block in this post: we will only replace items for which a local-keyed frame has + // been created before, which can only happen when using the Story Creator. + StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + this, + media.getId(), + media.getMediaId(), + mSite.getId() + ); + } } else if (media.getMarkedLocallyAsFeatured() && media.getLocalPostId() == mEditPostRepository .getId()) { setFeaturedImageId(media.getMediaId()); - } else if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getContent())) { - // if this is a Story media item, then make sure to keep up with the StoriesPrefs serialized slides - // this looks for the slide saved with the local id key (media.getId()), and re-converts it to - // mediaId. - // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload within - // a story block: we will only replace items for which a local-keyed frame has been created before, - // which can only happen when using the Story Creator. - StoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( - this, - media.getId(), - media.getMediaId(), - mSite.getId() - ); } } } From ce599122deca21ac2ca53c6652ede0b46bcb26d5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 11:07:16 -0300 Subject: [PATCH 060/343] added call to updateAndSavePostAsync after adding a remote media to the Story --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 984e091b9f0b..cde12d4297c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -1588,6 +1588,8 @@ private void onUploadSuccess(MediaModel media) { mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), FluxCUtils.mediaFileFromMediaModel(media)); if (PostUtils.contentContainsWPStoryGutenbergBlocks(mEditPostRepository.getContent())) { + // make sure to sync the local post object with the UI and save + updateAndSavePostAsync(); // if this is a Story media item, then make sure to keep up with the StoriesPrefs serialized slides // this looks for the slide saved with the local id key (media.getId()), and re-converts it to // mediaId. From 9749799bf068ecea154a19e2e09b7d45d8bb7b84 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 11:10:47 -0300 Subject: [PATCH 061/343] fixed line length --- .../org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 57793a39c7fa..c9890bb40e2f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -124,7 +124,12 @@ object StoriesPrefs { } @JvmStatic - fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide(context: Context, localIdKey: Long, remoteIdKey: Long, localSiteId: Long) { + fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + context: Context, + localIdKey: Long, + remoteIdKey: Long, + localSiteId: Long + ) { // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. getSlideWithLocalId( context, From fcfb771bea72df6847b26cb6928e0b3caa33f592 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 11:27:08 -0300 Subject: [PATCH 062/343] updated stories lib hash to keep up with camera preview launch fix --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 823ab8aa66f0..9affc818faa7 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 823ab8aa66f065f32fa69f66260569bce6ae7cee +Subproject commit 9affc818faa77cb9bbe93a41e32ca811c336710e From 3b6095c464a46ab3d9660df438396c4a0173220f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 12:19:33 -0300 Subject: [PATCH 063/343] updated gutenberg-mobile hash, commented lines --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index a7bc72c32822..21b823b4f333 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit a7bc72c32822f852f7030600d7f9388b7d7de01f +Subproject commit 21b823b4f33313fc5b134e7d37d5ef0a4d4bd943 From 9bc74f334bfe74bb544a19ee926de38537a1aee9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 29 Sep 2020 19:23:26 -0300 Subject: [PATCH 064/343] using StoryRepositoryWrapper --- .../wordpress/android/ui/stories/StoryComposerActivity.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 6efdc7a8f549..05237799bec6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -22,9 +22,7 @@ import com.wordpress.stories.compose.PrepublishingEventProvider import com.wordpress.stories.compose.SnackbarProvider import com.wordpress.stories.compose.StoryDiscardListener import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult -import com.wordpress.stories.compose.story.StoryFrameItemType.VIDEO import com.wordpress.stories.compose.story.StoryIndex -import com.wordpress.stories.compose.story.StoryRepository import com.wordpress.stories.compose.story.StoryRepository.DEFAULT_NONE_SELECTED import com.wordpress.stories.util.KEY_STORY_EDIT_MODE import com.wordpress.stories.util.KEY_STORY_INDEX @@ -107,6 +105,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), @Inject lateinit var saveStoryGutenbergBlockUseCase: SaveStoryGutenbergBlockUseCase @Inject lateinit var mediaStore: MediaStore @Inject lateinit var fluxCUtilsWrapper: FluxCUtilsWrapper + @Inject lateinit var storyRepositoryWrapper: StoryRepositoryWrapper private lateinit var viewModel: StoryComposerViewModel @@ -478,7 +477,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), storyIndex: StoryIndex ): ArrayList { val storyMediaFileDataList = ArrayList() // holds media files - val story = StoryRepository.getStoryAtIndex(storyIndex) + val story = storyRepositoryWrapper.getStoryAtIndex(storyIndex) for ((frameIndex, frame) in story.frames.withIndex()) { frame.id?.let { val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) From 7fedb76db93c80ff0a3050887b4b29aa1b9fd05b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 08:28:20 -0300 Subject: [PATCH 065/343] updated stories cmomit hash with bugfixes --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 9affc818faa7..05cc667c0a14 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 9affc818faa77cb9bbe93a41e32ca811c336710e +Subproject commit 05cc667c0a14dac23ede3955eea4e5bd8a4c943e From d9978b97fbe01afa558ecef8d3c38dc297cea618 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 08:33:15 -0300 Subject: [PATCH 066/343] updated stories lib hash - fixed lint warnings --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 05cc667c0a14..dab279ee26cc 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 05cc667c0a14dac23ede3955eea4e5bd8a4c943e +Subproject commit dab279ee26ccd966f88f575b6cd51c19cfbc9ad2 From 2ec71f97268bb54cffd9232381fc778e94b4c7ff Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 10:58:15 -0300 Subject: [PATCH 067/343] removed old comment --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 9e5a69080428..8a7d01d6e805 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -50,8 +50,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { fun buildMediaFileDataWithTemporaryId(mediaFile: MediaFile, temporaryId: String): StoryMediaFileData { return StoryMediaFileData( - alt = "", // notation abuse: we're repurposing this field to - // hold a temporaryId + alt = "", id = TEMPORARY_ID_PREFIX + temporaryId, // mediaFile.id, link = StringUtils.notNullStr(mediaFile.fileURL), type = if (mediaFile.isVideo) "video" else "image", From e9dc8308916e7ebaa113dec55b0f7601c3ce2dc7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 12:01:39 -0300 Subject: [PATCH 068/343] add support for editing an existing uploaded Story and adding a frame to it --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 12 ++++++ .../ui/stories/StoryComposerActivity.kt | 42 +++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 8a7d01d6e805..b68d2f9848b3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -60,6 +60,18 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { ) } + fun buildMediaFileDataWithTemporaryIdNoMediaFile(temporaryId: String, url: String, isVideo: Boolean): StoryMediaFileData { + return StoryMediaFileData( + alt = "", + id = TEMPORARY_ID_PREFIX + temporaryId, // mediaFile.id, + link = url, + type = if (isVideo) "video" else "image", + mime = "", + caption = "", + url = url + ) + } + fun cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost(editPostRepository: EditPostRepository) { editPostRepository.update { postModel: PostModel -> val gson = Gson() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 05237799bec6..9e3fb2ace80a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -22,6 +22,9 @@ import com.wordpress.stories.compose.PrepublishingEventProvider import com.wordpress.stories.compose.SnackbarProvider import com.wordpress.stories.compose.StoryDiscardListener import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult +import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource +import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource +import com.wordpress.stories.compose.story.StoryFrameItemType.VIDEO import com.wordpress.stories.compose.story.StoryIndex import com.wordpress.stories.compose.story.StoryRepository.DEFAULT_NONE_SELECTED import com.wordpress.stories.util.KEY_STORY_EDIT_MODE @@ -66,7 +69,6 @@ import org.wordpress.android.ui.utils.AuthenticationUtils import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.FluxCUtilsWrapper import org.wordpress.android.util.ListUtils -import org.wordpress.android.util.StringUtils import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper @@ -479,18 +481,42 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val storyMediaFileDataList = ArrayList() // holds media files val story = storyRepositoryWrapper.getStoryAtIndex(storyIndex) for ((frameIndex, frame) in story.frames.withIndex()) { - frame.id?.let { - val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) - val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) - mediaFile?.let { + when (frame.id) { + // if the frame.id is null, this is a new frame that has been added to an edited Story + // so, we don't have much information yet. We do have the background source (not the flattened + // image yet) so, let's use that for now, and assign the temporaryID we'll use to send + // save progress events to Gutenberg. + null -> { val storyMediaFileData = - saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( - mediaFile = it, - temporaryId = "$storyIndex-$frameIndex" + saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryIdNoMediaFile( + temporaryId = "$storyIndex-$frameIndex", + url = if (frame.source is FileBackgroundSource) { + (frame.source as FileBackgroundSource).file.toString() + } else { + (frame.source as UriBackgroundSource).contentUri.toString() + }, + isVideo = (frame.frameItemType is VIDEO) ) frame.id = storyMediaFileData.id storyMediaFileDataList.add(storyMediaFileData) } + // if the frame.id is populated, this should be an actual MediaModel mediaId so, + // let's use that to obtain the mediaFile and then replace it with the temporary frame.id + else -> { + frame.id?.let { + val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) + val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) + mediaFile?.let { + val storyMediaFileData = + saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( + mediaFile = it, + temporaryId = "$storyIndex-$frameIndex" + ) + frame.id = storyMediaFileData.id + storyMediaFileDataList.add(storyMediaFileData) + } + } + } } } return storyMediaFileDataList From 4e005ae2d06f106310025a55521120c2bf8bcfa7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 13:04:41 -0300 Subject: [PATCH 069/343] implemented git onFrameRemove() listener method and cleaning StoriesPrefs when a frame is discarded in the composer --- .../ui/stories/StoryComposerActivity.kt | 31 +++++++++++++++++++ libs/stories-android | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 9e3fb2ace80a..b2272c099396 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -65,6 +65,9 @@ import org.wordpress.android.ui.posts.prepublishing.PrepublishingBottomSheetList import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.StoryMediaFileData import org.wordpress.android.ui.stories.media.StoryEditorMedia import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState +import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import org.wordpress.android.ui.utils.AuthenticationUtils import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.FluxCUtilsWrapper @@ -112,6 +115,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), private lateinit var viewModel: StoryComposerViewModel private var addingMediaToEditorProgressDialog: ProgressDialog? = null + private val frameIdsToRemove = ArrayList() override fun getSite() = site override fun getEditPostRepository() = editPostRepository @@ -431,6 +435,19 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } } + override fun onFrameRemove(storyIndex: StoryIndex, storyFrameIndex: Int) { + // keep record of the frames users deleted. + // But we'll only actually do cleanup once they tap on the DONE/SAVE button, because they could + // still bail out of the StoryComposer by tapping back or the cross and then admiting they want to lose + // the changes they made (this means, they'd want to keep the stories). + val story = storyRepositoryWrapper.getStoryAtIndex(storyIndex) + if (storyFrameIndex < story.frames.size) { + story.frames[storyFrameIndex].id?.let { + frameIdsToRemove.add(it) + } + } + } + private fun openPrepublishingBottomSheet() { val fragment = supportFragmentManager.findFragmentByTag(PrepublishingBottomSheetFragment.TAG) if (fragment == null) { @@ -445,6 +462,20 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), override fun onStorySaveButtonPressed() { if (intent.getBooleanExtra(KEY_LAUNCHED_FROM_GUTENBERG, false)) { + // first of all, remove any StoriesPref for removed slides + site?.let { + val siteLocalId = it.id.toLong() + for (frameId in frameIdsToRemove) { + if (StoriesPrefs.checkSlideIdExists(this, siteLocalId, RemoteMediaId(frameId.toLong()))) { + StoriesPrefs.deleteSlideWithRemoteId(this, siteLocalId, RemoteMediaId(frameId.toLong())) + } else { + // shouldn't happen but just in case the story frame has just been created but not yet uploaded + // let's delete the local slide pref. + StoriesPrefs.deleteSlideWithLocalId(this, siteLocalId, LocalMediaId(frameId.toLong())) + } + } + } + val savedContentIntent = Intent() val blockId = intent.extras.getString(ARG_STORY_BLOCK_ID) savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) diff --git a/libs/stories-android b/libs/stories-android index dab279ee26cc..19ac403d23dd 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit dab279ee26ccd966f88f575b6cd51c19cfbc9ad2 +Subproject commit 19ac403d23ddfc3211bcd78e2c3d20c3f21f2275 From 144e3ced00cfa850bd0a940c50dec9b1b8807121 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 15:33:44 -0300 Subject: [PATCH 070/343] updated commit hash for stories library --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 19ac403d23dd..c0512c9b6b02 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 19ac403d23ddfc3211bcd78e2c3d20c3f21f2275 +Subproject commit c0512c9b6b0246a7619bb906d026118246a4f26a From 64cc559d2591ea6273b163221e12f4cdcb8448d7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 16:14:12 -0300 Subject: [PATCH 071/343] safeguard against a Story being corrupt (for example a crash when saving), to indicate this cannot be edited --- .../android/ui/posts/EditPostActivity.java | 14 ++++++++++++++ .../usecase/LoadStoryFromStoriesPrefsUseCase.kt | 15 ++++++++++++++- WordPress/src/main/res/values/strings.xml | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index cde12d4297c7..d80ae21eab1c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3064,6 +3064,20 @@ public void onTrackableEvent(TrackableEvent event, Map propertie mMediaStore, this ); + + if (loadStoryFromStoriesPrefsUseCase.anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles)) { + // unfortunately the medaiIds seem corrupt so, show a dialog and bail + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); + builder.setMessage(getString(R.string.dialog_edit_story_corrupt_message)); + builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { + dialog.dismiss(); + }); + AlertDialog dialog = builder.create(); + dialog.show(); + return; + } + ArrayList mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles); boolean allStorySlidesAreEditable = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(mSite, mediaIds); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index a4521e1cd8bc..e2c328bf9826 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -9,7 +9,6 @@ import dagger.Reusable import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore -import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import org.wordpress.android.ui.stories.prefs.StoriesPrefs.getSlideWithRemoteId @@ -38,6 +37,20 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return mediaIds } + fun anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles: ArrayList): Boolean { + for (mediaFile in mediaFiles) { + try { + (mediaFile as HashMap)["id"] + .toString() + .toDouble() // this conversion is needed to strip off decimals that can come from RN + .toLong() + } catch (exception: NumberFormatException) { + return true + } + } + return false + } + fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { if (!isValidSlide(context, site.getId().toLong(), RemoteMediaId(mediaId.toLong()))) { diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index a4dc6e748ade..2c14a38040bc 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2876,6 +2876,7 @@ This story was edited on a different device and the ability to edit certain objects may be limited. Can\'t edit Story This story was created on a different device and can\'t be edited at this moment. + There was a problem saving this story and can\'t be edited at this moment. Capture Flip camera Flash From de7a2ba39e9c52e052c7aa0629bdc5215c0dc493 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 16:33:40 -0300 Subject: [PATCH 072/343] updated gutenberg mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 21b823b4f333..7f9313f1704e 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 21b823b4f33313fc5b134e7d37d5ef0a4d4bd943 +Subproject commit 7f9313f1704e9d8d69994df8a33bcb4ae7909e38 From 7c04d20ec7c09aa6e01944b86a54b551f49ab61d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 16:40:09 -0300 Subject: [PATCH 073/343] fixed lint warnings --- .../ui/stories/SaveStoryGutenbergBlockUseCase.kt | 14 +++++++++----- .../android/ui/stories/StoryRepositoryWrapper.kt | 7 ++++--- .../ui/stories/media/StoryMediaSaveUploadBridge.kt | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index b68d2f9848b3..018b3b52e300 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -60,7 +60,11 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { ) } - fun buildMediaFileDataWithTemporaryIdNoMediaFile(temporaryId: String, url: String, isVideo: Boolean): StoryMediaFileData { + fun buildMediaFileDataWithTemporaryIdNoMediaFile( + temporaryId: String, + url: String, + isVideo: Boolean + ): StoryMediaFileData { return StoryMediaFileData( alt = "", id = TEMPORARY_ID_PREFIX + temporaryId, // mediaFile.id, @@ -80,8 +84,8 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { object : DoWithMediaFilesListener { override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { var processedContent = content - val storyBlockData: StoryBlockData? - = gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) + val storyBlockData: StoryBlockData? = + gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) storyBlockData?.let { if (hasTemporaryIdsInStoryData(it)) { // here remove the whole mediaFiles attribute @@ -136,8 +140,8 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { object : DoWithMediaFilesListener { override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { var processedContent = content - val storyBlockData: StoryBlockData? - = gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) + val storyBlockData: StoryBlockData? = + gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) storyBlockData?.let { storyBlockDataNonNull -> val localMediaId = mediaFile.id.toString() // now replace matching localMediaId with remoteMediaId in the mediaFileObjects, obtain the URLs and replace diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt index d5846c8652b7..dcd109264b6d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt @@ -11,9 +11,10 @@ class StoryRepositoryWrapper @Inject constructor() { fun getCurrentStoryTitle() = StoryRepository.getCurrentStoryTitle() fun getCurrentStoryIndex(): StoryIndex = StoryRepository.currentStoryIndex fun loadStory(storyIndex: StoryIndex) = StoryRepository.loadStory(storyIndex) - fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = StoryRepository.addStoryFrameItemToCurrentStory(item) + fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = + StoryRepository.addStoryFrameItemToCurrentStory(item) fun getStoryAtIndex(index: StoryIndex) = StoryRepository.getStoryAtIndex(index) fun getImmutableStories() = StoryRepository.getImmutableStories() - fun getCurrentStorySaveProgress(storyIndex: StoryIndex, oneItemActualProgress: Float = 0.0F) - = StoryRepository.getCurrentStorySaveProgress(storyIndex, oneItemActualProgress) + fun getCurrentStorySaveProgress(storyIndex: StoryIndex, oneItemActualProgress: Float = 0.0F) = + StoryRepository.getCurrentStorySaveProgress(storyIndex, oneItemActualProgress) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 93d34751c375..e0b154fca9de 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -100,7 +100,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( private fun addNewMediaItemsToPostAsync(site: SiteModel, uriList: List, isEditMode: Boolean) { // this is similar to addNewMediaItemsToEditorAsync in EditorMedia launch { - val localEditorMediaListener = object: EditorMediaListener { + val localEditorMediaListener = object : EditorMediaListener { override fun appendMediaFiles(mediaFiles: Map) { if (!isEditMode) { saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( @@ -239,5 +239,5 @@ class StoryMediaSaveUploadBridge @Inject constructor( event.frameSaveResult.size == storyRepositoryWrapper.getStoryAtIndex(event.storyIndex).frames.size) } - data class StoryFrameMediaModelCreatedEvent (val oldId: String, val newId: String, val oldUrl: String) + data class StoryFrameMediaModelCreatedEvent(val oldId: String, val newId: String, val oldUrl: String) } From 0d554b4e9243fa37a1c09f1fa8e0195f3a0f650f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 30 Sep 2020 16:42:57 -0300 Subject: [PATCH 074/343] updated mobile gutenberg commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 7f9313f1704e..430fd3c7e0b3 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 7f9313f1704e9d8d69994df8a33bcb4ae7909e38 +Subproject commit 430fd3c7e0b3284734e80b52cda7b1749807e917 From 35f2f55c82ad63fafb9791562019a687b163979a Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 00:35:05 -0300 Subject: [PATCH 075/343] updated commit hash for gutenberg mobile with fix in gutenberg mobile demo app --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 430fd3c7e0b3..fd317778b43b 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 430fd3c7e0b3284734e80b52cda7b1749807e917 +Subproject commit fd317778b43bf5229a4a033dfe76ae8e1c750b1d From 8c0a245106dc1eb39a9eebc68ad3cca1e148c24b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 10:24:10 -0300 Subject: [PATCH 076/343] updated gutenberg-mobile and jetpack story block commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index fd317778b43b..e39c4ea51d09 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit fd317778b43bf5229a4a033dfe76ae8e1c750b1d +Subproject commit e39c4ea51d09d5a4ae7d444df9a88f1b27fe63c0 From 95903d9105055fd638fd792a99d27a5af7458fb9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 16:32:39 -0300 Subject: [PATCH 077/343] updated commit hash for mobile gutenberg --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 00a0cc0b7e84..b2685445fa4f 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 00a0cc0b7e849521595d428d5161dea557aa29e9 +Subproject commit b2685445fa4fda8b03799a5a71606adb348bd12b From 8311a30867d6cbcf75a2e33d58f95c8c05b88333 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 17:24:56 -0300 Subject: [PATCH 078/343] updated gutenbeg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index b2685445fa4f..33f2addaab7f 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit b2685445fa4fda8b03799a5a71606adb348bd12b +Subproject commit 33f2addaab7f5338f791d968680cb6f1e97b4ffa From 111378b765f816aa297e0917fc3434cb8f7f667b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 17:40:57 -0300 Subject: [PATCH 079/343] added missing method declaration --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index dfa611a75fbb..bcf70b56ff64 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -414,6 +414,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } } + override fun onFrameRemove(storyIndex: StoryIndex, storyFrameIndex: Int) { + // TODO will implement later + } + private fun openPrepublishingBottomSheet() { val fragment = supportFragmentManager.findFragmentByTag(PrepublishingBottomSheetFragment.TAG) if (fragment == null) { From 34509cb4d4f2994ae4d4873a6ac4fe0cced4d994 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 18:18:11 -0300 Subject: [PATCH 080/343] added explicit null check --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index e529b6e1190c..01e6e8222883 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -474,7 +474,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } val savedContentIntent = Intent() - val blockId = intent.extras.getString(ARG_STORY_BLOCK_ID) + val blockId = intent.extras?.getString(ARG_STORY_BLOCK_ID) savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) val storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) From 85a10afb0ca709db4e193a4d08ad14930d34c5ef Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 1 Oct 2020 18:54:10 -0300 Subject: [PATCH 081/343] updated commit hash for mobile-gutenberg --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index f78b24fb30ee..b1de4df250b2 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit f78b24fb30eef7d1fc07161d46eb95d5d5fc766e +Subproject commit b1de4df250b2dc0c27f15aa94db8d1f1c0a6a65e From 0f4d87c25bde9da952d91fa9a5902a189acc0ed2 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 09:15:05 -0300 Subject: [PATCH 082/343] refactored onStoryComposerLoadRequested logic into LoadStoryFromStoriesPrefsUseCase --- .../android/ui/posts/EditPostActivity.java | 38 ++++--------------- .../ui/stories/StoryRepositoryWrapper.kt | 4 +- .../android/ui/stories/prefs/StoriesPrefs.kt | 5 --- .../LoadStoryFromStoriesPrefsUseCase.kt | 27 ++++++++++++- libs/stories-android | 2 +- 5 files changed, 36 insertions(+), 40 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 916f8aa71075..ea0096acf06c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -41,7 +41,6 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; -import com.wordpress.stories.compose.story.StoryRepository; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -163,7 +162,6 @@ import org.wordpress.android.ui.prefs.SiteSettingsInterface; import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; -import org.wordpress.android.ui.stories.StoryRepositoryWrapper; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; @@ -392,6 +390,7 @@ enum RestartEditorOptions { @Inject ModalLayoutPickerFeatureConfig mModalLayoutPickerFeatureConfig; @Inject CrashLogging mCrashLogging; @Inject MediaPickerLauncher mMediaPickerLauncher; + @Inject LoadStoryFromStoriesPrefsUseCase mLoadStoryFromStoriesPrefsUseCase; private StorePostViewModel mViewModel; @@ -3066,36 +3065,13 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - LoadStoryFromStoriesPrefsUseCase loadStoryFromStoriesPrefsUseCase = new LoadStoryFromStoriesPrefsUseCase( - new StoryRepositoryWrapper(), - mSite, - mMediaStore, - this - ); - ArrayList mediaIds = - loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles); - boolean allStorySlidesAreEditable = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(mSite, mediaIds); - boolean failedLoadingOrReCreatingStory = false; - - // now look for a Story in the StoryRepository that has all these frames and, if not found, let's - // just build the Story object ourselves to keep these files arrangement - int storyIndex = StoryRepository.findStoryContainingStoryFrameItemsByIds(mediaIds); - if (storyIndex == StoryRepository.DEFAULT_NONE_SELECTED) { - // the StoryRepository didn't have it but we have editable serialized slides so, - // create a new Story from scratch with these deserialized StoryFrameItems - ReCreateStoryResult result = - loadStoryFromStoriesPrefsUseCase.loadOrReCreateStoryFromStoriesPrefs(mediaIds); - failedLoadingOrReCreatingStory = result.getNoSlidesLoaded(); - if (allStorySlidesAreEditable) { - // double check and override if we found at least one couldn't be inflated - allStorySlidesAreEditable = result.getAllStorySlidesAreEditable(); - } - storyIndex = result.getStoryIndex(); - } - - if (!failedLoadingOrReCreatingStory) { + ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase + .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); + if (!result.getNoSlidesLoaded()) { // Story instance loaded or re-created! Load it - ActivityLauncher.editStoryForResult(this, mSite, storyIndex, allStorySlidesAreEditable, true); + ActivityLauncher.editStoryForResult( + this, mSite, result.getStoryIndex(), result.getAllStorySlidesAreEditable(), true + ); } else { // unfortunately we couldn't even load the remote media Ids indicated by the StoryBLock so we can't allow // editing at this time :( diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt index 7b89cea9bbda..570db83441ed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryRepositoryWrapper.kt @@ -12,4 +12,6 @@ class StoryRepositoryWrapper @Inject constructor() { fun getCurrentStoryIndex(): StoryIndex = StoryRepository.currentStoryIndex fun loadStory(storyIndex: StoryIndex) = StoryRepository.loadStory(storyIndex) fun addStoryFrameItemToCurrentStory(item: StoryFrameItem) = StoryRepository.addStoryFrameItemToCurrentStory(item) -} + fun findStoryContainingStoryFrameItemsByIds(ids: ArrayList) = + StoryRepository.findStoryContainingStoryFrameItemsByIds(ids) + } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index b2c88bb789c2..58024377eb65 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -23,13 +23,11 @@ object StoriesPrefs { KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.id.toString() } - @JvmStatic fun checkSlideIdExists(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { val slideIdKey = buildSlideKey(siteId, mediaId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - @JvmStatic fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(context, siteId, mediaId) storyFrameItem?.let { frame -> @@ -73,7 +71,6 @@ object StoriesPrefs { editor.apply() } - @JvmStatic fun isValidSlide(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { return checkSlideIdExists(context, siteId, mediaId) && checkSlideOriginalBackgroundMediaExists(context, siteId, mediaId) @@ -83,7 +80,6 @@ object StoriesPrefs { return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) } - @JvmStatic fun getSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId): StoryFrameItem? { val jsonSlide = getSlideJson(context, buildSlideKey(siteId, mediaId)) jsonSlide?.let { @@ -91,7 +87,6 @@ object StoriesPrefs { } ?: return null } - @JvmStatic fun getSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId): StoryFrameItem? { val jsonSlide = getSlideJson(context, buildSlideKey(siteId, mediaId)) jsonSlide?.let { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index ca3fe97647a5..978f52f750dd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -20,7 +20,6 @@ import javax.inject.Inject @Reusable class LoadStoryFromStoriesPrefsUseCase @Inject constructor( private val storyRepositoryWrapper: StoryRepositoryWrapper, - private val site: SiteModel, private val mediaStore: MediaStore, private val context: Context ) { @@ -46,7 +45,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return true } - fun loadOrReCreateStoryFromStoriesPrefs(mediaIds: ArrayList): ReCreateStoryResult { + private fun loadOrReCreateStoryFromStoriesPrefs(site: SiteModel, mediaIds: ArrayList): ReCreateStoryResult { // the StoryRepository didn't have it but we have editable serialized slides so, // create a new Story from scratch with these deserialized StoryFrameItems var allStorySlidesAreEditable: Boolean = true @@ -90,6 +89,30 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable, noSlidesLoaded) } + fun loadStoryFromMemoryOrRecreateFromPrefs(site: SiteModel, mediaFiles: ArrayList): ReCreateStoryResult { + val mediaIds = getMediaIdsFromStoryBlockBridgeMediaFiles( + mediaFiles + ) + var allStorySlidesAreEditable = areAllStorySlidesEditable( + site, + mediaIds + ) + + // now look for a Story in the StoryRepository that has all these frames and, if not found, let's + // just build the Story object ourselves to keep these files arrangement + var storyIndex = storyRepositoryWrapper.findStoryContainingStoryFrameItemsByIds(mediaIds) + if (storyIndex == StoryRepository.DEFAULT_NONE_SELECTED) { + // the StoryRepository didn't have it but we have editable serialized slides so, + // create a new Story from scratch with these deserialized StoryFrameItems + return loadOrReCreateStoryFromStoriesPrefs( + site, + mediaIds + ) + } else { + return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable, false) + } + } + data class ReCreateStoryResult( val storyIndex: StoryIndex, val allStorySlidesAreEditable: Boolean, diff --git a/libs/stories-android b/libs/stories-android index 2082538ec174..7966a7c7364c 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 2082538ec174aadf312ecd0fb2c5272efa33381b +Subproject commit 7966a7c7364c347170ba6986c8259018d69f9009 From 834be35827697bea6e67e62bc0394bf34f8341b3 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 7 Oct 2020 09:24:55 -0300 Subject: [PATCH 083/343] Update WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java Co-authored-by: Joel Dean --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index ea0096acf06c..168f3ad232c9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3073,7 +3073,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie this, mSite, result.getStoryIndex(), result.getAllStorySlidesAreEditable(), true ); } else { - // unfortunately we couldn't even load the remote media Ids indicated by the StoryBLock so we can't allow + // unfortunately we couldn't even load the remote media Ids indicated by the StoryBlock so we can't allow // editing at this time :( AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); From 10a8a9e9f9b83f0e8b2e54c096a1d1218b61c78a Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 7 Oct 2020 09:27:40 -0300 Subject: [PATCH 084/343] Update WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt Co-authored-by: Joel Dean --- .../android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt index ccb97c3de05c..6deae0e3139f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/AddLocalMediaToPostUseCase.kt @@ -69,7 +69,7 @@ class AddLocalMediaToPostUseCase @Inject constructor( // here we pass a map of "old" (before optimisation) Uris to the new MediaModels which contain // both the mediaModel ids and the optimized media URLs. - // this way, the listener will be able to process from other models potining to the old URLs + // this way, the listener will be able to process from other models pointing to the old URLs // and make any needed updates editorMediaListener.onMediaModelsCreatedFromOptimizedUris( uriList.zip(createMediaModelsResult.mediaModels).toMap() From f6d92d293ce64df43f5e5f08705c655203d9c74f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 10:16:42 -0300 Subject: [PATCH 085/343] made StoriesPrefs a @Singleton annotated injectable class --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 14 ++--- .../media/StoryMediaSaveUploadBridge.kt | 4 +- .../android/ui/stories/prefs/StoriesPrefs.kt | 57 +++++++++++-------- .../LoadStoryFromStoriesPrefsUseCase.kt | 13 ++--- .../ui/uploads/MediaUploadReadyProcessor.java | 5 +- 5 files changed, 49 insertions(+), 44 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index e9c22e279c03..d8b43ff0a138 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -1,7 +1,6 @@ package org.wordpress.android.ui.stories import com.google.gson.Gson -import org.wordpress.android.WordPress import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.prefs.StoriesPrefs @@ -11,7 +10,9 @@ import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject -class SaveStoryGutenbergBlockUseCase @Inject constructor() { +class SaveStoryGutenbergBlockUseCase @Inject constructor( + private val storiesPrefs: StoriesPrefs +) { fun buildJetpackStoryBlockInPost( editPostRepository: EditPostRepository, mediaFiles: Map @@ -66,21 +67,18 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor() { val localIdKey = mediaFile.id.toLong() val remoteIdKey = mediaFile.mediaId.toLong() val localSiteId = post.localSiteId.toLong() - StoriesPrefs.getSlideWithLocalId( - WordPress.getContext(), + storiesPrefs.getSlideWithLocalId( localSiteId, LocalMediaId(localIdKey) )?.let { it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID - StoriesPrefs.saveSlideWithRemoteId( - WordPress.getContext(), + storiesPrefs.saveSlideWithRemoteId( localSiteId, RemoteMediaId(remoteIdKey), // use the new mediaId as key it ) // now delete the old entry - StoriesPrefs.deleteSlideWithLocalId( - WordPress.getContext(), + storiesPrefs.deleteSlideWithLocalId( localSiteId, LocalMediaId(localIdKey) ) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index d45c809556d7..7c29f1928d49 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -50,6 +50,7 @@ import kotlin.coroutines.CoroutineContext class StoryMediaSaveUploadBridge @Inject constructor( private val addLocalMediaToPostUseCase: AddLocalMediaToPostUseCase, private val savePostToDbUseCase: SavePostToDbUseCase, + private val storiesPrefs: StoriesPrefs, private val uploadService: UploadServiceFacade, private val networkUtils: NetworkUtilsWrapper, private val postUtils: PostUtilsWrapper, @@ -188,8 +189,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( val mediaModel = oldUriToMediaFiles.get(Uri.fromFile(frame.composedFrameFile)) mediaModel?.let { frame.id = it.id.toString() - StoriesPrefs.saveSlideWithLocalId( - appContext, + storiesPrefs.saveSlideWithLocalId( it.localSiteId.toLong(), // use the local id to save the original, will be replaced later // with mediaModel.mediaId after uploading to the remote site diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 58024377eb65..f4bae3b9bb6c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -7,11 +7,18 @@ import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource import com.wordpress.stories.compose.story.StorySerializerUtils - -object StoriesPrefs { - private const val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" - private const val KEY_PREFIX_LOCAL_MEDIA_ID = "l-" - private const val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class StoriesPrefs @Inject constructor( + private val context: Context +) { + companion object { + private val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" + private val KEY_PREFIX_LOCAL_MEDIA_ID = "l-" + private val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" + } private fun buildSlideKey(siteId: Long, mediaId: RemoteMediaId): String { return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + @@ -23,13 +30,13 @@ object StoriesPrefs { KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.id.toString() } - fun checkSlideIdExists(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { + fun checkSlideIdExists(siteId: Long, mediaId: RemoteMediaId): Boolean { val slideIdKey = buildSlideKey(siteId, mediaId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - fun checkSlideOriginalBackgroundMediaExists(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { - val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(context, siteId, mediaId) + fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: RemoteMediaId): Boolean { + val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(siteId, mediaId) storyFrameItem?.let { frame -> // now check the background media exists or is accessible on this device frame.source.let { source -> @@ -39,7 +46,7 @@ object StoriesPrefs { } ?: return false } else if (source is UriBackgroundSource) { source.contentUri?.let { - return isUriAccessible(it, context) + return isUriAccessible(it) } ?: return false } } @@ -47,7 +54,7 @@ object StoriesPrefs { return false } - private fun isUriAccessible(uri: Uri, context: Context): Boolean { + private fun isUriAccessible(uri: Uri): Boolean { if (uri.toString().startsWith("http")) { // TODO: assume it'll be accessible - we'll figure out later // potentially force external download using MediaUtils.downloadExternalMedia() here to ensure @@ -65,53 +72,53 @@ object StoriesPrefs { return false } - private fun saveSlide(context: Context, slideIdKey: String, storySlideJson: String) { + private fun saveSlide(slideIdKey: String, storySlideJson: String) { val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() editor.putString(slideIdKey, storySlideJson) editor.apply() } - fun isValidSlide(context: Context, siteId: Long, mediaId: RemoteMediaId): Boolean { - return checkSlideIdExists(context, siteId, mediaId) && - checkSlideOriginalBackgroundMediaExists(context, siteId, mediaId) + fun isValidSlide(siteId: Long, mediaId: RemoteMediaId): Boolean { + return checkSlideIdExists(siteId, mediaId) && + checkSlideOriginalBackgroundMediaExists(siteId, mediaId) } - private fun getSlideJson(context: Context, slideIdKey: String): String? { + private fun getSlideJson(slideIdKey: String): String? { return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) } - fun getSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId): StoryFrameItem? { - val jsonSlide = getSlideJson(context, buildSlideKey(siteId, mediaId)) + fun getSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId): StoryFrameItem? { + val jsonSlide = getSlideJson(buildSlideKey(siteId, mediaId)) jsonSlide?.let { return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) } ?: return null } - fun getSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId): StoryFrameItem? { - val jsonSlide = getSlideJson(context, buildSlideKey(siteId, mediaId)) + fun getSlideWithLocalId(siteId: Long, mediaId: LocalMediaId): StoryFrameItem? { + val jsonSlide = getSlideJson(buildSlideKey(siteId, mediaId)) jsonSlide?.let { return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) } ?: return null } - fun saveSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId, storyFrameItem: StoryFrameItem) { + fun saveSlideWithLocalId(siteId: Long, mediaId: LocalMediaId, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) - saveSlide(context, slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) + saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } - fun saveSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId, storyFrameItem: StoryFrameItem) { + fun saveSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) - saveSlide(context, slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) + saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } - fun deleteSlideWithLocalId(context: Context, siteId: Long, mediaId: LocalMediaId) { + fun deleteSlideWithLocalId(siteId: Long, mediaId: LocalMediaId) { val slideIdKey = buildSlideKey(siteId, mediaId) val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() editor.remove(slideIdKey) editor.apply() } - fun deleteSlideWithRemoteId(context: Context, siteId: Long, mediaId: RemoteMediaId) { + fun deleteSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId) { val slideIdKey = buildSlideKey(siteId, mediaId) val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() editor.remove(slideIdKey) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 978f52f750dd..088619301dfb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.stories.usecase -import android.content.Context import android.net.Uri import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryIndex @@ -10,9 +9,8 @@ import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.StoryRepositoryWrapper +import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.getSlideWithRemoteId -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.isValidSlide import java.util.ArrayList import java.util.HashMap import javax.inject.Inject @@ -20,8 +18,8 @@ import javax.inject.Inject @Reusable class LoadStoryFromStoriesPrefsUseCase @Inject constructor( private val storyRepositoryWrapper: StoryRepositoryWrapper, - private val mediaStore: MediaStore, - private val context: Context + private val storiesPrefs: StoriesPrefs, + private val mediaStore: MediaStore ) { fun getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles: ArrayList): ArrayList { val mediaIds = ArrayList() @@ -38,7 +36,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { - if (!isValidSlide(context, site.getId().toLong(), RemoteMediaId(mediaId.toLong()))) { + if (!storiesPrefs.isValidSlide(site.getId().toLong(), RemoteMediaId(mediaId.toLong()))) { return false } } @@ -54,8 +52,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( storyRepositoryWrapper.loadStory(storyIndex) storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() for (mediaId in mediaIds) { - var storyFrameItem = getSlideWithRemoteId( - context, + var storyFrameItem = storiesPrefs.getSlideWithRemoteId( site.getId().toLong(), RemoteMediaId(mediaId.toLong()) ) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java index 8ff1bbdc09d1..1e1a6b419858 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java @@ -9,6 +9,7 @@ import org.wordpress.android.ui.posts.PostUtils; import org.wordpress.android.ui.prefs.AppPrefs; import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase; +import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.util.helpers.MediaFile; @@ -21,7 +22,9 @@ public PostModel replaceMediaFileWithUrlInPost(@Nullable PostModel post, String boolean showGutenbergEditor = AppPrefs.isGutenbergEditorEnabled(); if (PostUtils.contentContainsWPStoryGutenbergBlocks(post.getContent())) { - SaveStoryGutenbergBlockUseCase saveStoryGutenbergBlockUseCase = new SaveStoryGutenbergBlockUseCase(); + SaveStoryGutenbergBlockUseCase saveStoryGutenbergBlockUseCase = new SaveStoryGutenbergBlockUseCase( + new StoriesPrefs(WordPress.getContext()) + ); saveStoryGutenbergBlockUseCase .replaceLocalMediaIdsWithRemoteMediaIdsInPost(post, mediaFile); } else if (showGutenbergEditor && PostUtils.contentContainsGutenbergBlocks(post.getContent())) { From 34d4effeacc4f6b9356cb0c780bdbb8b2f73bd4e Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 10:20:20 -0300 Subject: [PATCH 086/343] kotlinified expression --- .../org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index f4bae3b9bb6c..6f256a6f663b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -61,9 +61,8 @@ class StoriesPrefs @Inject constructor( return true } try { - val inputStream = context.contentResolver.openInputStream(uri) - if (inputStream != null) { - inputStream.close() + context.contentResolver.openInputStream(uri)?.let { + it.close() return true } } catch (e: java.lang.Exception) { From a0faf8ac175f71a7812ee038d7ca6c43c7169659 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 7 Oct 2020 10:24:25 -0300 Subject: [PATCH 087/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt Co-authored-by: Joel Dean --- .../org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 6f256a6f663b..4b632a3a8bb6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -119,9 +119,10 @@ class StoriesPrefs @Inject constructor( fun deleteSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId) { val slideIdKey = buildSlideKey(siteId, mediaId) - val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() - editor.remove(slideIdKey) - editor.apply() + PreferenceManager.getDefaultSharedPreferences(context).edit().apply { + remove(slideIdKey) + apply() + } } data class RemoteMediaId(val mediaId: Long) From 34631b73f799058600f1395c8a08ef3531a25bcf Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 7 Oct 2020 10:25:05 -0300 Subject: [PATCH 088/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt Co-authored-by: Joel Dean --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 088619301dfb..d711a273c5f4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -21,7 +21,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( private val storiesPrefs: StoriesPrefs, private val mediaStore: MediaStore ) { - fun getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles: ArrayList): ArrayList { + fun getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles: ArrayList): ArrayList { val mediaIds = ArrayList() for (mediaFile in mediaFiles) { val mediaIdLong = (mediaFile as HashMap)["id"] From aba2177e498778e78ad10b1b4fcce518249c3771 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 10:28:40 -0300 Subject: [PATCH 089/343] fixed method signature --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index d711a273c5f4..e5bb1365ca4f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -86,7 +86,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable, noSlidesLoaded) } - fun loadStoryFromMemoryOrRecreateFromPrefs(site: SiteModel, mediaFiles: ArrayList): ReCreateStoryResult { + fun loadStoryFromMemoryOrRecreateFromPrefs(site: SiteModel, mediaFiles: ArrayList): ReCreateStoryResult { val mediaIds = getMediaIdsFromStoryBlockBridgeMediaFiles( mediaFiles ) From df5b594e2667dc1d94efa845553ae1e88bb71748 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 7 Oct 2020 10:32:01 -0300 Subject: [PATCH 090/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt Co-authored-by: Joel Dean --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index e5bb1365ca4f..6f477f556f58 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -68,7 +68,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( site, tmpMediaIdsLong ) - if (mediaModelList.size == 0) { + if (mediaModelList.isEmpty()) { noSlidesLoaded = true } else { for (mediaModel in mediaModelList) { From 99efe00984b0504395b4d1121b538c937c27379d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 10:33:15 -0300 Subject: [PATCH 091/343] using implicit getter --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 6f477f556f58..14056fda4035 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -36,7 +36,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { - if (!storiesPrefs.isValidSlide(site.getId().toLong(), RemoteMediaId(mediaId.toLong()))) { + if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteMediaId(mediaId.toLong()))) { return false } } From ddcda8b0698df5350af06a51397d7469d0dee3d8 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 10:50:04 -0300 Subject: [PATCH 092/343] using FluxC's LocalOrRemoteId sealed class variants --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 12 +++---- .../media/StoryMediaSaveUploadBridge.kt | 4 +-- .../android/ui/stories/prefs/StoriesPrefs.kt | 33 +++++++++---------- .../LoadStoryFromStoriesPrefsUseCase.kt | 6 ++-- 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index d8b43ff0a138..9b3fcbab5d8a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -1,11 +1,11 @@ package org.wordpress.android.ui.stories import com.google.gson.Gson +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.prefs.StoriesPrefs -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -64,23 +64,23 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( url = mediaFile.fileURL // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. - val localIdKey = mediaFile.id.toLong() + val localIdKey = mediaFile.id.toInt() val remoteIdKey = mediaFile.mediaId.toLong() val localSiteId = post.localSiteId.toLong() storiesPrefs.getSlideWithLocalId( localSiteId, - LocalMediaId(localIdKey) + LocalId(localIdKey) )?.let { it.id = mediaFile.mediaId // update the StoryFrameItem id to hold the same value as the remote mediaID storiesPrefs.saveSlideWithRemoteId( localSiteId, - RemoteMediaId(remoteIdKey), // use the new mediaId as key + RemoteId(remoteIdKey), // use the new mediaId as key it ) // now delete the old entry storiesPrefs.deleteSlideWithLocalId( localSiteId, - LocalMediaId(localIdKey) + LocalId(localIdKey) ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 7c29f1928d49..41d1d5938a5a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.launch import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.WordPress +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.PostImmutableModel import org.wordpress.android.fluxc.model.SiteModel @@ -30,7 +31,6 @@ import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase import org.wordpress.android.ui.stories.StoriesTrackerHelper import org.wordpress.android.ui.stories.StoryComposerActivity import org.wordpress.android.ui.stories.prefs.StoriesPrefs -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.LocalMediaId import org.wordpress.android.ui.uploads.UploadServiceFacade import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.NetworkUtilsWrapper @@ -193,7 +193,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( it.localSiteId.toLong(), // use the local id to save the original, will be replaced later // with mediaModel.mediaId after uploading to the remote site - LocalMediaId(it.id.toLong()), + LocalId(it.id.toInt()), frame ) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 4b632a3a8bb6..aed7d5e74f88 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -7,6 +7,8 @@ import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource import com.wordpress.stories.compose.story.StorySerializerUtils +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import javax.inject.Inject import javax.inject.Singleton @@ -20,22 +22,22 @@ class StoriesPrefs @Inject constructor( private val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" } - private fun buildSlideKey(siteId: Long, mediaId: RemoteMediaId): String { + private fun buildSlideKey(siteId: Long, mediaId: RemoteId): String { return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + - KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.mediaId.toString() + KEY_PREFIX_REMOTE_MEDIA_ID + mediaId.value.toString() } - private fun buildSlideKey(siteId: Long, mediaId: LocalMediaId): String { + private fun buildSlideKey(siteId: Long, mediaId: LocalId): String { return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + - KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.id.toString() + KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.value.toString() } - fun checkSlideIdExists(siteId: Long, mediaId: RemoteMediaId): Boolean { + fun checkSlideIdExists(siteId: Long, mediaId: RemoteId): Boolean { val slideIdKey = buildSlideKey(siteId, mediaId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: RemoteMediaId): Boolean { + fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: RemoteId): Boolean { val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(siteId, mediaId) storyFrameItem?.let { frame -> // now check the background media exists or is accessible on this device @@ -77,7 +79,7 @@ class StoriesPrefs @Inject constructor( editor.apply() } - fun isValidSlide(siteId: Long, mediaId: RemoteMediaId): Boolean { + fun isValidSlide(siteId: Long, mediaId: RemoteId): Boolean { return checkSlideIdExists(siteId, mediaId) && checkSlideOriginalBackgroundMediaExists(siteId, mediaId) } @@ -86,45 +88,42 @@ class StoriesPrefs @Inject constructor( return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) } - fun getSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId): StoryFrameItem? { + fun getSlideWithRemoteId(siteId: Long, mediaId: RemoteId): StoryFrameItem? { val jsonSlide = getSlideJson(buildSlideKey(siteId, mediaId)) jsonSlide?.let { return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) } ?: return null } - fun getSlideWithLocalId(siteId: Long, mediaId: LocalMediaId): StoryFrameItem? { + fun getSlideWithLocalId(siteId: Long, mediaId: LocalId): StoryFrameItem? { val jsonSlide = getSlideJson(buildSlideKey(siteId, mediaId)) jsonSlide?.let { return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) } ?: return null } - fun saveSlideWithLocalId(siteId: Long, mediaId: LocalMediaId, storyFrameItem: StoryFrameItem) { + fun saveSlideWithLocalId(siteId: Long, mediaId: LocalId, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } - fun saveSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId, storyFrameItem: StoryFrameItem) { + fun saveSlideWithRemoteId(siteId: Long, mediaId: RemoteId, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } - fun deleteSlideWithLocalId(siteId: Long, mediaId: LocalMediaId) { + fun deleteSlideWithLocalId(siteId: Long, mediaId: LocalId) { val slideIdKey = buildSlideKey(siteId, mediaId) val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() editor.remove(slideIdKey) editor.apply() } - fun deleteSlideWithRemoteId(siteId: Long, mediaId: RemoteMediaId) { + fun deleteSlideWithRemoteId(siteId: Long, mediaId: RemoteId) { val slideIdKey = buildSlideKey(siteId, mediaId) - PreferenceManager.getDefaultSharedPreferences(context).edit().apply { + PreferenceManager.getDefaultSharedPreferences(context).edit().apply { remove(slideIdKey) apply() } } - - data class RemoteMediaId(val mediaId: Long) - data class LocalMediaId(val id: Long) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 14056fda4035..e7fb88edf057 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -5,12 +5,12 @@ import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryIndex import com.wordpress.stories.compose.story.StoryRepository import dagger.Reusable +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.RemoteMediaId import java.util.ArrayList import java.util.HashMap import javax.inject.Inject @@ -36,7 +36,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { - if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteMediaId(mediaId.toLong()))) { + if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong()))) { return false } } @@ -54,7 +54,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( for (mediaId in mediaIds) { var storyFrameItem = storiesPrefs.getSlideWithRemoteId( site.getId().toLong(), - RemoteMediaId(mediaId.toLong()) + RemoteId(mediaId.toLong()) ) if (storyFrameItem != null) { storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) From 240aebb4bdf7d164d1ab5ebba29875335511e787 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 15:26:12 -0300 Subject: [PATCH 093/343] changed method signature for kotlin's Any --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 7e80baa6d018..3fec56348ac8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -34,7 +34,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return mediaIds } - fun anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles: ArrayList): Boolean { + fun anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles: ArrayList): Boolean { for (mediaFile in mediaFiles) { try { (mediaFile as HashMap)["id"] From a6dee817a13eddd0aecfbda821b5d0767dc8f508 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 7 Oct 2020 15:28:23 -0300 Subject: [PATCH 094/343] moved FrameSaveListener methods to StoriesEventListener lifecycle observer --- .../android/ui/posts/EditPostActivity.java | 134 +-------------- .../ui/posts/editor/StoriesEventListener.kt | 158 ++++++++++++++++++ 2 files changed, 164 insertions(+), 128 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 97bce8aa4f86..ad670889b5f0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -41,13 +41,6 @@ import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; -import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted; -import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed; -import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress; -import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart; -import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult; -import com.wordpress.stories.compose.story.Story; -import com.wordpress.stories.compose.story.StoryFrameItem; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -159,6 +152,7 @@ import org.wordpress.android.ui.posts.editor.StorePostViewModel.ActivityFinishState; import org.wordpress.android.ui.posts.editor.StorePostViewModel.UpdateFromEditor; import org.wordpress.android.ui.posts.editor.StorePostViewModel.UpdateFromEditor.PostFields; +import org.wordpress.android.ui.posts.editor.StoriesEventListener; import org.wordpress.android.ui.posts.editor.media.AddExistingMediaSource; import org.wordpress.android.ui.posts.editor.media.EditorMedia; import org.wordpress.android.ui.posts.editor.media.EditorMediaListener; @@ -172,7 +166,6 @@ import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper; import org.wordpress.android.ui.stockmedia.StockMediaPickerActivity; import org.wordpress.android.ui.stories.StoryRepositoryWrapper; -import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent; import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; @@ -243,7 +236,6 @@ import static org.wordpress.android.analytics.AnalyticsTracker.Stat.APP_REVIEWS_EVENT_INCREMENTED_BY_PUBLISHING_POST_OR_PAGE; import static org.wordpress.android.imageeditor.preview.PreviewImageFragment.PREVIEW_IMAGE_REDUCED_SIZE_FACTOR; import static org.wordpress.android.ui.history.HistoryDetailContainerFragment.KEY_REVISION; -import static org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.TEMPORARY_ID_PREFIX; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -343,7 +335,6 @@ enum RestartEditorOptions { private EditorFragmentAbstract mEditorFragment; private EditPostSettingsFragment mEditPostSettingsFragment; private EditorMediaUploadListener mEditorMediaUploadListener; - private StorySaveMediaListener mStorySaveMediaListener; private EditorPhotoPicker mEditorPhotoPicker; private ProgressDialog mProgressDialog; @@ -407,7 +398,7 @@ enum RestartEditorOptions { @Inject StoryRepositoryWrapper mStoryRepositoryWrapper; @Inject LoadStoryFromStoriesPrefsUseCase mLoadStoryFromStoriesPrefsUseCase; @Inject StoriesPrefs mStoriesPrefs; - + @Inject StoriesEventListener mStoriesEventListener; private StorePostViewModel mViewModel; @@ -590,7 +581,7 @@ protected void onCreate(Bundle savedInstanceState) { } if (mEditorFragment instanceof StorySaveMediaListener) { - mStorySaveMediaListener = (StorySaveMediaListener) mEditorFragment; + mStoriesEventListener.setSaveMediaListener((StorySaveMediaListener) mEditorFragment); } } @@ -668,6 +659,8 @@ protected void onCreate(Bundle savedInstanceState) { ActivityId.trackLastActivity(ActivityId.POST_EDITOR); setupPrepublishingBottomSheetRunnable(); + + mStoriesEventListener.start(this.getLifecycle(), mSite); } private void fetchSiteSettings() { @@ -2200,7 +2193,7 @@ public Fragment getItem(int position) { } if (mEditorFragment instanceof StorySaveMediaListener) { - mStorySaveMediaListener = (StorySaveMediaListener) mEditorFragment; + mStoriesEventListener.setSaveMediaListener((StorySaveMediaListener) mEditorFragment); } break; case PAGE_SETTINGS: @@ -3154,121 +3147,6 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } } - // Story Frame Save Service events - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - public void onStoryFrameSaveStart(FrameSaveStart event) { - if (isFinishing()) { - return; - } - String localMediaId = String.valueOf(event.getFrameId()); - if (mStorySaveMediaListener != null) { - float progress = mStoryRepositoryWrapper.getCurrentStorySaveProgress(event.getStoryIndex(), 0.0f); - mStorySaveMediaListener.onMediaSaveReattached(localMediaId, progress); - } - } - - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - public void onStoryFrameSaveProgress(FrameSaveProgress event) { - if (isFinishing()) { - return; - } - String localMediaId = String.valueOf(event.getFrameId()); - if (mStorySaveMediaListener != null) { - float progress = mStoryRepositoryWrapper.getCurrentStorySaveProgress( - event.getStoryIndex(), - event.getProgress() - ); - mStorySaveMediaListener.onMediaSaveProgress(localMediaId, progress); - } - } - - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - public void onStoryFrameSaveCompleted(FrameSaveCompleted event) { - if (isFinishing()) { - return; - } - String localMediaId = event.getFrameId(); - if (mStorySaveMediaListener != null) { - // check whether this is a temporary file being just saved (so we don't have a proper local MediaModel yet) - // catch ( NumberFormatException e) - if (localMediaId.startsWith(TEMPORARY_ID_PREFIX)) { - Story story = mStoryRepositoryWrapper.getStoryAtIndex(event.getStoryIndex()); - - // first, update the media's url - StoryFrameItem frame = story.getFrames().get(event.getFrameIndex()); - mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, - Uri.fromFile(frame.getComposedFrameFile()).toString()); - - // now update progress - float totalProgress = mStoryRepositoryWrapper.getCurrentStorySaveProgress(event.getStoryIndex(), 0.0f); - mStorySaveMediaListener.onMediaSaveProgress(localMediaId, totalProgress); - } else { - MediaModel mediaModel = mMediaStore.getSiteMediaWithId(mSite, Long.parseLong(localMediaId)); - if (mediaModel != null) { - MediaFile mediaFile = FluxCUtils.mediaFileFromMediaModel(mediaModel); - mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, mediaFile.getFileURL()); - } - } - } - } - - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - public void onStoryFrameMediaModelCreated(StoryFrameMediaModelCreatedEvent event) { - if (isFinishing()) { - return; - } - - if (mStorySaveMediaListener != null) { - mStorySaveMediaListener.onMediaModelCreatedForFile(event.getOldId(), event.getNewId(), event.getOldUrl()); - } - } - - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - public void onStoryFrameSaveFailed(FrameSaveFailed event) { - if (isFinishing()) { - return; - } - String localMediaId = String.valueOf(event.getFrameId()); -// if (mStorySaveMediaListener != null) { -// mStorySaveMediaListener.onMediaSaveFailed(localMediaId); -// } - // just update progress, we may have still some other frames in this story that need be saved. - // we will send the Failed signal once all the Story frames have been processed - if (mStorySaveMediaListener != null) { - float progress = mStoryRepositoryWrapper.getCurrentStorySaveProgress(event.getStoryIndex(), 0.0f); - mStorySaveMediaListener.onMediaSaveReattached(localMediaId, progress); - } - } - - - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - public void onStorySaveProcessFinished(StorySaveResult event) { - if (isFinishing()) { - return; - } - - Story story = mStoryRepositoryWrapper.getStoryAtIndex(event.getStoryIndex()); - if (event.isSuccess() && event.getFrameSaveResult().size() == story.getFrames().size()) { - // take the first frame IDs and mediaUri - String localMediaId = String.valueOf(story.getFrames().get(0).getId()); - String mediaUrl = Uri.fromFile(story.getFrames().get(0).getComposedFrameFile()).toString(); - if (mStorySaveMediaListener != null) { - mStorySaveMediaListener.onMediaSaveSucceeded(localMediaId, mediaUrl); - } - } else { - String localMediaId = String.valueOf(story.getFrames().get(0).getId()); - if (mStorySaveMediaListener != null) { - mStorySaveMediaListener.onMediaSaveFailed(localMediaId); - } - } - } - // FluxC events @SuppressWarnings("unused") diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt new file mode 100644 index 000000000000..4a2045aae724 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -0,0 +1,158 @@ +package org.wordpress.android.ui.posts.editor + +import android.net.Uri +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.Lifecycle.State.CREATED +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress +import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart +import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import org.wordpress.android.editor.gutenberg.StorySaveMediaListener +import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.model.MediaModel +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX +import org.wordpress.android.ui.stories.StoryRepositoryWrapper +import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent +import org.wordpress.android.util.FluxCUtils +import org.wordpress.android.util.helpers.MediaFile +import javax.inject.Inject + +class StoriesEventListener @Inject constructor( + private val dispatcher: Dispatcher, + private val mediaStore: MediaStore, + private val storyRepositoryWrapper: StoryRepositoryWrapper +) : LifecycleObserver { + private lateinit var lifecycle: Lifecycle + private lateinit var site: SiteModel + private var storySaveMediaListener: StorySaveMediaListener? = null + + @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) + private fun onCreate() { + dispatcher.register(this) + EventBus.getDefault().register(this) + } + + /** + * Handles the [Lifecycle.Event.ON_DESTROY] event to cleanup the registration for dispatcher and removing the + * observer for lifecycle. + */ + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + private fun onDestroy() { + lifecycle.removeObserver(this) + dispatcher.unregister(this) + EventBus.getDefault().unregister(this) + } + + fun start(lifecycle: Lifecycle, site: SiteModel) { + this.site = site + this.lifecycle = lifecycle + this.lifecycle.addObserver(this) + } + + fun setSaveMediaListener(newListener: StorySaveMediaListener) { + storySaveMediaListener = newListener + } + + // Story Frame Save Service events + @Subscribe(threadMode = ThreadMode.MAIN) + fun onStoryFrameSaveStart(event: FrameSaveStart) { + if (!lifecycle.currentState.isAtLeast(CREATED)) { + return + } + val localMediaId = event.frameId.toString() + val progress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress(event.storyIndex, 0.0f) + storySaveMediaListener?.onMediaSaveReattached(localMediaId, progress) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onStoryFrameSaveProgress(event: FrameSaveProgress) { + if (!lifecycle.currentState.isAtLeast(CREATED)) { + return + } + val localMediaId = event.frameId.toString() + val progress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress( + event.storyIndex, + event.progress + ) + storySaveMediaListener?.onMediaSaveProgress(localMediaId, progress) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onStoryFrameSaveCompleted(event: FrameSaveCompleted) { + if (!lifecycle.currentState.isAtLeast(CREATED)) { + return + } + val localMediaId = requireNotNull(event.frameId) + + // check whether this is a temporary file being just saved (so we don't have a proper local MediaModel yet) + // catch ( NumberFormatException e) + if (localMediaId.startsWith(TEMPORARY_ID_PREFIX)) { + val (frames) = storyRepositoryWrapper.getStoryAtIndex(event.storyIndex) + + // first, update the media's url + val frame = frames[event.frameIndex] + storySaveMediaListener?.onMediaSaveSucceeded( + localMediaId, + Uri.fromFile(frame.composedFrameFile).toString() + ) + + // now update progress + val totalProgress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress( + event.storyIndex, + 0.0f + ) + storySaveMediaListener?.onMediaSaveProgress(localMediaId, totalProgress) + } else { + val mediaModel: MediaModel = mediaStore.getSiteMediaWithId(site, localMediaId.toLong()) + if (mediaModel != null) { + val mediaFile: MediaFile = FluxCUtils.mediaFileFromMediaModel(mediaModel) + storySaveMediaListener?.onMediaSaveSucceeded(localMediaId, mediaFile.getFileURL()) + } + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onStoryFrameMediaModelCreated(event: StoryFrameMediaModelCreatedEvent) { + if (!lifecycle.currentState.isAtLeast(CREATED)) { + return + } + storySaveMediaListener?.onMediaModelCreatedForFile(event.oldId, event.newId, event.oldUrl) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onStoryFrameSaveFailed(event: FrameSaveFailed) { + if (!lifecycle.currentState.isAtLeast(CREATED)) { + return + } + val localMediaId = event.frameId.toString() + // just update progress, we may have still some other frames in this story that need be saved. + // we will send the Failed signal once all the Story frames have been processed + val progress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress(event.storyIndex, 0.0f) + storySaveMediaListener?.onMediaSaveReattached(localMediaId, progress) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onStorySaveProcessFinished(event: StorySaveResult) { + if (!lifecycle.currentState.isAtLeast(CREATED)) { + return + } + val story = storyRepositoryWrapper.getStoryAtIndex(event.storyIndex) + if (event.isSuccess() && event.frameSaveResult.size == story.frames.size) { + // take the first frame IDs and mediaUri + val localMediaId = story.frames[0].id.toString() + val mediaUrl: String = Uri.fromFile(story.frames[0].composedFrameFile).toString() + storySaveMediaListener?.onMediaSaveSucceeded(localMediaId, mediaUrl) + } else { + val localMediaId = story.frames[0].id.toString() + storySaveMediaListener?.onMediaSaveFailed(localMediaId) + } + } +} From 2c54019f4edb6e1d905ec05f9a31c7a796ea334c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 8 Oct 2020 15:48:53 -0300 Subject: [PATCH 095/343] made MediaUploadReadyProcessor injectable, and injecting SaveStoryGutenbergBlockUseCase --- .../wordpress/android/modules/AppComponent.java | 3 +++ .../ui/uploads/MediaUploadReadyProcessor.java | 16 ++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java index de63dfa201aa..1b4ed181b6fe 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java @@ -182,6 +182,7 @@ import org.wordpress.android.ui.themes.ThemeBrowserActivity; import org.wordpress.android.ui.themes.ThemeBrowserFragment; import org.wordpress.android.ui.uploads.MediaUploadHandler; +import org.wordpress.android.ui.uploads.MediaUploadReadyProcessor; import org.wordpress.android.ui.uploads.PostUploadHandler; import org.wordpress.android.ui.uploads.UploadService; import org.wordpress.android.ui.whatsnew.FeatureAnnouncementDialogFragment; @@ -584,6 +585,8 @@ public interface AppComponent extends AndroidInjector { void inject(MediaPickerFragment object); + void inject(MediaUploadReadyProcessor object); + // Allows us to inject the application without having to instantiate any modules, and provides the Application // in the app graph @Component.Builder diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java index 1e1a6b419858..7eb3180df08d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadReadyProcessor.java @@ -9,11 +9,18 @@ import org.wordpress.android.ui.posts.PostUtils; import org.wordpress.android.ui.prefs.AppPrefs; import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase; -import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.util.helpers.MediaFile; +import javax.inject.Inject; + public class MediaUploadReadyProcessor implements MediaUploadReadyListener { + @Inject SaveStoryGutenbergBlockUseCase mSaveStoryGutenbergBlockUseCase; + + @Inject public MediaUploadReadyProcessor() { + ((WordPress) WordPress.getContext().getApplicationContext()).component().inject(this); + } + @Override public PostModel replaceMediaFileWithUrlInPost(@Nullable PostModel post, String localMediaId, MediaFile mediaFile, String siteUrl) { @@ -22,11 +29,8 @@ public PostModel replaceMediaFileWithUrlInPost(@Nullable PostModel post, String boolean showGutenbergEditor = AppPrefs.isGutenbergEditorEnabled(); if (PostUtils.contentContainsWPStoryGutenbergBlocks(post.getContent())) { - SaveStoryGutenbergBlockUseCase saveStoryGutenbergBlockUseCase = new SaveStoryGutenbergBlockUseCase( - new StoriesPrefs(WordPress.getContext()) - ); - saveStoryGutenbergBlockUseCase - .replaceLocalMediaIdsWithRemoteMediaIdsInPost(post, mediaFile); + mSaveStoryGutenbergBlockUseCase + .replaceLocalMediaIdsWithRemoteMediaIdsInPost(post, mediaFile); } else if (showGutenbergEditor && PostUtils.contentContainsGutenbergBlocks(post.getContent())) { post.setContent( PostUtils.replaceMediaFileWithUrlInGutenbergPost(post.getContent(), localMediaId, mediaFile, From cadf22b4df7d65b78aa6c3d37ef339dbca0aea22 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 8 Oct 2020 15:53:42 -0300 Subject: [PATCH 096/343] modified comment --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index e7fb88edf057..9d717a4bef5d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -96,7 +96,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( ) // now look for a Story in the StoryRepository that has all these frames and, if not found, let's - // just build the Story object ourselves to keep these files arrangement + // just build the Story object ourselves to match the order in which the media files were passed. var storyIndex = storyRepositoryWrapper.findStoryContainingStoryFrameItemsByIds(mediaIds) if (storyIndex == StoryRepository.DEFAULT_NONE_SELECTED) { // the StoryRepository didn't have it but we have editable serialized slides so, From 62fec15aded73be7233ca1b13fdc3cb06122e7e9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 8 Oct 2020 16:20:33 -0300 Subject: [PATCH 097/343] adding specific error dialogs in case we dont find the backing media for a given story slide so to make it editable --- .../android/ui/posts/EditPostActivity.java | 38 +++++++++++++++---- WordPress/src/main/res/values/strings.xml | 4 +- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 168f3ad232c9..65f156da84ca 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -398,6 +398,8 @@ enum RestartEditorOptions { private SiteSettingsInterface mSiteSettings; private boolean mIsJetpackSsoEnabled; + private boolean mNetworkErrorOnLastMediaFetchAttempt = false; + public static boolean checkToRestart(@NonNull Intent data) { return data.hasExtra(EditPostActivity.EXTRA_RESTART_EDITOR) && RestartEditorOptions.valueOf(data.getStringExtra(EditPostActivity.EXTRA_RESTART_EDITOR)) @@ -3075,14 +3077,30 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } else { // unfortunately we couldn't even load the remote media Ids indicated by the StoryBlock so we can't allow // editing at this time :( - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); - builder.setMessage(getString(R.string.dialog_edit_story_unavailable_message)); - builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { - dialog.dismiss(); - }); - AlertDialog dialog = builder.create(); - dialog.show(); + if (mNetworkErrorOnLastMediaFetchAttempt) { + // there was an error fetching media when we were loading the editor, + // we *may* still have a possibility, tell the user they may try refreshing the media again + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); + builder.setMessage(getString(R.string.dialog_edit_story_unavailable_message)); + builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { + // try another fetchMedia request + fetchMediaList(); + dialog.dismiss(); + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } else { + // unrecoverable error, nothing we can do, inform the user :(. + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.dialog_edit_story_unrecoverable_title)); + builder.setMessage(getString(R.string.dialog_edit_story_unrecoverable_message)); + builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { + dialog.dismiss(); + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } } } @@ -3123,6 +3141,9 @@ public void onMediaUploaded(OnMediaUploaded event) { public void onMediaListFetched(OnMediaListFetched event) { // no op - we don't need to check anything just now, but declaring the method so it's // clear we make a request to FetchMedia in this class. + if (event != null) { + mNetworkErrorOnLastMediaFetchAttempt = event.isError(); + } } @SuppressWarnings("unused") @@ -3253,6 +3274,7 @@ private void refreshEditorTheme() { private void fetchMediaList() { // do not refresh if there is no network if (!NetworkUtils.isNetworkAvailable(this)) { + mNetworkErrorOnLastMediaFetchAttempt = true; return; } FetchMediaListPayload payload = diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index aa8a9915c1cd..5a4ed6dda55a 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2886,7 +2886,9 @@ Limited Story Editing This story was edited on a different device and the ability to edit certain objects may be limited. Can\'t edit Story - This story was created on a different device and can\'t be edited at this moment. + Unable to load media for this story. Check your internet connection and try again in a moment. + Can\'t edit Story + We couldn\'t find the media for your story on this site. Capture Flip camera Flash From 1e5e84d6e753000f216d961fa829eb323e3dcab6 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 8 Oct 2020 17:54:35 -0300 Subject: [PATCH 098/343] fixed method signature --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index b438e9b7fa54..f53f4a94915b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.stories -import android.content.Context import com.google.gson.Gson import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.ui.posts.EditPostRepository @@ -135,7 +134,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( postModel.setContent(content) } - fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(context: Context, postModel: PostModel, mediaFile: MediaFile) { + fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(postModel: PostModel, mediaFile: MediaFile) { val gson = Gson() findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( postModel, From 437c44b9500328b15976834a97639f293f0d1c91 Mon Sep 17 00:00:00 2001 From: mzorz Date: Thu, 8 Oct 2020 22:47:56 -0300 Subject: [PATCH 099/343] Update WordPress/src/main/res/values/strings.xml Co-authored-by: Alex --- WordPress/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 5a4ed6dda55a..017e32f35f7e 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2888,7 +2888,7 @@ Can\'t edit Story Unable to load media for this story. Check your internet connection and try again in a moment. Can\'t edit Story - We couldn\'t find the media for your story on this site. + We couldn\'t find the media for this story on the site. Capture Flip camera Flash From 13e5bfd2ca54ff0635dabe11769dc847dddb8f7f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 9 Oct 2020 10:25:02 -0300 Subject: [PATCH 100/343] removed comment --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 65f156da84ca..71e5b1e78b1d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3139,8 +3139,6 @@ public void onMediaUploaded(OnMediaUploaded event) { @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onMediaListFetched(OnMediaListFetched event) { - // no op - we don't need to check anything just now, but declaring the method so it's - // clear we make a request to FetchMedia in this class. if (event != null) { mNetworkErrorOnLastMediaFetchAttempt = event.isError(); } From f74f63bcca376a0fe6d5754577d83f4ccc578427 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 9 Oct 2020 13:23:27 -0300 Subject: [PATCH 101/343] udpated gutenberg-mobile and submodules --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 33f2addaab7f..0f8916f5a4dc 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 33f2addaab7f5338f791d968680cb6f1e97b4ffa +Subproject commit 0f8916f5a4dccd263cfbecfb88e1a6df61cf09f2 From 0d193db0a15985d69e5b477889fadbcaa598bc17 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 9 Oct 2020 15:41:15 -0300 Subject: [PATCH 102/343] updated gutenberg-mobile hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 0f8916f5a4dc..7c7430cf0261 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 0f8916f5a4dccd263cfbecfb88e1a6df61cf09f2 +Subproject commit 7c7430cf026148db0bfea6392cf938f27c43e237 From 1f91b0f1991857004aacd9ebbe368384035a6fe4 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 9 Oct 2020 15:56:39 -0300 Subject: [PATCH 103/343] updated gutenberg-mobile hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 7c7430cf0261..6360f4c43299 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 7c7430cf026148db0bfea6392cf938f27c43e237 +Subproject commit 6360f4c43299fcac464a775196a8e7627d47fa81 From 78ed9dba6033b99b73899dc32e60e4046d5cb8d4 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 10 Oct 2020 11:50:05 -0300 Subject: [PATCH 104/343] updated gutenberg mobile commit hash to fix initial html data setup --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 6360f4c43299..aa1b58b4d8ca 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 6360f4c43299fcac464a775196a8e7627d47fa81 +Subproject commit aa1b58b4d8cae446cf8a84542709b0fde1a40fc2 From 670fb0810376b9356e90381a636b50a6fc37bba0 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 10 Oct 2020 11:59:35 -0300 Subject: [PATCH 105/343] updated gutenberg mobile hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 8eb45016dac8..445bdec90982 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 8eb45016dac8b3c3a478fbfef52cd2f74e2406fe +Subproject commit 445bdec909822945a2cc33cf3c193dfe7d7e5bc9 From f3c949e6fd2b9b8c6d5a6c2e45062c5b2f2a0520 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 10 Oct 2020 13:28:25 -0300 Subject: [PATCH 106/343] using new method names for gutetnberg-mobile bridge --- .../gutenberg/GutenbergContainerFragment.java | 12 ++++++------ .../gutenberg/GutenbergEditorFragment.java | 17 ++++++++++------- libs/gutenberg-mobile | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index b38465c240dc..36e657ba4234 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -23,12 +23,12 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidSendButtonPressedActionListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnImageFullscreenPreviewListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnLogGutenbergUserEventListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaSavingQueryListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaUploadQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaEditorListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStorySavingReattachQueryListener; import java.util.ArrayList; @@ -54,8 +54,8 @@ public boolean hasReceivedAnyContent() { } public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onMediaLibraryButtonListener, - OnReattachQueryListener onReattachQueryListener, - OnStorySavingReattachQueryListener onStorySavingReattachQueryListener, + OnReattachMediaUploadQueryListener onReattachQueryListener, + OnReattachMediaSavingQueryListener onStorySavingReattachQueryListener, OnEditorMountListener onEditorMountListener, OnEditorAutosaveListener onEditorAutosaveListener, OnAuthHeaderRequestedListener onAuthHeaderRequestedListener, @@ -214,7 +214,7 @@ public void replaceUnsupportedBlock(String content, String blockId) { } public void replaceStoryEditedBlock(String mediaFiles, String blockId) { - mWPAndroidGlueCode.replaceStoryEditedBlock(mediaFiles, blockId); + mWPAndroidGlueCode.replaceMediaFilesEditedBlock(mediaFiles, blockId); } public void updateTheme(Bundle editorTheme) { @@ -243,7 +243,7 @@ public void mediaFileSaveSucceeded(final String mediaId, final String mediaUrl) } public void onStorySaveResult(final String storyFirstMediaId, final boolean success) { - mWPAndroidGlueCode.storySaveResult(storyFirstMediaId, success); + mWPAndroidGlueCode.mediaCollectionFinalSaveResult(storyFirstMediaId, success); } public void onMediaModelCreatedForFile(String oldId, String newId, String oldUrl) { diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index c34b07ff7f6f..ded2ebc514bf 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -60,11 +60,11 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidRequestUnsupportedBlockFallbackListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidSendButtonPressedActionListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnLogGutenbergUserEventListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaSavingQueryListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaUploadQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStorySavingReattachQueryListener; import java.util.ArrayList; import java.util.HashMap; @@ -265,15 +265,15 @@ public void onOtherMediaButtonClicked(String mediaSource, boolean allowMultipleS } } }, - new OnReattachQueryListener() { + new OnReattachMediaUploadQueryListener() { @Override public void onQueryCurrentProgressForUploadingMedia() { updateFailedMediaState(); updateMediaProgress(); } }, - new OnStorySavingReattachQueryListener() { - @Override public void onQueryCurrentProgressForStoryMediaSaving() { + new OnReattachMediaSavingQueryListener() { + @Override public void onQueryCurrentProgressForSavingMedia() { // TODO: probably go through mFailedMediaIds, and see if any block in the post content // has these mediaFIleIds. If there's a match, mark such a block in FAILED state. updateFailedMediaState(); @@ -512,8 +512,11 @@ private void updateFailedMediaState() { private void updateMediaProgress() { for (String mediaId : mUploadingMediaProgressMax.keySet()) { - getGutenbergContainerFragment().mediaFileUploadProgress(Integer.valueOf(mediaId), - mUploadingMediaProgressMax.get(mediaId)); + // upload progress should work on numeric mediaIds only + if (!TextUtils.isEmpty(mediaId) && TextUtils.isDigitsOnly(mediaId)) { + getGutenbergContainerFragment().mediaFileUploadProgress(Integer.valueOf(mediaId), + mUploadingMediaProgressMax.get(mediaId)); + } } } diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 445bdec90982..5e873998f92d 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 445bdec909822945a2cc33cf3c193dfe7d7e5bc9 +Subproject commit 5e873998f92dd361dedc538fd5c0f5a9031c2166 From 69b1aa2a0daf5bb04af426bd766a30be97012dbc Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 10 Oct 2020 13:33:30 -0300 Subject: [PATCH 107/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 5e873998f92d..c6319697105b 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 5e873998f92dd361dedc538fd5c0f5a9031c2166 +Subproject commit c6319697105b55fae3581fa802ef7c277684aa98 From f7c9657f00f6122ebd10a2c7370ae4253ebd4f66 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 10 Oct 2020 20:05:22 -0300 Subject: [PATCH 108/343] renamed bridge methods to more generic onMediaFilesEditorLoadRequestListener --- .../editor/gutenberg/GutenbergContainerFragment.java | 6 +++--- .../android/editor/gutenberg/GutenbergEditorFragment.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 36e657ba4234..9e37c239742e 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -28,7 +28,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaEditorListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaFilesEditorLoadRequestListener; import java.util.ArrayList; @@ -69,7 +69,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidSendButtonPressedActionListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onSPTTooltipShownEventListener, - OnStoryCreatorLoadRequestListener onStoryCreatorRequestListener, + OnMediaFilesEditorLoadRequestListener onMediaFilesEditorLoadRequestListener, boolean isDarkMode) { mWPAndroidGlueCode.attachToContainer( viewGroup, @@ -87,7 +87,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidSendButtonPressedActionListener, addMentionUtil, onSPTTooltipShownEventListener, - onStoryCreatorRequestListener, + onMediaFilesEditorLoadRequestListener, isDarkMode); } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index ded2ebc514bf..c511f9362eda 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -64,7 +64,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaUploadQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStoryCreatorLoadRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaFilesEditorLoadRequestListener; import java.util.ArrayList; import java.util.HashMap; @@ -343,8 +343,8 @@ public boolean onRequestStarterPageTemplatesTooltipShown() { return mEditorFragmentListener.onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); } }, - new OnStoryCreatorLoadRequestListener() { - @Override public void onRequestStoryCreatorLoad(ArrayList mediaFiles, String blockId) { + new OnMediaFilesEditorLoadRequestListener() { + @Override public void onRequestMediaFilesEditorLoad(ArrayList mediaFiles, String blockId) { mEditorFragmentListener.onStoryComposerLoadRequested(mediaFiles, blockId); } }, From 864317f7a355790e7c0d8a548273f5c036e91d1a Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 10 Oct 2020 20:06:51 -0300 Subject: [PATCH 109/343] updated gutenberg-mobile commit hash with bridge method renames --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index c6319697105b..e8aea4af51d5 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit c6319697105b55fae3581fa802ef7c277684aa98 +Subproject commit e8aea4af51d58d1a5d4941414d550dd411161394 From 88fdf9df946f7500086f94c98b4d0853df211bb6 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sun, 11 Oct 2020 07:57:17 -0300 Subject: [PATCH 110/343] updated commit hash for gutenberg-mobile and stories-android, minor linter fixes --- libs/gutenberg-mobile | 2 +- libs/stories-android | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index e8aea4af51d5..be942f006cef 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit e8aea4af51d58d1a5d4941414d550dd411161394 +Subproject commit be942f006ceffb9850044ba6ad04f299a63e275d diff --git a/libs/stories-android b/libs/stories-android index 7966a7c7364c..2395566a4470 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 7966a7c7364c347170ba6986c8259018d69f9009 +Subproject commit 2395566a4470d6751bdb547bd231f9fdb31f2ca0 From 745a1c71ce6d55515740746fd877be8289827701 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sun, 11 Oct 2020 08:04:31 -0300 Subject: [PATCH 111/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index be942f006cef..e4b907c3e9a9 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit be942f006ceffb9850044ba6ad04f299a63e275d +Subproject commit e4b907c3e9a971c8e0021e310ba4b86dcdb92076 From 8e415babbf281f77d11b057b8e01d3145ea78aa3 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sun, 11 Oct 2020 10:46:12 -0300 Subject: [PATCH 112/343] wired in storySaveMediaListener.onStorySaveResult() correctly --- .../android/ui/posts/editor/StoriesEventListener.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 4a2045aae724..15e023817247 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -137,6 +137,7 @@ class StoriesEventListener @Inject constructor( // we will send the Failed signal once all the Story frames have been processed val progress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress(event.storyIndex, 0.0f) storySaveMediaListener?.onMediaSaveReattached(localMediaId, progress) + // storySaveMediaListener?.onMediaSaveFailed(localMediaId) } @Subscribe(threadMode = ThreadMode.MAIN) @@ -145,14 +146,10 @@ class StoriesEventListener @Inject constructor( return } val story = storyRepositoryWrapper.getStoryAtIndex(event.storyIndex) - if (event.isSuccess() && event.frameSaveResult.size == story.frames.size) { + if (event.frameSaveResult.size == story.frames.size) { // take the first frame IDs and mediaUri val localMediaId = story.frames[0].id.toString() - val mediaUrl: String = Uri.fromFile(story.frames[0].composedFrameFile).toString() - storySaveMediaListener?.onMediaSaveSucceeded(localMediaId, mediaUrl) - } else { - val localMediaId = story.frames[0].id.toString() - storySaveMediaListener?.onMediaSaveFailed(localMediaId) + storySaveMediaListener?.onStorySaveResult(localMediaId, event.isSuccess()) } } } From 477f23139954f28f7cb0bcaa4523751ce6c93a62 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sun, 11 Oct 2020 10:46:55 -0300 Subject: [PATCH 113/343] cover the case for signaling temporal ids in media file save progress and failure --- .../editor/gutenberg/GutenbergEditorFragment.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index c511f9362eda..c81f7fdb9606 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -506,7 +506,12 @@ public void resetUploadingMediaToFailed(Set failedMediaIds) { private void updateFailedMediaState() { for (String mediaId : mFailedMediaIds) { - getGutenbergContainerFragment().mediaFileUploadFailed(Integer.valueOf(mediaId)); + // upload progress should work on numeric mediaIds only + if (!TextUtils.isEmpty(mediaId) && TextUtils.isDigitsOnly(mediaId)) { + getGutenbergContainerFragment().mediaFileUploadFailed(Integer.valueOf(mediaId)); + } else { + getGutenbergContainerFragment().mediaFileSaveFailed(mediaId); + } } } @@ -516,6 +521,9 @@ private void updateMediaProgress() { if (!TextUtils.isEmpty(mediaId) && TextUtils.isDigitsOnly(mediaId)) { getGutenbergContainerFragment().mediaFileUploadProgress(Integer.valueOf(mediaId), mUploadingMediaProgressMax.get(mediaId)); + } else { + getGutenbergContainerFragment().mediaFileSaveProgress(mediaId, + mUploadingMediaProgressMax.get(mediaId)); } } } From dc429da468ac567b5251fb368a53827b7bf68b68 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sun, 11 Oct 2020 11:00:25 -0300 Subject: [PATCH 114/343] updated gutenberg-mobile commit hash for error handling WIP --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index e4b907c3e9a9..90f84ac9da86 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit e4b907c3e9a971c8e0021e310ba4b86dcdb92076 +Subproject commit 90f84ac9da86e576232508e83c8efed8cbeb07b9 From 823404cfd8ea5032a6646598c0788369bc903fcf Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 10:16:07 -0300 Subject: [PATCH 115/343] added upload retry and cancel bridge methods for mediaFiles collection based block Story block --- .../android/ui/posts/EditPostActivity.java | 57 ++++++++++++++++ WordPress/src/main/res/values/strings.xml | 1 + .../editor/EditorFragmentAbstract.java | 4 +- .../gutenberg/GutenbergContainerFragment.java | 6 +- .../gutenberg/GutenbergEditorFragment.java | 67 ++++++++++++++++++- libs/gutenberg-mobile | 2 +- 6 files changed, 130 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 174fce50cd68..36cb23e11d5a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3165,6 +3165,63 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } } + @Override public void onRetryUploadForMediaCollection(ArrayList mediaFiles) { + ArrayList mediaIdsToRetry = new ArrayList<>(); + for (Object mediaFile : mediaFiles) { + int localMediaId + = StringUtils.stringToInt(((HashMap) mediaFile).toString(), 0); + if (localMediaId != 0) { + MediaModel media = mMediaStore.getMediaWithLocalId(localMediaId); + // if we find at least one item in the mediaFiles collection passed + // for which we don't have a local MediaModel, just tell the user and bail + if (media == null) { + AppLog.e(T.MEDIA, "Can't find media with local id: " + localMediaId); + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.cannot_retry_deleted_media_item_fatal)); + builder.setPositiveButton(R.string.yes, (dialog, id) -> { + dialog.dismiss(); + }); + + builder.setNegativeButton(getString(R.string.no), (dialog, id) -> dialog.dismiss()); + + AlertDialog dialog = builder.create(); + dialog.show(); + + return; + } + + if (media.getUrl() != null && media.getUploadState().equals(MediaUploadState.UPLOADED.toString())) { + // Note: we should actually do this when the editor fragment starts instead of waiting for user input. + // Notify the editor fragment upload was successful and it should replace the local url by the remote url. + if (mEditorMediaUploadListener != null) { + mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), + FluxCUtils.mediaFileFromMediaModel(media)); + } + } else { + UploadService.cancelFinalNotification(this, mEditPostRepository.getPost()); + UploadService.cancelFinalNotificationForMedia(this, mSite); + mediaIdsToRetry.add(localMediaId); + } + } + } + + if (!mediaIdsToRetry.isEmpty()) { + mEditorMedia.retryFailedMediaAsync(mediaIdsToRetry); + } + AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED); + } + + @Override public void onCancelUploadForMediaCollection(ArrayList mediaFiles) { + // just cancel upload for each media + for (Object mediaFile : mediaFiles) { + int localMediaId + = StringUtils.stringToInt(((HashMap) mediaFile).get("id").toString(), 0); + if (localMediaId != 0) { + mEditorMedia.cancelMediaUploadAsync(localMediaId, false); + } + } + } + // FluxC events @SuppressWarnings("unused") diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 502d7f9a0212..ff075553c4da 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -205,6 +205,7 @@ Some media can\'t be deleted at this time. Try again later. Media has been removed. Delete it from this post? + Media has been removed. Try editing your Story. You don\'t have any media No media matching your search You don\'t have any images diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java index 277e0e3bf4b6..11bd1dfe3fff 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java @@ -206,8 +206,10 @@ public interface EditorFragmentListener { void onGutenbergEditorSetStarterPageTemplatesTooltipShown(boolean tooltipShown); boolean onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); String getErrorMessageFromMedia(int mediaId); - void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId); void showJetpackSettings(); + void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId); + void onRetryUploadForMediaCollection(ArrayList mediaFiles); + void onCancelUploadForMediaCollection(ArrayList mediaFiles); } /** diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 9e37c239742e..4bed0d26769f 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -28,7 +28,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaEditorListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaFilesEditorLoadRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaFilesCollectionBasedBlockEditorListener; import java.util.ArrayList; @@ -69,7 +69,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidSendButtonPressedActionListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onSPTTooltipShownEventListener, - OnMediaFilesEditorLoadRequestListener onMediaFilesEditorLoadRequestListener, + OnMediaFilesCollectionBasedBlockEditorListener onMediaFilesCollectionBasedBlockEditorListener, boolean isDarkMode) { mWPAndroidGlueCode.attachToContainer( viewGroup, @@ -87,7 +87,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidSendButtonPressedActionListener, addMentionUtil, onSPTTooltipShownEventListener, - onMediaFilesEditorLoadRequestListener, + onMediaFilesCollectionBasedBlockEditorListener, isDarkMode); } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index c81f7fdb9606..9202ef2361ed 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -46,6 +46,7 @@ import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.PermissionUtils; import org.wordpress.android.util.ProfilingUtils; +import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.helpers.MediaFile; import org.wordpress.android.util.helpers.MediaGallery; @@ -64,7 +65,7 @@ import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaUploadQueryListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnStarterPageTemplatesTooltipShownEventListener; import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener; -import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaFilesEditorLoadRequestListener; +import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaFilesCollectionBasedBlockEditorListener; import java.util.ArrayList; import java.util.HashMap; @@ -343,10 +344,18 @@ public boolean onRequestStarterPageTemplatesTooltipShown() { return mEditorFragmentListener.onGutenbergEditorRequestStarterPageTemplatesTooltipShown(); } }, - new OnMediaFilesEditorLoadRequestListener() { + new OnMediaFilesCollectionBasedBlockEditorListener() { @Override public void onRequestMediaFilesEditorLoad(ArrayList mediaFiles, String blockId) { mEditorFragmentListener.onStoryComposerLoadRequested(mediaFiles, blockId); } + + @Override public void onCancelUploadForMediaCollection(ArrayList mediaFiles) { + showCancelMediaCollectionUploadDialog(mediaFiles); + } + + @Override public void onRetryUploadForMediaCollection(ArrayList mediaFiles) { + showRetryMediaCollectionUploadDialog(mediaFiles); + } }, GutenbergUtils.isDarkMode(getActivity())); @@ -610,6 +619,60 @@ public void onClick(DialogInterface dialog, int id) { dialog.show(); } + private void showCancelMediaCollectionUploadDialog(ArrayList mediaFiles) { + // Display 'cancel upload' dialog + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(getActivity()); + builder.setTitle(getString(R.string.stop_upload_dialog_title)); + builder.setPositiveButton(R.string.stop_upload_dialog_button_yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mEditorFragmentListener.onCancelUploadForMediaCollection(mediaFiles); + // now signal Gutenberg upload failed, and remove the mediaIds from our tracking map + for (Object mediaFile : mediaFiles) { + // this conversion is needed to strip off decimals that can come from RN when using int as + // string + int localMediaId + = StringUtils.stringToInt( + ((HashMap) mediaFile).get("id").toString(), 0); + getGutenbergContainerFragment().mediaFileUploadFailed(localMediaId); + mUploadingMediaProgressMax.remove(localMediaId); + } + dialog.dismiss(); + } + }); + + builder.setNegativeButton(R.string.stop_upload_dialog_button_no, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void showRetryMediaCollectionUploadDialog(ArrayList mediaFiles) { + // Display 'retry upload' dialog + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(getActivity()); + builder.setTitle(getString(R.string.retry_failed_upload_title)); + builder.setPositiveButton(R.string.retry_failed_upload_yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mEditorFragmentListener.onRetryUploadForMediaCollection(mediaFiles); + dialog.dismiss(); + } + }); + + builder.setNegativeButton(R.string.retry_failed_upload_remove, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + private void showImplicitKeyboard() { InputMethodManager keyboard = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); keyboard.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 90f84ac9da86..246d3a706ccd 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 90f84ac9da86e576232508e83c8efed8cbeb07b9 +Subproject commit 246d3a706ccdbba266710ba5175dd461d39c18c7 From 02defebfa1efbbe48ae9a074a8d40fa62a51b185 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 10:58:47 -0300 Subject: [PATCH 116/343] updated commit hash for gutenberg-mobile, deep copy of mediaFiles when udpating id or url --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 246d3a706ccd..4a09adc333c2 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 246d3a706ccdbba266710ba5175dd461d39c18c7 +Subproject commit 4a09adc333c25d34edebfc9a059d9249df5ac492 From f69413d0328dd03bf700805265dfc1f6afb47f26 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 12:17:38 -0300 Subject: [PATCH 117/343] implemented requestMediaFilesSaveCancelDialog bridge method, just showing message that saving cannot be cancelled --- .../android/ui/posts/EditPostActivity.java | 10 ++++++++++ .../editor/EditorFragmentAbstract.java | 1 + .../gutenberg/GutenbergEditorFragment.java | 20 +++++++++++++++++++ .../src/main/res/values/strings.xml | 4 ++++ libs/gutenberg-mobile | 2 +- 5 files changed, 36 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 36cb23e11d5a..92a624c6550c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3222,6 +3222,16 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } } + @Override public void onCancelSaveForMediaCollection(ArrayList mediaFiles) { + // just cancel upload for each media + for (Object mediaFile : mediaFiles) { + int localMediaId + = StringUtils.stringToInt(((HashMap) mediaFile).get("id").toString(), 0); + if (localMediaId != 0) { + } + } + } + // FluxC events @SuppressWarnings("unused") diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java index 11bd1dfe3fff..5230c67e3eff 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java @@ -210,6 +210,7 @@ public interface EditorFragmentListener { void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId); void onRetryUploadForMediaCollection(ArrayList mediaFiles); void onCancelUploadForMediaCollection(ArrayList mediaFiles); + void onCancelSaveForMediaCollection(ArrayList mediaFiles); } /** diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 9202ef2361ed..bb8431e150e1 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -356,6 +356,10 @@ public boolean onRequestStarterPageTemplatesTooltipShown() { @Override public void onRetryUploadForMediaCollection(ArrayList mediaFiles) { showRetryMediaCollectionUploadDialog(mediaFiles); } + + @Override public void onCancelSaveForMediaCollection(ArrayList mediaFiles) { + showCancelMediaCollectionSaveDialog(mediaFiles); + } }, GutenbergUtils.isDarkMode(getActivity())); @@ -673,6 +677,22 @@ public void onClick(DialogInterface dialog, int id) { dialog.show(); } + private void showCancelMediaCollectionSaveDialog(ArrayList mediaFiles) { + // Display 'cancel upload' dialog + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(getActivity()); + builder.setTitle(getString(R.string.stop_save_dialog_title)); + builder.setMessage(getString(R.string.stop_save_dialog_message)); + builder.setPositiveButton(R.string.stop_save_dialog_ok_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + private void showImplicitKeyboard() { InputMethodManager keyboard = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); keyboard.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); diff --git a/libs/editor/WordPressEditor/src/main/res/values/strings.xml b/libs/editor/WordPressEditor/src/main/res/values/strings.xml index 3473aa1ea6c1..5f0296797549 100644 --- a/libs/editor/WordPressEditor/src/main/res/values/strings.xml +++ b/libs/editor/WordPressEditor/src/main/res/values/strings.xml @@ -20,6 +20,10 @@ Stop uploading? Can\'t stop the upload because it\'s already finished + Files saving + Please wait until all files have been saved + OK + bold italic blockquote diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 4a09adc333c2..8f30bdf272b8 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 4a09adc333c25d34edebfc9a059d9249df5ac492 +Subproject commit 8f30bdf272b8cfdd67531690ab3222762a688ef3 From a7451cd86234963e8c254ee9a16b34670c0d562b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 12:40:26 -0300 Subject: [PATCH 118/343] fixed lint errors, and added TODO for cancellation (not going to implement it right now) --- .../org/wordpress/android/ui/posts/EditPostActivity.java | 8 +------- .../editor/gutenberg/GutenbergContainerFragment.java | 3 ++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 92a624c6550c..ad36534bde65 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3223,13 +3223,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onCancelSaveForMediaCollection(ArrayList mediaFiles) { - // just cancel upload for each media - for (Object mediaFile : mediaFiles) { - int localMediaId - = StringUtils.stringToInt(((HashMap) mediaFile).get("id").toString(), 0); - if (localMediaId != 0) { - } - } + // TODO implement cancelling save process for media collection } // FluxC events diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 4bed0d26769f..10c9daae3784 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -69,7 +69,8 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener onGutenbergDidSendButtonPressedActionListener, AddMentionUtil addMentionUtil, OnStarterPageTemplatesTooltipShownEventListener onSPTTooltipShownEventListener, - OnMediaFilesCollectionBasedBlockEditorListener onMediaFilesCollectionBasedBlockEditorListener, + OnMediaFilesCollectionBasedBlockEditorListener + onMediaFilesCollectionBasedBlockEditorListener, boolean isDarkMode) { mWPAndroidGlueCode.attachToContainer( viewGroup, From 33bf90d3c0c7a077e874d1adbf23f22ab2c65cdc Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 12:55:36 -0300 Subject: [PATCH 119/343] removed unnecessary toInt conversion call --- .../android/ui/stories/media/StoryMediaSaveUploadBridge.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 7507f4431069..8b02052d01e6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -147,7 +147,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( it.localSiteId.toLong(), // use the local id to save the original, will be replaced later // with mediaModel.mediaId after uploading to the remote site - LocalId(it.id.toInt()), + LocalId(it.id), frame ) From e0f364004adda3a2e9cf83450f9ecabcf163dbc9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 12:56:51 -0300 Subject: [PATCH 120/343] broke comment lines to avoid exceeding 120 chars max length --- .../org/wordpress/android/ui/posts/EditPostActivity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index ad36534bde65..9400070cabc2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3191,8 +3191,10 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } if (media.getUrl() != null && media.getUploadState().equals(MediaUploadState.UPLOADED.toString())) { - // Note: we should actually do this when the editor fragment starts instead of waiting for user input. - // Notify the editor fragment upload was successful and it should replace the local url by the remote url. + // Note: we should actually do this when the editor fragment starts instead of waiting for user + // input. + // Notify the editor fragment upload was successful and it should replace the local url by the + // remote url. if (mEditorMediaUploadListener != null) { mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), FluxCUtils.mediaFileFromMediaModel(media)); From 1039293674c52e9f81683167c0096098561f700b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 13:18:57 -0300 Subject: [PATCH 121/343] removed unnecessary toInt() conversion --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index f53f4a94915b..71868421d4de 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -156,7 +156,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( // look for the slide saved with the local id key (mediaFile.id), and re-convert to // mediaId. storiesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( - mediaFile.id.toInt(), + mediaFile.id, mediaFile.mediaId.toLong(), postModel.localSiteId.toLong() ) From f8b3dea41e6181004b580850e1a913430632b1df Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 12 Oct 2020 13:19:13 -0300 Subject: [PATCH 122/343] clarified comment --- .../wordpress/android/ui/posts/editor/StoriesEventListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 15e023817247..51c0bb99eb8c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -134,7 +134,7 @@ class StoriesEventListener @Inject constructor( } val localMediaId = event.frameId.toString() // just update progress, we may have still some other frames in this story that need be saved. - // we will send the Failed signal once all the Story frames have been processed + // we will send the Failed signal once all the Story frames have been processed (see onStorySaveProcessFinished) val progress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress(event.storyIndex, 0.0f) storySaveMediaListener?.onMediaSaveReattached(localMediaId, progress) // storySaveMediaListener?.onMediaSaveFailed(localMediaId) From 471d7f407b3d6b9292a23d1c562af17248b12761 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 13 Oct 2020 09:06:28 -0300 Subject: [PATCH 123/343] updated bridge method names from mediaModelCreatedForFile to mediaIdChanged --- .../wordpress/android/ui/posts/editor/StoriesEventListener.kt | 4 ++-- .../android/ui/stories/media/StoryMediaSaveUploadBridge.kt | 4 ++-- .../android/editor/gutenberg/GutenbergContainerFragment.java | 2 +- libs/gutenberg-mobile | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 51c0bb99eb8c..4b1f86649993 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -20,7 +20,7 @@ import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper -import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent +import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaIdChangeEvent import org.wordpress.android.util.FluxCUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -120,7 +120,7 @@ class StoriesEventListener @Inject constructor( } @Subscribe(threadMode = ThreadMode.MAIN) - fun onStoryFrameMediaModelCreated(event: StoryFrameMediaModelCreatedEvent) { + fun onStoryFrameMediaIdChanged(event: StoryFrameMediaIdChangeEvent) { if (!lifecycle.currentState.isAtLeast(CREATED)) { return } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 8b02052d01e6..1b8c3f521648 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -156,7 +156,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( if (isEditMode) { // finally send the event that this frameId has changed EventBus.getDefault().post( - StoryFrameMediaModelCreatedEvent( + StoryFrameMediaIdChangeEvent( oldTemporaryId, it.id.toString(), oldUri.toString() @@ -239,5 +239,5 @@ class StoryMediaSaveUploadBridge @Inject constructor( event.frameSaveResult.size == storyRepositoryWrapper.getStoryAtIndex(event.storyIndex).frames.size) } - data class StoryFrameMediaModelCreatedEvent(val oldId: String, val newId: String, val oldUrl: String) + data class StoryFrameMediaIdChangeEvent(val oldId: String, val newId: String, val oldUrl: String) } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index 10c9daae3784..a691c1b8b3b8 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -248,6 +248,6 @@ public void onStorySaveResult(final String storyFirstMediaId, final boolean succ } public void onMediaModelCreatedForFile(String oldId, String newId, String oldUrl) { - mWPAndroidGlueCode.mediaModelCreatedForFile(oldId, newId, oldUrl); + mWPAndroidGlueCode.mediaIdChanged(oldId, newId, oldUrl); } } diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 8f30bdf272b8..433e2cb33c22 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 8f30bdf272b8cfdd67531690ab3222762a688ef3 +Subproject commit 433e2cb33c22cefcb8aadfcc390d2cd720e61188 From 032a7415fd61661d5a63f02a2764172c10757f8d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 13 Oct 2020 10:03:07 -0300 Subject: [PATCH 124/343] rolled back rename of event class StoryFrameMediaModelCreatedEvent, it better represents the changes on the WPAndroid side of things (was changed before to the more generic StoryFrameMediaIdChangeEvent which is used in the bridge) --- .../wordpress/android/ui/posts/editor/StoriesEventListener.kt | 4 ++-- .../android/ui/stories/media/StoryMediaSaveUploadBridge.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 4b1f86649993..adf8ba3aa624 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -20,7 +20,7 @@ import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper -import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaIdChangeEvent +import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent import org.wordpress.android.util.FluxCUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -120,7 +120,7 @@ class StoriesEventListener @Inject constructor( } @Subscribe(threadMode = ThreadMode.MAIN) - fun onStoryFrameMediaIdChanged(event: StoryFrameMediaIdChangeEvent) { + fun onStoryFrameMediaIdChanged(event: StoryFrameMediaModelCreatedEvent) { if (!lifecycle.currentState.isAtLeast(CREATED)) { return } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 1b8c3f521648..8b02052d01e6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -156,7 +156,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( if (isEditMode) { // finally send the event that this frameId has changed EventBus.getDefault().post( - StoryFrameMediaIdChangeEvent( + StoryFrameMediaModelCreatedEvent( oldTemporaryId, it.id.toString(), oldUri.toString() @@ -239,5 +239,5 @@ class StoryMediaSaveUploadBridge @Inject constructor( event.frameSaveResult.size == storyRepositoryWrapper.getStoryAtIndex(event.storyIndex).frames.size) } - data class StoryFrameMediaIdChangeEvent(val oldId: String, val newId: String, val oldUrl: String) + data class StoryFrameMediaModelCreatedEvent(val oldId: String, val newId: String, val oldUrl: String) } From 1bcf38d19179ab2efb4a00026c3a77c065db1d8d Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 13 Oct 2020 16:02:28 -0300 Subject: [PATCH 125/343] built logic to save temporary ids to prefs as well --- .../android/ui/posts/EditPostActivity.java | 2 +- .../ui/posts/editor/StoriesEventListener.kt | 6 +- .../stories/SaveStoryGutenbergBlockUseCase.kt | 33 +++++- .../ui/stories/StoryComposerActivity.kt | 8 +- .../media/StoryMediaSaveUploadBridge.kt | 48 +++++--- .../android/ui/stories/prefs/StoriesPrefs.kt | 109 +++++++++++++++++- .../LoadStoryFromStoriesPrefsUseCase.kt | 8 +- 7 files changed, 192 insertions(+), 22 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 9400070cabc2..9d9028101501 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -1614,7 +1614,7 @@ private void onUploadSuccess(MediaModel media) { // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload // within a story block in this post: we will only replace items for which a local-keyed frame has // been created before, which can only happen when using the Story Creator. - mStoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + mStoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide_Phase2( media.getId(), media.getMediaId(), mSite.getId() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index adf8ba3aa624..3f5245f2a145 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -15,12 +15,15 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.wordpress.android.editor.gutenberg.StorySaveMediaListener import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent +import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId import org.wordpress.android.util.FluxCUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -33,6 +36,7 @@ class StoriesEventListener @Inject constructor( private lateinit var lifecycle: Lifecycle private lateinit var site: SiteModel private var storySaveMediaListener: StorySaveMediaListener? = null + @Inject lateinit var storiesPrefs: StoriesPrefs @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) private fun onCreate() { @@ -124,7 +128,7 @@ class StoriesEventListener @Inject constructor( if (!lifecycle.currentState.isAtLeast(CREATED)) { return } - storySaveMediaListener?.onMediaModelCreatedForFile(event.oldId, event.newId, event.oldUrl) + storySaveMediaListener?.onMediaModelCreatedForFile(event.oldId, event.newId.toString(), event.oldUrl) } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 71868421d4de..118212cf54c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -1,9 +1,14 @@ package org.wordpress.android.ui.stories import com.google.gson.Gson +import com.wordpress.stories.compose.frame.FrameIndex +import com.wordpress.stories.compose.story.StoryFrameItem +import com.wordpress.stories.compose.story.StoryIndex import org.wordpress.android.fluxc.model.PostModel +import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -52,7 +57,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( fun buildMediaFileDataWithTemporaryId(mediaFile: MediaFile, temporaryId: String): StoryMediaFileData { return StoryMediaFileData( alt = "", - id = TEMPORARY_ID_PREFIX + temporaryId, // mediaFile.id, + id = temporaryId, // mediaFile.id, link = StringUtils.notNullStr(mediaFile.fileURL), type = if (mediaFile.isVideo) "video" else "image", mime = StringUtils.notNullStr(mediaFile.mimeType), @@ -68,7 +73,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( ): StoryMediaFileData { return StoryMediaFileData( alt = "", - id = TEMPORARY_ID_PREFIX + temporaryId, // mediaFile.id, + id = temporaryId, // mediaFile.id, link = url, type = if (isVideo) "video" else "image", mime = "", @@ -77,6 +82,10 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( ) } + fun getTempIdForStoryFrame(tempIdBase: Long, storyIndex: StoryIndex, frameIndex: FrameIndex): String { + return TEMPORARY_ID_PREFIX + "$tempIdBase-$storyIndex-$frameIndex" + } + fun cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost(editPostRepository: EditPostRepository) { editPostRepository.update { postModel: PostModel -> val gson = Gson() @@ -155,7 +164,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( // look for the slide saved with the local id key (mediaFile.id), and re-convert to // mediaId. - storiesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + storiesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide_Phase2( mediaFile.id, mediaFile.mediaId.toLong(), postModel.localSiteId.toLong() @@ -170,6 +179,24 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( ) } + fun saveNewLocalFilesToStoriesPrefsTempSlides(site: SiteModel, storyIndex: StoryIndex, frames: ArrayList) { + for ((frameIndex, frame) in frames.withIndex()) { + if (frame.id == null) { + val assignedTempId = getTempIdForStoryFrame( + storiesPrefs.getNewIncrementalTempId(), + storyIndex, + frameIndex + ) + frame.id = assignedTempId + } + storiesPrefs.saveSlideWithTempId( + site.id.toLong(), + TempId(requireNotNull(frame.id)), // should not be null at this point + frame + ) + } + } + private fun createGBStoryBlockStringFromJson(storyBlock: StoryBlockData): String { val gson = Gson() return HEADING_START + gson.toJson(storyBlock) + HEADING_END + DIV_PART + CLOSING_TAG diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 647e03d92d33..563175bb482e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -509,6 +509,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val storyMediaFileDataList = ArrayList() // holds media files val story = storyRepositoryWrapper.getStoryAtIndex(storyIndex) for ((frameIndex, frame) in story.frames.withIndex()) { + val newTempId = storiesPrefs.getNewIncrementalTempId() + val assignedTempId = saveStoryGutenbergBlockUseCase.getTempIdForStoryFrame( + newTempId, storyIndex, frameIndex + ) when (frame.id) { // if the frame.id is null, this is a new frame that has been added to an edited Story // so, we don't have much information yet. We do have the background source (not the flattened @@ -517,7 +521,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), null -> { val storyMediaFileData = saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryIdNoMediaFile( - temporaryId = "$storyIndex-$frameIndex", + temporaryId = assignedTempId, url = if (frame.source is FileBackgroundSource) { (frame.source as FileBackgroundSource).file.toString() } else { @@ -538,7 +542,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val storyMediaFileData = saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( mediaFile = it, - temporaryId = "$storyIndex-$frameIndex" + temporaryId = assignedTempId ) frame.id = storyMediaFileData.id storyMediaFileDataList.add(storyMediaFileData) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 8b02052d01e6..326c1482126c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.OnLifecycleEvent import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult +import com.wordpress.stories.compose.story.StoryFrameItem import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -32,6 +33,7 @@ import org.wordpress.android.ui.stories.StoriesTrackerHelper import org.wordpress.android.ui.stories.StoryComposerActivity import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId import org.wordpress.android.ui.uploads.UploadServiceFacade import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.NetworkUtilsWrapper @@ -143,7 +145,14 @@ class StoryMediaSaveUploadBridge @Inject constructor( mediaModel?.let { val oldTemporaryId = frame.id ?: "" frame.id = it.id.toString() - storiesPrefs.saveSlideWithLocalId( + + // if prefs has this Slide with the temporary key, replace it + // if not, let's now save the new slide with the local key + storiesPrefs.replaceTempMediaIdKeyedSlideWithLocalMediaIdKeyedSlide_Phase1( + TempId(oldTemporaryId), + LocalId(it.id), + it.localSiteId.toLong() + ) ?: storiesPrefs.saveSlideWithLocalId( it.localSiteId.toLong(), // use the local id to save the original, will be replaced later // with mediaModel.mediaId after uploading to the remote site @@ -158,8 +167,9 @@ class StoryMediaSaveUploadBridge @Inject constructor( EventBus.getDefault().post( StoryFrameMediaModelCreatedEvent( oldTemporaryId, - it.id.toString(), - oldUri.toString() + it.id, + oldUri.toString(), + frame ) ) } @@ -211,15 +221,22 @@ class StoryMediaSaveUploadBridge @Inject constructor( // track event storiesTrackerHelper.trackStorySaveResultEvent(event) - // only trigger the bridge preparation and the UploadService if the Story is now complete - // otherwise we can be receiving successful retry events for individual frames we shouldn't care about just - // yet. - if (isStorySavingComplete(event)) { - // only remove it if it was successful - we want to keep it and show a snackbar once when the user - // comes back to the app if it wasn't, see MySiteFrament for details. - eventBusWrapper.removeStickyEvent(event) - event.metadata?.let { - val site = it.getSerializable(WordPress.SITE) as SiteModel + event.metadata?.let { + val site = it.getSerializable(WordPress.SITE) as SiteModel + val story = storyRepositoryWrapper.getStoryAtIndex(event.storyIndex) + saveStoryGutenbergBlockUseCase.saveNewLocalFilesToStoriesPrefsTempSlides( + site, + event.storyIndex, + story.frames + ) + + // only trigger the bridge preparation and the UploadService if the Story is now complete + // otherwise we can be receiving successful retry events for individual frames we shouldn't care about just + // yet. + if (isStorySavingComplete(event)) { + // only remove it if it was successful - we want to keep it and show a snackbar once when the user + // comes back to the app if it wasn't, see MySiteFrament for details. + eventBusWrapper.removeStickyEvent(event) editPostRepository.loadPostByLocalPostId(it.getInt(StoryComposerActivity.KEY_POST_LOCAL_ID)) if (event.isEditMode) { // we're done using the temporary ids, let's clean mediaFiles attribute from the blocks that have @@ -239,5 +256,10 @@ class StoryMediaSaveUploadBridge @Inject constructor( event.frameSaveResult.size == storyRepositoryWrapper.getStoryAtIndex(event.storyIndex).frames.size) } - data class StoryFrameMediaModelCreatedEvent(val oldId: String, val newId: String, val oldUrl: String) + data class StoryFrameMediaModelCreatedEvent( + val oldId: String, + val newId: Int, + val oldUrl: String, + val frame: StoryFrameItem + ) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index d09d7b3234b4..87ceead46a61 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -1,5 +1,6 @@ package org.wordpress.android.ui.stories.prefs +import android.annotation.SuppressLint import android.content.Context import android.net.Uri import androidx.preference.PreferenceManager @@ -17,7 +18,9 @@ class StoriesPrefs @Inject constructor( private val context: Context ) { companion object { + private val KEY_STORIES_SLIDE_INCREMENTAL_ID = "incremental_id" private val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" + private val KEY_PREFIX_TEMP_MEDIA_ID = "t-" private val KEY_PREFIX_LOCAL_MEDIA_ID = "l-" private val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" } @@ -32,13 +35,60 @@ class StoriesPrefs @Inject constructor( KEY_PREFIX_LOCAL_MEDIA_ID + mediaId.value.toString() } + private fun buildSlideKey(siteId: Long, tempId: TempId): String { + return KEY_PREFIX_STORIES_SLIDE_ID + siteId.toString() + "-" + + KEY_PREFIX_TEMP_MEDIA_ID + tempId.id + } + + @SuppressLint("ApplySharedPref") + @Synchronized + fun getNewIncrementalTempId(): Long { + var currentIncrementalId = getIncrementalTempId() + currentIncrementalId++ + val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() + editor.putLong(KEY_STORIES_SLIDE_INCREMENTAL_ID, currentIncrementalId) + editor.commit() + return currentIncrementalId + } + + fun getIncrementalTempId(): Long { + return PreferenceManager.getDefaultSharedPreferences(context).getLong( + KEY_STORIES_SLIDE_INCREMENTAL_ID, + 0 + ) + } + fun checkSlideIdExists(siteId: Long, mediaId: RemoteId): Boolean { val slideIdKey = buildSlideKey(siteId, mediaId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } + fun checkSlideIdExists(siteId: Long, tempId: TempId): Boolean { + val slideIdKey = buildSlideKey(siteId, tempId) + return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) + } + + fun checkSlideIdExists(siteId: Long, localId: LocalId): Boolean { + val slideIdKey = buildSlideKey(siteId, localId) + return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) + } + fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: RemoteId): Boolean { val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(siteId, mediaId) + return checkSlideOriginalBackgroundMediaExists(storyFrameItem) + } + + fun checkSlideOriginalBackgroundMediaExists(siteId: Long, tempId: TempId): Boolean { + val storyFrameItem: StoryFrameItem? = getSlideWithTempId(siteId, tempId) + return checkSlideOriginalBackgroundMediaExists(storyFrameItem) + } + + fun checkSlideOriginalBackgroundMediaExists(siteId: Long, localId: LocalId): Boolean { + val storyFrameItem: StoryFrameItem? = getSlideWithLocalId(siteId, localId) + return checkSlideOriginalBackgroundMediaExists(storyFrameItem) + } + + private fun checkSlideOriginalBackgroundMediaExists(storyFrameItem: StoryFrameItem?): Boolean { storyFrameItem?.let { frame -> // now check the background media exists or is accessible on this device frame.source.let { source -> @@ -84,6 +134,16 @@ class StoriesPrefs @Inject constructor( checkSlideOriginalBackgroundMediaExists(siteId, mediaId) } + fun isValidSlide(siteId: Long, tempId: TempId): Boolean { + return checkSlideIdExists(siteId, tempId) && + checkSlideOriginalBackgroundMediaExists(siteId, tempId) + } + + fun isValidSlide(siteId: Long, localId: LocalId): Boolean { + return checkSlideIdExists(siteId, localId) && + checkSlideOriginalBackgroundMediaExists(siteId, localId) + } + private fun getSlideJson(slideIdKey: String): String? { return PreferenceManager.getDefaultSharedPreferences(context).getString(slideIdKey, null) } @@ -102,6 +162,18 @@ class StoriesPrefs @Inject constructor( } ?: return null } + fun getSlideWithTempId(siteId: Long, tempId: TempId): StoryFrameItem? { + val jsonSlide = getSlideJson(buildSlideKey(siteId, tempId)) + jsonSlide?.let { + return StorySerializerUtils.deserializeStoryFrameItem(jsonSlide) + } ?: return null + } + + fun saveSlideWithTempId(siteId: Long, tempId: TempId, storyFrameItem: StoryFrameItem) { + val slideIdKey = buildSlideKey(siteId, tempId) + saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) + } + fun saveSlideWithLocalId(siteId: Long, mediaId: LocalId, storyFrameItem: StoryFrameItem) { val slideIdKey = buildSlideKey(siteId, mediaId) saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) @@ -112,6 +184,13 @@ class StoriesPrefs @Inject constructor( saveSlide(slideIdKey, StorySerializerUtils.serializeStoryFrameItem(storyFrameItem)) } + fun deleteSlideWithTempId(siteId: Long, tempId: TempId) { + val slideIdKey = buildSlideKey(siteId, tempId) + val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() + editor.remove(slideIdKey) + editor.apply() + } + fun deleteSlideWithLocalId(siteId: Long, mediaId: LocalId) { val slideIdKey = buildSlideKey(siteId, mediaId) val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() @@ -127,7 +206,7 @@ class StoriesPrefs @Inject constructor( } } - fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( + fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide_Phase2( localIdKey: Int, remoteIdKey: Long, localSiteId: Long @@ -150,4 +229,32 @@ class StoriesPrefs @Inject constructor( ) } } + + fun replaceTempMediaIdKeyedSlideWithLocalMediaIdKeyedSlide_Phase1( + tempId: TempId, + localId: LocalId, + localSiteId: Long + ): StoryFrameItem? { + // look for the slide saved with the local id key (mediaFile.id), and re-convert to mediaId. + getSlideWithTempId( + localSiteId, + tempId + )?.let { + it.id = localId.value.toString() + saveSlideWithLocalId( + localSiteId, + localId, // use the new localId as key + it + ) + // now delete the old entry + deleteSlideWithTempId( + localSiteId, + tempId + ) + return it + } + return null + } + + data class TempId(val id: String) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 08ea59a5d2f0..c993c917f5ea 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -5,12 +5,15 @@ import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryIndex import com.wordpress.stories.compose.story.StoryRepository import dagger.Reusable +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId +import org.wordpress.android.util.StringUtils import java.util.ArrayList import java.util.HashMap import javax.inject.Inject @@ -50,7 +53,10 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { - if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong()))) { + // if this is not a remote nor a local / temporary slide, return false + if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong())) + && !storiesPrefs.isValidSlide(site.id.toLong(), LocalId(StringUtils.stringToInt(mediaId))) + && !storiesPrefs.isValidSlide(site.id.toLong(), TempId(mediaId))) { return false } } From 246c4fef03277bf796a45e9de379149fef049b93 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 13 Oct 2020 16:58:19 -0300 Subject: [PATCH 126/343] accounting for Story re-creation out of temporary saved slides --- .../android/ui/posts/EditPostActivity.java | 24 ++--- .../LoadStoryFromStoriesPrefsUseCase.kt | 92 ++++++++++++------- 2 files changed, 71 insertions(+), 45 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 9d9028101501..79f25da089d5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3109,18 +3109,18 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - if (mLoadStoryFromStoriesPrefsUseCase.anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles)) { - // unfortunately the medaiIds seem corrupt so, show a dialog and bail - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); - builder.setMessage(getString(R.string.dialog_edit_story_corrupt_message)); - builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { - dialog.dismiss(); - }); - AlertDialog dialog = builder.create(); - dialog.show(); - return; - } +// if (mLoadStoryFromStoriesPrefsUseCase.anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles)) { +// // unfortunately the medaiIds seem corrupt so, show a dialog and bail +// AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); +// builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); +// builder.setMessage(getString(R.string.dialog_edit_story_corrupt_message)); +// builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { +// dialog.dismiss(); +// }); +// AlertDialog dialog = builder.create(); +// dialog.show(); +// return; +// } ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index c993c917f5ea..0554898e9576 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -10,6 +10,7 @@ import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId @@ -27,12 +28,17 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles: ArrayList): ArrayList { val mediaIds = ArrayList() for (mediaFile in mediaFiles) { - val mediaIdLong = (mediaFile as HashMap)["id"] - .toString() - .toDouble() // this conversion is needed to strip off decimals that can come from RN - .toLong() - val mediaIdString = mediaIdLong.toString() - mediaIds.add(mediaIdString) + val rawIdField = (mediaFile as HashMap)["id"] as String + if (rawIdField.startsWith(TEMPORARY_ID_PREFIX)) { + mediaIds.add(rawIdField) + } else { + val mediaIdLong = (mediaFile as HashMap)["id"] + .toString() + .toDouble() // this conversion is needed to strip off decimals that can come from RN + .toLong() + val mediaIdString = mediaIdLong.toString() + mediaIds.add(mediaIdString) + } } return mediaIds } @@ -54,10 +60,15 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { // if this is not a remote nor a local / temporary slide, return false - if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong())) - && !storiesPrefs.isValidSlide(site.id.toLong(), LocalId(StringUtils.stringToInt(mediaId))) - && !storiesPrefs.isValidSlide(site.id.toLong(), TempId(mediaId))) { - return false + if (mediaId.startsWith(TEMPORARY_ID_PREFIX)) { + if (!storiesPrefs.isValidSlide(site.id.toLong(), TempId(mediaId))) { + return false + } + } else { + if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong())) + && !storiesPrefs.isValidSlide(site.id.toLong(), LocalId(StringUtils.stringToInt(mediaId)))) { + return false + } } } return true @@ -71,38 +82,53 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( var storyIndex = StoryRepository.DEFAULT_NONE_SELECTED storyRepositoryWrapper.loadStory(storyIndex) storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() + var tempSlidesLoadedCount: Int = 0 for (mediaId in mediaIds) { - var storyFrameItem = storiesPrefs.getSlideWithRemoteId( - site.getId().toLong(), - RemoteId(mediaId.toLong()) - ) - if (storyFrameItem != null) { - storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + // let's check if this is a temporary id + if (mediaId.startsWith(TEMPORARY_ID_PREFIX)) { + var storyFrameItem = storiesPrefs.getSlideWithTempId( + site.getId().toLong(), + TempId(mediaId) + ) + if (storyFrameItem != null) { + tempSlidesLoadedCount++ + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + } } else { - allStorySlidesAreEditable = false - - // for this missing frame we'll create a new frame using the actual uploaded flattened media - val tmpMediaIdsLong = ArrayList() - tmpMediaIdsLong.add(mediaId.toLong()) - val mediaModelList: List = mediaStore.getSiteMediaWithIds( - site, - tmpMediaIdsLong + var storyFrameItem = storiesPrefs.getSlideWithRemoteId( + site.getId().toLong(), + RemoteId(mediaId.toLong()) ) - if (mediaModelList.isEmpty()) { - noSlidesLoaded = true + if (storyFrameItem != null) { + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) } else { - for (mediaModel in mediaModelList) { - storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( - Uri.parse(mediaModel.url), - mediaModel.isVideo - ) - storyFrameItem.id = mediaModel.mediaId.toString() - storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + allStorySlidesAreEditable = false + + // for this missing frame we'll create a new frame using the actual uploaded flattened media + val tmpMediaIdsLong = ArrayList() + tmpMediaIdsLong.add(mediaId.toLong()) + val mediaModelList: List = mediaStore.getSiteMediaWithIds( + site, + tmpMediaIdsLong + ) + if (mediaModelList.isEmpty()) { + noSlidesLoaded = true + } else { + for (mediaModel in mediaModelList) { + storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( + Uri.parse(mediaModel.url), + mediaModel.isVideo + ) + storyFrameItem.id = mediaModel.mediaId.toString() + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + } } } } } + noSlidesLoaded = (!noSlidesLoaded && tempSlidesLoadedCount == 0) + return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable, noSlidesLoaded) } From 3958007f218447ffc93594e7a781105a4536589e Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 06:39:21 -0300 Subject: [PATCH 127/343] updated gutenberg-mobile commit hash, removed commented out code --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 433e2cb33c22..875112c46153 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 433e2cb33c22cefcb8aadfcc390d2cd720e61188 +Subproject commit 875112c46153b0a2169d2ddc7a6144b537728a5f From d7c646a2556321b09cce047a287fa2a7e4aada79 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 08:00:19 -0300 Subject: [PATCH 128/343] kotlinified / streamlined logic for recreating Story from prefs --- .../LoadStoryFromStoriesPrefsUseCase.kt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 0554898e9576..ac996821e083 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -82,26 +82,22 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( var storyIndex = StoryRepository.DEFAULT_NONE_SELECTED storyRepositoryWrapper.loadStory(storyIndex) storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() - var tempSlidesLoadedCount: Int = 0 for (mediaId in mediaIds) { // let's check if this is a temporary id if (mediaId.startsWith(TEMPORARY_ID_PREFIX)) { - var storyFrameItem = storiesPrefs.getSlideWithTempId( + storiesPrefs.getSlideWithTempId( site.getId().toLong(), TempId(mediaId) - ) - if (storyFrameItem != null) { - tempSlidesLoadedCount++ - storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) + )?.let { + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(it) } } else { - var storyFrameItem = storiesPrefs.getSlideWithRemoteId( + storiesPrefs.getSlideWithRemoteId( site.getId().toLong(), RemoteId(mediaId.toLong()) - ) - if (storyFrameItem != null) { - storyRepositoryWrapper.addStoryFrameItemToCurrentStory(storyFrameItem) - } else { + )?.let { + storyRepositoryWrapper.addStoryFrameItemToCurrentStory(it) + } ?: run { allStorySlidesAreEditable = false // for this missing frame we'll create a new frame using the actual uploaded flattened media @@ -115,7 +111,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( noSlidesLoaded = true } else { for (mediaModel in mediaModelList) { - storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( + val storyFrameItem = StoryFrameItem.getNewStoryFrameItemFromUri( Uri.parse(mediaModel.url), mediaModel.isVideo ) @@ -127,7 +123,7 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( } } - noSlidesLoaded = (!noSlidesLoaded && tempSlidesLoadedCount == 0) + noSlidesLoaded = storyRepositoryWrapper.getStoryAtIndex(storyIndex).frames.size == 0 return ReCreateStoryResult(storyIndex, allStorySlidesAreEditable, noSlidesLoaded) } From d75a6f9cd94ac267aabb5c38676bca67ef62e045 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 08:00:54 -0300 Subject: [PATCH 129/343] if story is unsuccessful, add id to failedMediaIds and remove from progressMax map --- .../android/editor/gutenberg/GutenbergEditorFragment.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index bb8431e150e1..47cd12d0288d 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -1130,6 +1130,10 @@ public void onEditorThemeUpdated(Bundle editorTheme) { } @Override public void onStorySaveResult(String storyFirstMediaId, boolean success) { + if (!success) { + mFailedMediaIds.add(storyFirstMediaId); + mUploadingMediaProgressMax.remove(storyFirstMediaId); + } getGutenbergContainerFragment().onStorySaveResult(storyFirstMediaId, success); } From 20e8f715fc69394e32d00826be1e9745d84af247 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 09:39:10 -0300 Subject: [PATCH 130/343] make sure not to cast id coming from Gutenberg mediaFiles as String, as it may have been replaced by Double with final remote mediaIds --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index ac996821e083..799f5cfb5ad1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -28,11 +28,11 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( fun getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles: ArrayList): ArrayList { val mediaIds = ArrayList() for (mediaFile in mediaFiles) { - val rawIdField = (mediaFile as HashMap)["id"] as String - if (rawIdField.startsWith(TEMPORARY_ID_PREFIX)) { + val rawIdField = (mediaFile as HashMap)["id"] + if (rawIdField is String && rawIdField.startsWith(TEMPORARY_ID_PREFIX)) { mediaIds.add(rawIdField) } else { - val mediaIdLong = (mediaFile as HashMap)["id"] + val mediaIdLong = rawIdField .toString() .toDouble() // this conversion is needed to strip off decimals that can come from RN .toLong() From cd82b8d5867e03a9ba87f8b6d9f6f9cfff6b26b7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 09:40:19 -0300 Subject: [PATCH 131/343] only proceed with readding a saved slide if this is a final StorySaveResult and not a single slide save Retry event --- .../ui/posts/editor/StoriesEventListener.kt | 2 +- .../stories/media/StoryMediaSaveUploadBridge.kt | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 3f5245f2a145..ba59411fe3cb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -150,7 +150,7 @@ class StoriesEventListener @Inject constructor( return } val story = storyRepositoryWrapper.getStoryAtIndex(event.storyIndex) - if (event.frameSaveResult.size == story.frames.size) { + if (!event.isRetry && event.frameSaveResult.size == story.frames.size) { // take the first frame IDs and mediaUri val localMediaId = story.frames[0].id.toString() storySaveMediaListener?.onStorySaveResult(localMediaId, event.isSuccess()) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 326c1482126c..4a8dde186011 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -233,18 +233,18 @@ class StoryMediaSaveUploadBridge @Inject constructor( // only trigger the bridge preparation and the UploadService if the Story is now complete // otherwise we can be receiving successful retry events for individual frames we shouldn't care about just // yet. - if (isStorySavingComplete(event)) { + if (isStorySavingComplete(event) && !event.isRetry) { // only remove it if it was successful - we want to keep it and show a snackbar once when the user // comes back to the app if it wasn't, see MySiteFrament for details. eventBusWrapper.removeStickyEvent(event) editPostRepository.loadPostByLocalPostId(it.getInt(StoryComposerActivity.KEY_POST_LOCAL_ID)) - if (event.isEditMode) { - // we're done using the temporary ids, let's clean mediaFiles attribute from the blocks that have - // those - saveStoryGutenbergBlockUseCase.cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost( - editPostRepository - ) - } +// if (event.isEditMode) { +// // we're done using the temporary ids, let's clean mediaFiles attribute from the blocks that have +// // those +// saveStoryGutenbergBlockUseCase.cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost( +// editPostRepository +// ) +// } // media upload tracking already in addLocalMediaToPostUseCase.addNewMediaToEditorAsync addNewStoryFrameMediaItemsToPostAndUploadAsync(site, event) } From 8b3e7e8243be091eea7024103188f986cc482d78 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 09:41:32 -0300 Subject: [PATCH 132/343] processing media slides with buildStoryMediaFileDataForTemporarySlide for both the case the id hasn't been assigned yet or the case where this is already a temporary slide that has a tempid, but has failed being saved --- .../ui/stories/StoryComposerActivity.kt | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 563175bb482e..be2ed22a3325 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -22,6 +22,7 @@ import com.wordpress.stories.compose.PrepublishingEventProvider import com.wordpress.stories.compose.SnackbarProvider import com.wordpress.stories.compose.StoryDiscardListener import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult +import com.wordpress.stories.compose.story.StoryFrameItem import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.FileBackgroundSource import com.wordpress.stories.compose.story.StoryFrameItem.BackgroundSource.UriBackgroundSource import com.wordpress.stories.compose.story.StoryFrameItemType.VIDEO @@ -62,6 +63,7 @@ import org.wordpress.android.ui.posts.PublishPost import org.wordpress.android.ui.posts.editor.media.AddExistingMediaSource.WP_MEDIA_LIBRARY import org.wordpress.android.ui.posts.editor.media.EditorMediaListener import org.wordpress.android.ui.posts.prepublishing.PrepublishingBottomSheetListener +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.StoryMediaFileData import org.wordpress.android.ui.stories.media.StoryEditorMedia import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState @@ -519,33 +521,36 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), // image yet) so, let's use that for now, and assign the temporaryID we'll use to send // save progress events to Gutenberg. null -> { - val storyMediaFileData = - saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryIdNoMediaFile( - temporaryId = assignedTempId, - url = if (frame.source is FileBackgroundSource) { - (frame.source as FileBackgroundSource).file.toString() - } else { - (frame.source as UriBackgroundSource).contentUri.toString() - }, - isVideo = (frame.frameItemType is VIDEO) - ) + val storyMediaFileData = buildStoryMediaFileDataForTemporarySlide( + frame, + assignedTempId + ) frame.id = storyMediaFileData.id storyMediaFileDataList.add(storyMediaFileData) } - // if the frame.id is populated, this should be an actual MediaModel mediaId so, + // if the frame.id is populated and is not a temporary id, this should be an actual MediaModel mediaId so, // let's use that to obtain the mediaFile and then replace it with the temporary frame.id else -> { frame.id?.let { - val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) - val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) - mediaFile?.let { - val storyMediaFileData = - saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( - mediaFile = it, - temporaryId = assignedTempId - ) - frame.id = storyMediaFileData.id + if (it.startsWith(TEMPORARY_ID_PREFIX)) { + val storyMediaFileData = buildStoryMediaFileDataForTemporarySlide( + frame, + it + ) + // frame.id = storyMediaFileData.id storyMediaFileDataList.add(storyMediaFileData) + } else { + val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) + val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) + mediaFile?.let { + val storyMediaFileData = + saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( + mediaFile = it, + temporaryId = assignedTempId + ) + frame.id = storyMediaFileData.id + storyMediaFileDataList.add(storyMediaFileData) + } } } } @@ -554,6 +559,20 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), return storyMediaFileDataList } + private fun buildStoryMediaFileDataForTemporarySlide(frame: StoryFrameItem, tempId: String): StoryMediaFileData { + val storyMediaFileData = + saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryIdNoMediaFile( + temporaryId = tempId, + url = if (frame.source is FileBackgroundSource) { + (frame.source as FileBackgroundSource).file.toString() + } else { + (frame.source as UriBackgroundSource).contentUri.toString() + }, + isVideo = (frame.frameItemType is VIDEO) + ) + return storyMediaFileData + } + override fun onSubmitButtonClicked(publishPost: PublishPost) { viewModel.onSubmitButtonClicked() } From a4996049a67893a7473ce339c32191e00af8de31 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 09:43:05 -0300 Subject: [PATCH 133/343] removed no longer used cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost --- .../stories/SaveStoryGutenbergBlockUseCase.kt | 31 ------------------- .../media/StoryMediaSaveUploadBridge.kt | 7 ----- 2 files changed, 38 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 118212cf54c7..b27894457404 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -86,37 +86,6 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( return TEMPORARY_ID_PREFIX + "$tempIdBase-$storyIndex-$frameIndex" } - fun cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost(editPostRepository: EditPostRepository) { - editPostRepository.update { postModel: PostModel -> - val gson = Gson() - findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( - postModel, - object : DoWithMediaFilesListener { - override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { - var processedContent = content - val storyBlockData: StoryBlockData? = - gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) - storyBlockData?.let { - if (hasTemporaryIdsInStoryData(it)) { - // here remove the whole mediaFiles attribute - processedContent = content.replace(mediaFilesJsonString, "") - } - } - return processedContent - } - } - ) - true - } - } - - private fun hasTemporaryIdsInStoryData(storyBlockData: StoryBlockData): Boolean { - val temporaryIds = storyBlockData.mediaFiles.filter { - it.id.startsWith(TEMPORARY_ID_PREFIX) - } - return temporaryIds.size > 0 - } - fun findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( postModel: PostModel, listener: DoWithMediaFilesListener diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 4a8dde186011..c53ba1fca142 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -238,13 +238,6 @@ class StoryMediaSaveUploadBridge @Inject constructor( // comes back to the app if it wasn't, see MySiteFrament for details. eventBusWrapper.removeStickyEvent(event) editPostRepository.loadPostByLocalPostId(it.getInt(StoryComposerActivity.KEY_POST_LOCAL_ID)) -// if (event.isEditMode) { -// // we're done using the temporary ids, let's clean mediaFiles attribute from the blocks that have -// // those -// saveStoryGutenbergBlockUseCase.cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost( -// editPostRepository -// ) -// } // media upload tracking already in addLocalMediaToPostUseCase.addNewMediaToEditorAsync addNewStoryFrameMediaItemsToPostAndUploadAsync(site, event) } From dd312efde932e274572ca9caca4afd19290eecb5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 09:52:22 -0300 Subject: [PATCH 134/343] fixed linter warnings --- .../android/ui/posts/editor/StoriesEventListener.kt | 2 -- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 6 +++++- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index ba59411fe3cb..18073b7bc209 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -15,7 +15,6 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.wordpress.android.editor.gutenberg.StorySaveMediaListener import org.wordpress.android.fluxc.Dispatcher -import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore @@ -23,7 +22,6 @@ import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent import org.wordpress.android.ui.stories.prefs.StoriesPrefs -import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId import org.wordpress.android.util.FluxCUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index b27894457404..f1d3ae7285f6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -148,7 +148,11 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( ) } - fun saveNewLocalFilesToStoriesPrefsTempSlides(site: SiteModel, storyIndex: StoryIndex, frames: ArrayList) { + fun saveNewLocalFilesToStoriesPrefsTempSlides( + site: SiteModel, + storyIndex: StoryIndex, + frames: ArrayList + ) { for ((frameIndex, frame) in frames.withIndex()) { if (frame.id == null) { val assignedTempId = getTempIdForStoryFrame( diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 799f5cfb5ad1..8a8dc49dda64 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -65,8 +65,8 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return false } } else { - if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong())) - && !storiesPrefs.isValidSlide(site.id.toLong(), LocalId(StringUtils.stringToInt(mediaId)))) { + if (!storiesPrefs.isValidSlide(site.id.toLong(), RemoteId(mediaId.toLong())) && + !storiesPrefs.isValidSlide(site.id.toLong(), LocalId(StringUtils.stringToInt(mediaId)))) { return false } } From b8d6c4f7ca81bfc2c55025f48173c70ee3434d0f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 10:09:12 -0300 Subject: [PATCH 135/343] removed no longer needed check for anyMediaIdsInGutenbergStoryBlockAreCorrupt --- .../android/ui/posts/EditPostActivity.java | 13 ------------- .../usecase/LoadStoryFromStoriesPrefsUseCase.kt | 14 -------------- WordPress/src/main/res/values/strings.xml | 1 - 3 files changed, 28 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 79f25da089d5..49d6790a705f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3109,19 +3109,6 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { -// if (mLoadStoryFromStoriesPrefsUseCase.anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles)) { -// // unfortunately the medaiIds seem corrupt so, show a dialog and bail -// AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); -// builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); -// builder.setMessage(getString(R.string.dialog_edit_story_corrupt_message)); -// builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { -// dialog.dismiss(); -// }); -// AlertDialog dialog = builder.create(); -// dialog.show(); -// return; -// } - ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); if (!result.getNoSlidesLoaded()) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt index 8a8dc49dda64..4b7d0e694015 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt @@ -43,20 +43,6 @@ class LoadStoryFromStoriesPrefsUseCase @Inject constructor( return mediaIds } - fun anyMediaIdsInGutenbergStoryBlockAreCorrupt(mediaFiles: ArrayList): Boolean { - for (mediaFile in mediaFiles) { - try { - (mediaFile as HashMap)["id"] - .toString() - .toDouble() // this conversion is needed to strip off decimals that can come from RN - .toLong() - } catch (exception: NumberFormatException) { - return true - } - } - return false - } - fun areAllStorySlidesEditable(site: SiteModel, mediaIds: ArrayList): Boolean { for (mediaId in mediaIds) { // if this is not a remote nor a local / temporary slide, return false diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index ff075553c4da..ed5197aa7070 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2887,7 +2887,6 @@ Limited Story Editing This story was edited on a different device and the ability to edit certain objects may be limited. Can\'t edit Story - There was a problem saving this story and can\'t be edited at this moment. Unable to load media for this story. Check your internet connection and try again in a moment. Can\'t edit Story We couldn\'t find the media for this story on the site. From de53b35595ff3d999ccb5bad10e8c5b56a1c9a3f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 10:31:11 -0300 Subject: [PATCH 136/343] fixed bad indirection reference --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 49d6790a705f..1dc46d4c47f4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3156,7 +3156,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie ArrayList mediaIdsToRetry = new ArrayList<>(); for (Object mediaFile : mediaFiles) { int localMediaId - = StringUtils.stringToInt(((HashMap) mediaFile).toString(), 0); + = StringUtils.stringToInt(((HashMap) mediaFile).get("id").toString(), 0); if (localMediaId != 0) { MediaModel media = mMediaStore.getMediaWithLocalId(localMediaId); // if we find at least one item in the mediaFiles collection passed From 5406c63842a5938984cd8eb6e0d344d1cf8d24e1 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 10:35:10 -0300 Subject: [PATCH 137/343] using cancel label for cancel button --- .../android/editor/gutenberg/GutenbergEditorFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 47cd12d0288d..1b8767ea5eb4 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -667,7 +667,7 @@ public void onClick(DialogInterface dialog, int id) { } }); - builder.setNegativeButton(R.string.retry_failed_upload_remove, new DialogInterface.OnClickListener() { + builder.setNegativeButton(R.string.dialog_button_cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.dismiss(); } From d065c92af47bedcae4cf0826765a60e80c75d0b4 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 10:52:20 -0300 Subject: [PATCH 138/343] using eventBusWrapper --- .../android/ui/posts/editor/StoriesEventListener.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 4a2045aae724..fc853b1c4370 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -10,7 +10,6 @@ import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult -import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.wordpress.android.editor.gutenberg.StorySaveMediaListener @@ -21,6 +20,7 @@ import org.wordpress.android.fluxc.store.MediaStore import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent +import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.FluxCUtils import org.wordpress.android.util.helpers.MediaFile import javax.inject.Inject @@ -28,6 +28,7 @@ import javax.inject.Inject class StoriesEventListener @Inject constructor( private val dispatcher: Dispatcher, private val mediaStore: MediaStore, + private val eventBusWrapper: EventBusWrapper, private val storyRepositoryWrapper: StoryRepositoryWrapper ) : LifecycleObserver { private lateinit var lifecycle: Lifecycle @@ -37,7 +38,7 @@ class StoriesEventListener @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) private fun onCreate() { dispatcher.register(this) - EventBus.getDefault().register(this) + eventBusWrapper.register(this) } /** @@ -48,7 +49,7 @@ class StoriesEventListener @Inject constructor( private fun onDestroy() { lifecycle.removeObserver(this) dispatcher.unregister(this) - EventBus.getDefault().unregister(this) + eventBusWrapper.unregister(this) } fun start(lifecycle: Lifecycle, site: SiteModel) { From 29f27c856d52600c92bd118e2ea37e91b3ed0743 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 14 Oct 2020 10:55:01 -0300 Subject: [PATCH 139/343] Update WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt Co-authored-by: Joel Dean --- .../wordpress/android/ui/posts/editor/StoriesEventListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index fc853b1c4370..b8c5b7ffb9a0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -69,7 +69,7 @@ class StoriesEventListener @Inject constructor( return } val localMediaId = event.frameId.toString() - val progress: Float = storyRepositoryWrapper.getCurrentStorySaveProgress(event.storyIndex, 0.0f) + val progress = storyRepositoryWrapper.getCurrentStorySaveProgress(event.storyIndex, 0.0f) storySaveMediaListener?.onMediaSaveReattached(localMediaId, progress) } From 5505f7d27cb608d73db2c77750566ab6f29e68e0 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 14 Oct 2020 10:57:21 -0300 Subject: [PATCH 140/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt Co-authored-by: Joel Dean --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index f53f4a94915b..7f78f1f8c58f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -21,7 +21,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( } } - fun buildJetpackStoryBlockString( + private fun buildJetpackStoryBlockString( mediaFiles: List ): String { val jsonArrayMediaFiles = ArrayList() // holds media files From 8b9885f6c968a5f5bec45ecf8a67eec8ac791c25 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 11:02:16 -0300 Subject: [PATCH 141/343] stripped String type from method name, as it can be inferred from the parameter type --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 7f78f1f8c58f..568c7cfd7f0d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -83,7 +83,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( postModel, object : DoWithMediaFilesListener { - override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { + override fun doWithMediaFilesJson(content: String, mediaFilesJsonString: String): String { var processedContent = content val storyBlockData: StoryBlockData? = gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) @@ -126,7 +126,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( val jsonString: String = content.substring( storyBlockStartIndex + HEADING_START.length, content.indexOf(HEADING_END)) - content = listener.doWithMediaFilesJsonString(content, jsonString) + content = listener.doWithMediaFilesJson(content, jsonString) storyBlockStartIndex += HEADING_START.length } } @@ -139,7 +139,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( postModel, object : DoWithMediaFilesListener { - override fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String { + override fun doWithMediaFilesJson(content: String, mediaFilesJsonString: String): String { var processedContent = content val storyBlockData: StoryBlockData? = gson.fromJson(mediaFilesJsonString, StoryBlockData::class.java) @@ -176,7 +176,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( } interface DoWithMediaFilesListener { - fun doWithMediaFilesJsonString(content: String, mediaFilesJsonString: String): String + fun doWithMediaFilesJson(content: String, mediaFilesJsonString: String): String } data class StoryBlockData( From f0c509d5c91577b7a162c8b80780b9b946408ce9 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 14 Oct 2020 11:02:59 -0300 Subject: [PATCH 142/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt Co-authored-by: Joel Dean --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 647e03d92d33..eb97bf399291 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -435,7 +435,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), override fun onFrameRemove(storyIndex: StoryIndex, storyFrameIndex: Int) { // keep record of the frames users deleted. // But we'll only actually do cleanup once they tap on the DONE/SAVE button, because they could - // still bail out of the StoryComposer by tapping back or the cross and then admiting they want to lose + // still bail out of the StoryComposer by tapping back or the cross and then admitting they want to lose // the changes they made (this means, they'd want to keep the stories). val story = storyRepositoryWrapper.getStoryAtIndex(storyIndex) if (storyFrameIndex < story.frames.size) { From 8def90fff7d33482f56027391de0efa2baee43cf Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 14 Oct 2020 11:03:15 -0300 Subject: [PATCH 143/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt Co-authored-by: Joel Dean --- .../android/ui/stories/media/StoryMediaSaveUploadBridge.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 7507f4431069..38d77f22b07b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -106,7 +106,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( if (!isEditMode) { saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( editPostRepository, - ArrayList(mediaFiles.values) + ArrayList(mediaFiles.values) ) } else { // no op: in edit mode, we're handling replacing of the block's mediaFiles in Gutenberg From eff3b2881afce174e8fc62ec329b92ba581e312c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 11:20:19 -0300 Subject: [PATCH 144/343] renamed to shorten name --- .../android/ui/stories/SaveStoryGutenbergBlockUseCase.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 568c7cfd7f0d..81f1fd87a6dd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -80,7 +80,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( fun cleanTemporaryMediaFilesStructFoundInAnyStoryBlockInPost(editPostRepository: EditPostRepository) { editPostRepository.update { postModel: PostModel -> val gson = Gson() - findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( + findAllStoryBlocksInPostAndPerformOnEachMediaFilesJson( postModel, object : DoWithMediaFilesListener { override fun doWithMediaFilesJson(content: String, mediaFilesJsonString: String): String { @@ -108,7 +108,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( return temporaryIds.size > 0 } - fun findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( + fun findAllStoryBlocksInPostAndPerformOnEachMediaFilesJson( postModel: PostModel, listener: DoWithMediaFilesListener ) { @@ -136,7 +136,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( fun replaceLocalMediaIdsWithRemoteMediaIdsInPost(postModel: PostModel, mediaFile: MediaFile) { val gson = Gson() - findAllStoryBlocksInPostContentAndPerformOnEachMediaFilesJsonString( + findAllStoryBlocksInPostAndPerformOnEachMediaFilesJson( postModel, object : DoWithMediaFilesListener { override fun doWithMediaFilesJson(content: String, mediaFilesJsonString: String): String { From 873a364d290b970a53efccaa9901d1bb0c7b3a49 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 11:49:56 -0300 Subject: [PATCH 145/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index e4b907c3e9a9..a76e41781e4e 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit e4b907c3e9a971c8e0021e310ba4b86dcdb92076 +Subproject commit a76e41781e4e8a4791c8006804aaf9eb06edbd04 From bb63fc3bb243eb0f024f045e063cd2580bc81ab5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 12:00:27 -0300 Subject: [PATCH 146/343] updated commit hash for gutenberg-mobile --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index a76e41781e4e..3130e2449ca3 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit a76e41781e4e8a4791c8006804aaf9eb06edbd04 +Subproject commit 3130e2449ca3c390474a579654076f90ee58555c From 640183eb2fc24de718635b15552d8654a4df745b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 14:04:00 -0300 Subject: [PATCH 147/343] updated commit hash after merge --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 3130e2449ca3..03d3abe6590b 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 3130e2449ca3c390474a579654076f90ee58555c +Subproject commit 03d3abe6590b4c46e7db2176d0633b62325f7ef0 From cb1305d36188591b8a513ce2635161892c9de717 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 14 Oct 2020 14:56:24 -0300 Subject: [PATCH 148/343] updated gutenberg-mobile branch --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 03d3abe6590b..de365b19dd79 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 03d3abe6590b4c46e7db2176d0633b62325f7ef0 +Subproject commit de365b19dd795a5843b08ec36ca349eeb617703b From 05f424d189c762682aed23b6655818dc8b97c150 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 15 Oct 2020 21:08:19 -0300 Subject: [PATCH 149/343] updated gutenberg-mobile hash' --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index de365b19dd79..afedc7e30ca7 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit de365b19dd795a5843b08ec36ca349eeb617703b +Subproject commit afedc7e30ca7c7dc5e86a00c8a9be7f9a276cd2a From c56d2db21766334b01ed6cbd32f2c9c5ed8db376 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 16 Oct 2020 16:47:08 -0300 Subject: [PATCH 150/343] updated gutenberg-mobile hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index afedc7e30ca7..56f2eda8e029 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit afedc7e30ca7c7dc5e86a00c8a9be7f9a276cd2a +Subproject commit 56f2eda8e02967c76ff1fb0dc23a25cc5915edc7 From 27feee8f913cd11ee22786514cd0b00edd535d0f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 16 Oct 2020 19:31:52 -0300 Subject: [PATCH 151/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 56f2eda8e029..1e637673eeb9 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 56f2eda8e02967c76ff1fb0dc23a25cc5915edc7 +Subproject commit 1e637673eeb98d93981ff7a0b2c4c0921e197108 From b09a3c954f7ca62fe6894ac21a226bdafefcad1f Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 16 Oct 2020 20:21:36 -0300 Subject: [PATCH 152/343] updated gutenberg-mobile hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 875112c46153..6274268bb958 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 875112c46153b0a2169d2ddc7a6144b537728a5f +Subproject commit 6274268bb9589eb1ffde2df4f616413281350299 From 550f2e134259bd9d37a0aff8249e0bafb5f12ef1 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 16 Oct 2020 20:21:53 -0300 Subject: [PATCH 153/343] updates stories lib hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 2395566a4470..ec323840d236 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 2395566a4470d6751bdb547bd231f9fdb31f2ca0 +Subproject commit ec323840d2360bb8421b63f59f5e7249689332d4 From 012aac76a4af1f31e3f55cfc9836983ae169efd5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 17 Oct 2020 09:59:56 -0300 Subject: [PATCH 154/343] update gutenberg-mobile commit hash to point to bundling DEV flag lifting for internal release --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 6274268bb958..5606cc683c73 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 6274268bb9589eb1ffde2df4f616413281350299 +Subproject commit 5606cc683c732256217fa009973de8a1888ebe00 From f38dd3f568b1b1a4d61f2857e61e1c123f12ed82 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 17 Oct 2020 11:10:26 -0300 Subject: [PATCH 155/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 5606cc683c73..f0374d931e8e 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 5606cc683c732256217fa009973de8a1888ebe00 +Subproject commit f0374d931e8e415055b3690335468bb2f8c6823e From 64f084d4503e06f7ab3f98ddde1f57cba8d3df56 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 17 Oct 2020 11:23:03 -0300 Subject: [PATCH 156/343] added temporary alert dialog for alpha when users tap on an empty story block --- .../wordpress/android/ui/posts/EditPostActivity.java | 12 ++++++++++++ WordPress/src/main/res/values/strings.xml | 2 ++ 2 files changed, 14 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index cc26947a5ace..fa8a894c4f69 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3252,6 +3252,18 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { + if (mediaFiles.isEmpty()) { + AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); + builder.setTitle(getString(R.string.dialog_edit_story_no_new_blocks_title)); + builder.setMessage(getString(R.string.dialog_edit_story_no_new_blocks_message)); + builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { + dialog.dismiss(); + }); + AlertDialog dialog = builder.create(); + dialog.show(); + return; + } + ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); if (!result.getNoSlidesLoaded()) { diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 6f47ea49191f..7705271cf669 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2906,6 +2906,8 @@ Unable to load media for this story. Check your internet connection and try again in a moment. Can\'t edit Story We couldn\'t find the media for this story on the site. + Can\'t create Story + Alpha - we can\'t create a Story here for now ¯\_(ツ)_/¯ - use the main menu or Posts list entry points Capture Flip camera Flash From 9495a36e293eca76697025859459aee536a6c04a Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Sat, 17 Oct 2020 11:25:01 -0300 Subject: [PATCH 157/343] updated string --- WordPress/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 7705271cf669..af4a02915eaf 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2907,7 +2907,7 @@ Can\'t edit Story We couldn\'t find the media for this story on the site. Can\'t create Story - Alpha - we can\'t create a Story here for now ¯\_(ツ)_/¯ - use the main menu or Posts list entry points + Alpha - we can\'t create a Story here for now ¯\\_(ツ)_/¯ - use the main menu or Posts list entry points Capture Flip camera Flash From 037b679ede139f08c6bff05ea351e9fc0bdeeff1 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 08:57:37 -0300 Subject: [PATCH 158/343] changed name --- WordPress/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index af4a02915eaf..7051dbad0cb9 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2907,7 +2907,7 @@ Can\'t edit Story We couldn\'t find the media for this story on the site. Can\'t create Story - Alpha - we can\'t create a Story here for now ¯\\_(ツ)_/¯ - use the main menu or Posts list entry points + Internal Beta - we can\'t create a Story here for now ¯\\_(ツ)_/¯ - use the main menu or Posts list entry points Capture Flip camera Flash From ae1010043868d6f7f99cb612d601faab5cd494b8 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 09:24:15 -0300 Subject: [PATCH 159/343] updated stories lib hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 93390586f1f8..f7f20e13e397 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 93390586f1f852761fac44ba9ec6b1b1dfc9bc09 +Subproject commit f7f20e13e39793949b432c31e8e7ffca8d0644ad From 75e8942ddf0eb9eacfa47cf861314c90ec506a63 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 11:07:55 -0300 Subject: [PATCH 160/343] opening the editor in capture mode when an empty blocked is tapped to allow adding slides --- .../android/ui/ActivityLauncher.java | 19 +++++++++++++++++++ .../android/ui/posts/EditPostActivity.java | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index 9f684944a8b0..54d1b3d0f7f6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -771,6 +771,25 @@ public static void editStoryForResult( activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); } + public static void editEmptyStoryForResult( + Activity activity, + SiteModel site, + int storyIndex, + String storyBlockId + ) { + if (site == null) { + return; + } + + Intent intent = new Intent(activity, StoryComposerActivity.class); + intent.putExtra(WordPress.SITE, site); + intent.putExtra(KEY_LAUNCHED_FROM_GUTENBERG, true); + intent.putExtra(MediaPickerConstants.EXTRA_LAUNCH_WPSTORIES_CAMERA_REQUESTED, true); + intent.putExtra(KEY_STORY_INDEX, storyIndex); + intent.putExtra(ARG_STORY_BLOCK_ID, storyBlockId); + activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); + } + public static void editPostOrPageForResult(Activity activity, SiteModel site, PostModel post) { editPostOrPageForResult(new Intent(activity, EditPostActivity.class), activity, site, post.getId(), false); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index cc26947a5ace..c80232b0ad4c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -119,6 +119,7 @@ import org.wordpress.android.ui.ActivityId; import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.LocaleAwareActivity; +import org.wordpress.android.ui.PagePostCreationSourcesDetail; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog.PrivateAtCookieProgressDialogOnDismissListener; import org.wordpress.android.ui.RequestCodes; @@ -3254,6 +3255,16 @@ public void onTrackableEvent(TrackableEvent event, Map propertie @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); + if (mediaFiles.isEmpty()) { + ActivityLauncher.editEmptyStoryForResult( + this, + mSite, + mStoryRepositoryWrapper.getCurrentStoryIndex(), + blockId + ); + return; + } + if (!result.getNoSlidesLoaded()) { // Story instance loaded or re-created! Load it onto the StoryComposer for editing now ActivityLauncher.editStoryForResult( From 67c9bea1250d85366abe4948936ed3f2aa6e7844 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 11:23:33 -0300 Subject: [PATCH 161/343] load an existing story if KEY_STORY_INDEX passed, or load current story if not passed, or load a new story if unselected --- .../wordpress/android/ui/stories/StoryComposerActivity.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 4f321cea2ccf..ff21dc49bc98 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -480,7 +480,12 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val blockId = intent.extras?.getString(ARG_STORY_BLOCK_ID) savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) - val storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) + var storyIndex = intent.getIntExtra(KEY_STORY_INDEX, storyRepositoryWrapper.getCurrentStoryIndex()) + if (storyIndex == DEFAULT_NONE_SELECTED) { + // load a new empty story + storyRepositoryWrapper.loadStory(DEFAULT_NONE_SELECTED) + storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() + } // if we are editing this Story Block, then the id is assured to be a remote media file id, but // the frame no longer points to such media Id on the site given we are just about to save a // new flattened media. Hence, we need to set a new temporary Id we can use to identify From 6d1cdc3c5fc0ef05f2b5b13fc07c6f84242ee000 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 11:43:40 -0300 Subject: [PATCH 162/343] got activity launcher call for stories back, for the case of empty mediaFiles --- .../android/ui/posts/EditPostActivity.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 4ac4b6e7d8b7..3aacec115202 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3254,14 +3254,20 @@ public void onTrackableEvent(TrackableEvent event, Map propertie @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { if (mediaFiles.isEmpty()) { - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(getString(R.string.dialog_edit_story_no_new_blocks_title)); - builder.setMessage(getString(R.string.dialog_edit_story_no_new_blocks_message)); - builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { - dialog.dismiss(); - }); - AlertDialog dialog = builder.create(); - dialog.show(); +// AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); +// builder.setTitle(getString(R.string.dialog_edit_story_no_new_blocks_title)); +// builder.setMessage(getString(R.string.dialog_edit_story_no_new_blocks_message)); +// builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { +// dialog.dismiss(); +// }); +// AlertDialog dialog = builder.create(); +// dialog.show(); + ActivityLauncher.editEmptyStoryForResult( + this, + mSite, + mStoryRepositoryWrapper.getCurrentStoryIndex(), + blockId + ); return; } From 35f45a64b18f4cc977c0bd36fac661e60ca9925b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 11:46:06 -0300 Subject: [PATCH 163/343] added TODO comment --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 3aacec115202..2128b4c444d1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3254,6 +3254,7 @@ public void onTrackableEvent(TrackableEvent event, Map propertie @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { if (mediaFiles.isEmpty()) { + // TODO remove this code and related string resources after beta // AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); // builder.setTitle(getString(R.string.dialog_edit_story_no_new_blocks_title)); // builder.setMessage(getString(R.string.dialog_edit_story_no_new_blocks_message)); From 48302482b70defe5c0c464e56188e143859da951 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 12:24:05 -0300 Subject: [PATCH 164/343] fixed story index loading --- .../wordpress/android/ui/stories/StoryComposerActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index ff21dc49bc98..0e5ebdc6e28b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -480,10 +480,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val blockId = intent.extras?.getString(ARG_STORY_BLOCK_ID) savedContentIntent.putExtra(ARG_STORY_BLOCK_ID, blockId) - var storyIndex = intent.getIntExtra(KEY_STORY_INDEX, storyRepositoryWrapper.getCurrentStoryIndex()) + // check if story index has been passed through intent + var storyIndex = intent.getIntExtra(KEY_STORY_INDEX, DEFAULT_NONE_SELECTED) if (storyIndex == DEFAULT_NONE_SELECTED) { - // load a new empty story - storyRepositoryWrapper.loadStory(DEFAULT_NONE_SELECTED) + // if not, let's use the current Story storyIndex = storyRepositoryWrapper.getCurrentStoryIndex() } // if we are editing this Story Block, then the id is assured to be a remote media file id, but From 6f0928f2c6c538b9d15d6ea4c081aab34e120f7b Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 12:24:24 -0300 Subject: [PATCH 165/343] passing localPostId to correctly update it --- .../java/org/wordpress/android/ui/ActivityLauncher.java | 2 ++ .../org/wordpress/android/ui/posts/EditPostActivity.java | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index 54d1b3d0f7f6..2c674ce7e86e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -774,6 +774,7 @@ public static void editStoryForResult( public static void editEmptyStoryForResult( Activity activity, SiteModel site, + LocalId localPostId, int storyIndex, String storyBlockId ) { @@ -785,6 +786,7 @@ public static void editEmptyStoryForResult( intent.putExtra(WordPress.SITE, site); intent.putExtra(KEY_LAUNCHED_FROM_GUTENBERG, true); intent.putExtra(MediaPickerConstants.EXTRA_LAUNCH_WPSTORIES_CAMERA_REQUESTED, true); + intent.putExtra(KEY_POST_LOCAL_ID, localPostId.getValue()); intent.putExtra(KEY_STORY_INDEX, storyIndex); intent.putExtra(ARG_STORY_BLOCK_ID, storyBlockId); activity.startActivityForResult(intent, RequestCodes.EDIT_STORY); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index c80232b0ad4c..e969385d84dc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3253,18 +3253,20 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase - .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); if (mediaFiles.isEmpty()) { ActivityLauncher.editEmptyStoryForResult( this, mSite, + new LocalId(mEditPostRepository.getId()), mStoryRepositoryWrapper.getCurrentStoryIndex(), blockId ); return; } + ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase + .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); + if (!result.getNoSlidesLoaded()) { // Story instance loaded or re-created! Load it onto the StoryComposer for editing now ActivityLauncher.editStoryForResult( From 946941376122fa7f06471b76fbf89bb406a274b9 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 19 Oct 2020 12:36:00 -0300 Subject: [PATCH 166/343] removed unused import --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index e969385d84dc..9978df937d00 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -119,7 +119,6 @@ import org.wordpress.android.ui.ActivityId; import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.LocaleAwareActivity; -import org.wordpress.android.ui.PagePostCreationSourcesDetail; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog; import org.wordpress.android.ui.PrivateAtCookieRefreshProgressDialog.PrivateAtCookieProgressDialogOnDismissListener; import org.wordpress.android.ui.RequestCodes; From ca2bc90286f104b9babf12f02a7658a47eb01898 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 20 Oct 2020 08:09:20 -0300 Subject: [PATCH 167/343] updated commit hash for gutenberg-mobile, with new media placehodler for empty block --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index f0374d931e8e..73b2f1fd0bbf 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit f0374d931e8e415055b3690335468bb2f8c6823e +Subproject commit 73b2f1fd0bbfe774a5293347ce08e16a99a22ab6 From 423068ac7f19989a4cbd970ca9c1df4249021d71 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 20 Oct 2020 08:27:37 -0300 Subject: [PATCH 168/343] removed old commented dialog and related string resourced --- .../org/wordpress/android/ui/posts/EditPostActivity.java | 9 --------- WordPress/src/main/res/values/strings.xml | 2 -- 2 files changed, 11 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 772ea78bdfb6..9978df937d00 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3253,15 +3253,6 @@ public void onTrackableEvent(TrackableEvent event, Map propertie @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { if (mediaFiles.isEmpty()) { - // TODO remove this code and related string resources after beta -// AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); -// builder.setTitle(getString(R.string.dialog_edit_story_no_new_blocks_title)); -// builder.setMessage(getString(R.string.dialog_edit_story_no_new_blocks_message)); -// builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { -// dialog.dismiss(); -// }); -// AlertDialog dialog = builder.create(); -// dialog.show(); ActivityLauncher.editEmptyStoryForResult( this, mSite, diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 7051dbad0cb9..6f47ea49191f 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2906,8 +2906,6 @@ Unable to load media for this story. Check your internet connection and try again in a moment. Can\'t edit Story We couldn\'t find the media for this story on the site. - Can\'t create Story - Internal Beta - we can\'t create a Story here for now ¯\\_(ツ)_/¯ - use the main menu or Posts list entry points Capture Flip camera Flash From ef587b644105ee2b30bd4a98e91a6449824e293c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 20 Oct 2020 10:34:06 -0300 Subject: [PATCH 169/343] updated commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 6274268bb958..080a970f72b6 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 6274268bb9589eb1ffde2df4f616413281350299 +Subproject commit 080a970f72b6c7e64884d58cafe6345efc7855c2 From 76bc10097b85878411b1266ee113c30307548000 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 20 Oct 2020 12:06:29 -0300 Subject: [PATCH 170/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 080a970f72b6..ed36dfc682ec 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 080a970f72b6c7e64884d58cafe6345efc7855c2 +Subproject commit ed36dfc682ec2203fe8f7b9995ca0dc94b90065d From 1e5220fa113216e062e3269924c8cd5afeafb887 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 11:19:46 -0300 Subject: [PATCH 171/343] moved logic for Stories-related load/retry/cancel handling from EditPostActivity to dedicated class StoriesEventListener --- .../android/ui/posts/EditPostActivity.java | 111 ++----------- .../ui/posts/editor/StoriesEventListener.kt | 154 +++++++++++++++++- 2 files changed, 167 insertions(+), 98 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index cc26947a5ace..3714347c3bb6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -171,7 +171,6 @@ import org.wordpress.android.ui.stories.StoryRepositoryWrapper; import org.wordpress.android.ui.stories.prefs.StoriesPrefs; import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase; -import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult; import org.wordpress.android.ui.uploads.PostEvents; import org.wordpress.android.ui.uploads.UploadService; import org.wordpress.android.ui.uploads.UploadUtils; @@ -677,7 +676,7 @@ protected void onCreate(Bundle savedInstanceState) { setupPrepublishingBottomSheetRunnable(); - mStoriesEventListener.start(this.getLifecycle(), mSite); + mStoriesEventListener.start(this.getLifecycle(), mSite, mEditPostRepository); setupPreviewUI(); } @@ -3252,110 +3251,30 @@ public void onTrackableEvent(TrackableEvent event, Map propertie } @Override public void onStoryComposerLoadRequested(ArrayList mediaFiles, String blockId) { - ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase - .loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles); - if (!result.getNoSlidesLoaded()) { - // Story instance loaded or re-created! Load it onto the StoryComposer for editing now - ActivityLauncher.editStoryForResult( - this, - mSite, - new LocalId(mEditPostRepository.getId()), - result.getStoryIndex(), - result.getAllStorySlidesAreEditable(), - true, - blockId - ); - } else { - // unfortunately we couldn't even load the remote media Ids indicated by the StoryBlock so we can't allow - // editing at this time :( - if (mNetworkErrorOnLastMediaFetchAttempt) { - // there was an error fetching media when we were loading the editor, - // we *may* still have a possibility, tell the user they may try refreshing the media again - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title)); - builder.setMessage(getString(R.string.dialog_edit_story_unavailable_message)); - builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { - // try another fetchMedia request - fetchMediaList(); - dialog.dismiss(); - }); - AlertDialog dialog = builder.create(); - dialog.show(); - } else { - // unrecoverable error, nothing we can do, inform the user :(. - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(getString(R.string.dialog_edit_story_unrecoverable_title)); - builder.setMessage(getString(R.string.dialog_edit_story_unrecoverable_message)); - builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> { - dialog.dismiss(); - }); - AlertDialog dialog = builder.create(); - dialog.show(); - } + boolean noSlidesLoaded = mStoriesEventListener.onRequestMediaFilesEditorLoad( + this, + new LocalId(mEditPostRepository.getId()), + mNetworkErrorOnLastMediaFetchAttempt, + mediaFiles, + blockId + ); + + if (mNetworkErrorOnLastMediaFetchAttempt && noSlidesLoaded) { + // try another fetchMedia request + fetchMediaList(); } } @Override public void onRetryUploadForMediaCollection(ArrayList mediaFiles) { - ArrayList mediaIdsToRetry = new ArrayList<>(); - for (Object mediaFile : mediaFiles) { - int localMediaId - = StringUtils.stringToInt(((HashMap) mediaFile).get("id").toString(), 0); - if (localMediaId != 0) { - MediaModel media = mMediaStore.getMediaWithLocalId(localMediaId); - // if we find at least one item in the mediaFiles collection passed - // for which we don't have a local MediaModel, just tell the user and bail - if (media == null) { - AppLog.e(T.MEDIA, "Can't find media with local id: " + localMediaId); - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this); - builder.setTitle(getString(R.string.cannot_retry_deleted_media_item_fatal)); - builder.setPositiveButton(R.string.yes, (dialog, id) -> { - dialog.dismiss(); - }); - - builder.setNegativeButton(getString(R.string.no), (dialog, id) -> dialog.dismiss()); - - AlertDialog dialog = builder.create(); - dialog.show(); - - return; - } - - if (media.getUrl() != null && media.getUploadState().equals(MediaUploadState.UPLOADED.toString())) { - // Note: we should actually do this when the editor fragment starts instead of waiting for user - // input. - // Notify the editor fragment upload was successful and it should replace the local url by the - // remote url. - if (mEditorMediaUploadListener != null) { - mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()), - FluxCUtils.mediaFileFromMediaModel(media)); - } - } else { - UploadService.cancelFinalNotification(this, mEditPostRepository.getPost()); - UploadService.cancelFinalNotificationForMedia(this, mSite); - mediaIdsToRetry.add(localMediaId); - } - } - } - - if (!mediaIdsToRetry.isEmpty()) { - mEditorMedia.retryFailedMediaAsync(mediaIdsToRetry); - } - AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED); + mStoriesEventListener.onRetryUploadForMediaCollection(this, mediaFiles, mEditorMediaUploadListener); } @Override public void onCancelUploadForMediaCollection(ArrayList mediaFiles) { - // just cancel upload for each media - for (Object mediaFile : mediaFiles) { - int localMediaId - = StringUtils.stringToInt(((HashMap) mediaFile).get("id").toString(), 0); - if (localMediaId != 0) { - mEditorMedia.cancelMediaUploadAsync(localMediaId, false); - } - } + mStoriesEventListener.onCancelUploadForMediaCollection(mediaFiles); } @Override public void onCancelSaveForMediaCollection(ArrayList mediaFiles) { - // TODO implement cancelling save process for media collection + mStoriesEventListener.onCancelSaveForMediaCollection(mediaFiles); } // FluxC events diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt index 4469a26bb3a8..2732766f11db 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt @@ -1,10 +1,14 @@ package org.wordpress.android.ui.posts.editor +import android.app.Activity +import android.content.DialogInterface import android.net.Uri +import androidx.appcompat.app.AlertDialog.Builder import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle.State.CREATED import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress @@ -12,27 +16,47 @@ import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import org.wordpress.android.R +import org.wordpress.android.R.string +import org.wordpress.android.analytics.AnalyticsTracker +import org.wordpress.android.analytics.AnalyticsTracker.Stat.EDITOR_UPLOAD_MEDIA_RETRIED +import org.wordpress.android.editor.EditorMediaUploadListener import org.wordpress.android.editor.gutenberg.StorySaveMediaListener import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.MediaModel +import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState.UPLOADED import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.ui.ActivityLauncher +import org.wordpress.android.ui.posts.EditPostRepository +import org.wordpress.android.ui.posts.editor.media.EditorMedia import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent +import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase +import org.wordpress.android.ui.uploads.UploadService +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.AppLog.T.MEDIA import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.FluxCUtils +import org.wordpress.android.util.StringUtils import org.wordpress.android.util.helpers.MediaFile +import java.util.ArrayList +import java.util.HashMap import javax.inject.Inject class StoriesEventListener @Inject constructor( private val dispatcher: Dispatcher, private val mediaStore: MediaStore, private val eventBusWrapper: EventBusWrapper, + private val editorMedia: EditorMedia, + private val loadStoryFromStoriesPrefsUseCase: LoadStoryFromStoriesPrefsUseCase, private val storyRepositoryWrapper: StoryRepositoryWrapper ) : LifecycleObserver { private lateinit var lifecycle: Lifecycle private lateinit var site: SiteModel + private lateinit var editPostRepository: EditPostRepository private var storySaveMediaListener: StorySaveMediaListener? = null @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) @@ -43,7 +67,7 @@ class StoriesEventListener @Inject constructor( /** * Handles the [Lifecycle.Event.ON_DESTROY] event to cleanup the registration for dispatcher and removing the - * observer for lifecycle. + * observer for lifecycle . */ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) private fun onDestroy() { @@ -52,8 +76,9 @@ class StoriesEventListener @Inject constructor( eventBusWrapper.unregister(this) } - fun start(lifecycle: Lifecycle, site: SiteModel) { + fun start(lifecycle: Lifecycle, site: SiteModel, editPostRepository: EditPostRepository) { this.site = site + this.editPostRepository = editPostRepository this.lifecycle = lifecycle this.lifecycle.addObserver(this) } @@ -153,4 +178,129 @@ class StoriesEventListener @Inject constructor( storySaveMediaListener?.onStorySaveResult(localMediaId, event.isSuccess()) } } + + // Editor load / cancel events + fun onRequestMediaFilesEditorLoad( + activity: Activity, + postId: LocalId, + networkErrorOnLastMediaFetchAttempt: Boolean, + mediaFiles: ArrayList, + blockId: String + ): Boolean { + val reCreateStoryResult = loadStoryFromStoriesPrefsUseCase + .loadStoryFromMemoryOrRecreateFromPrefs(site, mediaFiles) + if (!reCreateStoryResult.noSlidesLoaded) { + // Story instance loaded or re-created! Load it onto the StoryComposer for editing now + ActivityLauncher.editStoryForResult( + activity, + site, + postId, + reCreateStoryResult.storyIndex, + reCreateStoryResult.allStorySlidesAreEditable, + true, + blockId + ) + } else { + // unfortunately we couldn't even load the remote media Ids indicated by the StoryBlock so we can't allow + // editing at this time :( + if (networkErrorOnLastMediaFetchAttempt) { + // there was an error fetching media when we were loading the editor, + // we *may* still have a possibility, tell the user they may try refreshing the media again + val builder: Builder = MaterialAlertDialogBuilder( + activity + ) + builder.setTitle(activity.getString(R.string.dialog_edit_story_unavailable_title)) + builder.setMessage(activity.getString(R.string.dialog_edit_story_unavailable_message)) + builder.setPositiveButton(R.string.dialog_button_ok) { dialog, id -> + dialog.dismiss() + } + val dialog = builder.create() + dialog.show() + } else { + // unrecoverable error, nothing we can do, inform the user :(. + val builder: Builder = MaterialAlertDialogBuilder( + activity + ) + builder.setTitle(activity.getString(R.string.dialog_edit_story_unrecoverable_title)) + builder.setMessage(activity.getString(R.string.dialog_edit_story_unrecoverable_message)) + builder.setPositiveButton(R.string.dialog_button_ok) { dialog, id -> dialog.dismiss() } + val dialog = builder.create() + dialog.show() + } + } + return reCreateStoryResult.noSlidesLoaded + } + + fun onCancelUploadForMediaCollection(mediaFiles: ArrayList) { + // just cancel upload for each media + for (mediaFile in mediaFiles) { + val localMediaId = StringUtils.stringToInt( + (mediaFile as HashMap)["id"].toString(), 0 + ) + if (localMediaId != 0) { + editorMedia.cancelMediaUploadAsync(localMediaId, false) + } + } + } + + fun onRetryUploadForMediaCollection( + activity: Activity, + mediaFiles: ArrayList, + editorMediaUploadListener: EditorMediaUploadListener? + ) { + val mediaIdsToRetry = ArrayList() + for (mediaFile in mediaFiles) { + val localMediaId = StringUtils.stringToInt( + (mediaFile as HashMap)["id"].toString(), 0 + ) + if (localMediaId != 0) { + val media: MediaModel = mediaStore.getMediaWithLocalId(localMediaId) + // if we find at least one item in the mediaFiles collection passed + // for which we don't have a local MediaModel, just tell the user and bail + if (media == null) { + AppLog.e( + MEDIA, + "Can't find media with local id: $localMediaId" + ) + val builder: Builder = MaterialAlertDialogBuilder( + activity + ) + builder.setTitle(activity.getString(string.cannot_retry_deleted_media_item_fatal)) + builder.setPositiveButton(string.yes) { dialog, id -> dialog.dismiss() } + builder.setNegativeButton(activity.getString(string.no), + DialogInterface.OnClickListener { dialog: DialogInterface, id: Int -> dialog.dismiss() } + ) + val dialog = builder.create() + dialog.show() + return + } + if (media.url != null && media.uploadState == UPLOADED.toString()) { + // Note: we should actually do this when the editor fragment starts instead of waiting for user + // input. + // Notify the editor fragment upload was successful and it should replace the local url by the + // remote url. + editorMediaUploadListener?.onMediaUploadSucceeded( + media.id.toString(), + FluxCUtils.mediaFileFromMediaModel(media) + ) + } else { + UploadService.cancelFinalNotification( + activity, + editPostRepository.getPost() + ) + UploadService.cancelFinalNotificationForMedia(activity, site) + mediaIdsToRetry.add(localMediaId) + } + } + } + + if (!mediaIdsToRetry.isEmpty()) { + editorMedia.retryFailedMediaAsync(mediaIdsToRetry) + } + AnalyticsTracker.track(EDITOR_UPLOAD_MEDIA_RETRIED) + } + + fun onCancelSaveForMediaCollection(mediaFiles: ArrayList) { + // TODO implement cancelling save process for media collection + } } From 6d01bff26e8751421ca81becaa899fe24953e637 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 12:07:04 -0300 Subject: [PATCH 172/343] updated stories commit hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index f7f20e13e397..621c36488b99 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit f7f20e13e39793949b432c31e8e7ffca8d0644ad +Subproject commit 621c36488b999208617b9e72b3291f4a83c02d0a From 546b6671b8527c24a98e05bae750f6069c1ccc08 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:11:26 -0300 Subject: [PATCH 173/343] adding unit tests for LoadStoryFromStoriesPrefsUseCase class - partial --- .../LoadStoryFromStoriesPrefsUseCaseTest.kt | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt new file mode 100644 index 000000000000..a4608bdd348c --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt @@ -0,0 +1,120 @@ +package org.wordpress.android.ui.stories.usecase + +import android.content.Context +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import org.assertj.core.api.Assertions +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.MediaStore +import org.wordpress.android.ui.posts.EditPostRepository +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX +import org.wordpress.android.ui.stories.StoryRepositoryWrapper +import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.ui.stories.prefs.StoriesPrefs.TempId + +@RunWith(MockitoJUnitRunner::class) +class LoadStoryFromStoriesPrefsUseCaseTest { + private lateinit var loadStoryFromStoriesPrefsUseCase: LoadStoryFromStoriesPrefsUseCase + @Mock lateinit var storyRepositoryWrapper: StoryRepositoryWrapper + @Mock lateinit var mediaStore: MediaStore + @Mock lateinit var storiesPrefs: StoriesPrefs + @Mock lateinit var context: Context + @Mock lateinit var postRepository: EditPostRepository + private lateinit var siteModel: SiteModel + + @Before + fun setUp() { + loadStoryFromStoriesPrefsUseCase = LoadStoryFromStoriesPrefsUseCase( + storyRepositoryWrapper, + storiesPrefs, + mediaStore + ) + siteModel = SiteModel() + } + + @Test + fun `obtain empty media ids list from empty mediaFiles array`() { + // Given + val mediaFiles: ArrayList> = setupMediaFiles(emptyList = true) + + // When + val mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles as ArrayList) + + // Then + Assertions.assertThat(mediaIds).isEmpty() + } + + @Test + fun `obtain media ids list from non empty mediaFiles array`() { + // Given + val mediaFiles: ArrayList> = setupMediaFiles(emptyList = false) + + // When + val mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles as ArrayList) + + // Then + Assertions.assertThat(mediaIds).isNotEmpty + Assertions.assertThat(mediaIds).containsExactly("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") + } + + @Test + fun `verify all story slides are editable with temporary ids`() { + // Given + val mediaIdsTemp = setupTestSildes(sayValid = true, useTempPrefix = true, useRemoteId = false) + + // When + val result = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(siteModel, mediaIdsTemp) + + // Then + Assertions.assertThat(result).isTrue() + } + + private fun setupMediaFiles( + emptyList: Boolean + ): ArrayList> { + when (emptyList) { + true -> return ArrayList>() + false -> { + val mediaFiles = ArrayList>() + for (i in 1..10) { + val oneMediaFile = HashMap() + oneMediaFile.put("mime", "image/jpeg") + oneMediaFile.put("link", "https://testmzorz3.files.wordpress.com/2020/10/wp-0000000.jpg") + oneMediaFile.put("type", "image") + oneMediaFile.put("id", i.toString()) + mediaFiles.add(oneMediaFile) + } + return mediaFiles + } + } + } + + private fun setupTestSildes( + sayValid: Boolean, + useTempPrefix: Boolean, + useRemoteId: Boolean + ): ArrayList { + val mediaIds = ArrayList() + + for (i in 1..10) { + mediaIds.add((if (useTempPrefix) TEMPORARY_ID_PREFIX else "") + i.toString()) + } + + if (useTempPrefix) { + whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), mock())).thenReturn(sayValid) + } else if (useRemoteId) { + whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), mock())).thenReturn(sayValid) + } else { + whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), mock())).thenReturn(sayValid) + } + + return mediaIds + } +} From 7ab26a533238e770f19d65ae3b5ea3e917e0a8e1 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:30:37 -0300 Subject: [PATCH 174/343] added test cases for method areAllStorySlidesEditable --- .../LoadStoryFromStoriesPrefsUseCaseTest.kt | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt index a4608bdd348c..48856e2b7567 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt @@ -76,6 +76,66 @@ class LoadStoryFromStoriesPrefsUseCaseTest { Assertions.assertThat(result).isTrue() } + @Test + fun `verify all story slides are editable with local ids`() { + // Given + val mediaIdsLocal = setupTestSildes(sayValid = true, useTempPrefix = false, useRemoteId = false) + + // When + val result = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(siteModel, mediaIdsLocal) + + // Then + Assertions.assertThat(result).isTrue() + } + + @Test + fun `verify all story slides are editable with remote ids`() { + // Given + val mediaIdsLocal = setupTestSildes(sayValid = true, useTempPrefix = false, useRemoteId = true) + + // When + val result = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(siteModel, mediaIdsLocal) + + // Then + Assertions.assertThat(result).isTrue() + } + + @Test + fun `verify not all story slides are editable with temporary ids`() { + // Given + val mediaIdsLocal = setupTestSildes(sayValid = false, useTempPrefix = true, useRemoteId = false) + + // When + val result = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(siteModel, mediaIdsLocal) + + // Then + Assertions.assertThat(result).isFalse() + } + + @Test + fun `verify not all story slides are editable with remote ids`() { + // Given + val mediaIdsLocal = setupTestSildes(sayValid = false, useTempPrefix = false, useRemoteId = true) + + // When + val result = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(siteModel, mediaIdsLocal) + + // Then + Assertions.assertThat(result).isFalse() + } + + @Test + fun `verify not all story slides are editable with local ids`() { + // Given + val mediaIdsLocal = setupTestSildes(sayValid = false, useTempPrefix = false, useRemoteId = false) + + // When + val result = loadStoryFromStoriesPrefsUseCase.areAllStorySlidesEditable(siteModel, mediaIdsLocal) + + // Then + Assertions.assertThat(result).isFalse() + } + private fun setupMediaFiles( emptyList: Boolean ): ArrayList> { @@ -104,15 +164,15 @@ class LoadStoryFromStoriesPrefsUseCaseTest { val mediaIds = ArrayList() for (i in 1..10) { - mediaIds.add((if (useTempPrefix) TEMPORARY_ID_PREFIX else "") + i.toString()) - } - - if (useTempPrefix) { - whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), mock())).thenReturn(sayValid) - } else if (useRemoteId) { - whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), mock())).thenReturn(sayValid) - } else { - whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), mock())).thenReturn(sayValid) + val mediaId = (if (useTempPrefix) TEMPORARY_ID_PREFIX else "") + i.toString() + mediaIds.add(mediaId) + if (useTempPrefix) { + whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), TempId(mediaId))).thenReturn(sayValid) + } else if (useRemoteId) { + whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), RemoteId(mediaId.toLong()))).thenReturn(sayValid) + } else { + whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), LocalId(mediaId.toInt()))).thenReturn(sayValid) + } } return mediaIds From d5d5731bdac77f525d8a05c6ddc5c36cb40b6164 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:37:18 -0300 Subject: [PATCH 175/343] removed unused imports --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt index 48856e2b7567..c62f61452dd7 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt @@ -1,7 +1,6 @@ package org.wordpress.android.ui.stories.usecase import android.content.Context -import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.whenever import org.assertj.core.api.Assertions import org.junit.Before From 5e55e168f61e44be0c5ae1baaec9ce21c164cdae Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:40:49 -0300 Subject: [PATCH 176/343] removed commented line --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 4f321cea2ccf..0e7424acd391 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -538,7 +538,6 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), frame, it ) - // frame.id = storyMediaFileData.id storyMediaFileDataList.add(storyMediaFileData) } else { val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) From 4d1eaa16d6fe7b754da2380a761d21bba613e722 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:42:27 -0300 Subject: [PATCH 177/343] giving parameter explicit name --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 0e7424acd391..b75ecef1f1b8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -542,10 +542,10 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } else { val mediaModel = mediaStore.getSiteMediaWithId(site, it.toLong()) val mediaFile = fluxCUtilsWrapper.mediaFileFromMediaModel(mediaModel) - mediaFile?.let { + mediaFile?.let { mediafile -> val storyMediaFileData = saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( - mediaFile = it, + mediaFile = mediafile, temporaryId = assignedTempId ) frame.id = storyMediaFileData.id From 6bb0e31cf37b9e48aa900087e4494e1c95cbec41 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:47:06 -0300 Subject: [PATCH 178/343] removed unnecessary val declaration --- .../org/wordpress/android/ui/stories/StoryComposerActivity.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index b75ecef1f1b8..9ccdb705ae61 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -560,8 +560,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } private fun buildStoryMediaFileDataForTemporarySlide(frame: StoryFrameItem, tempId: String): StoryMediaFileData { - val storyMediaFileData = - saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryIdNoMediaFile( + return saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryIdNoMediaFile( temporaryId = tempId, url = if (frame.source is FileBackgroundSource) { (frame.source as FileBackgroundSource).file.toString() @@ -570,7 +569,6 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), }, isVideo = (frame.frameItemType is VIDEO) ) - return storyMediaFileData } override fun onSubmitButtonClicked(publishPost: PublishPost) { From 9b80af491ae999fd001ca4df65e615af7be255d0 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:53:12 -0300 Subject: [PATCH 179/343] renamed _Phase1 and _Phase2 postfixed method names and added comments to illustrate how id assignment for media works in two phases --- .../wordpress/android/ui/posts/EditPostActivity.java | 2 +- .../ui/stories/SaveStoryGutenbergBlockUseCase.kt | 2 +- .../ui/stories/media/StoryMediaSaveUploadBridge.kt | 2 +- .../wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 10 ++++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index cc26947a5ace..982d90addf21 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -1685,7 +1685,7 @@ private void onUploadSuccess(MediaModel media) { // Also: we don't need to worry about checking if this mediaModel corresponds to a media upload // within a story block in this post: we will only replace items for which a local-keyed frame has // been created before, which can only happen when using the Story Creator. - mStoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide_Phase2( + mStoriesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( media.getId(), media.getMediaId(), mSite.getId() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt index 968d3063edc1..8dae05f35665 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCase.kt @@ -133,7 +133,7 @@ class SaveStoryGutenbergBlockUseCase @Inject constructor( // look for the slide saved with the local id key (mediaFile.id), and re-convert to // mediaId. - storiesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide_Phase2( + storiesPrefs.replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( mediaFile.id, mediaFile.mediaId.toLong(), postModel.localSiteId.toLong() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 11dc97eda47c..5b6c2fc6de9a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -148,7 +148,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( // if prefs has this Slide with the temporary key, replace it // if not, let's now save the new slide with the local key - storiesPrefs.replaceTempMediaIdKeyedSlideWithLocalMediaIdKeyedSlide_Phase1( + storiesPrefs.replaceTempMediaIdKeyedSlideWithLocalMediaIdKeyedSlide( TempId(oldTemporaryId), LocalId(it.id), it.localSiteId.toLong() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 87ceead46a61..a485fa2b088b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -206,7 +206,10 @@ class StoriesPrefs @Inject constructor( } } - fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide_Phase2( + // Phase 2: this method is likely used after a first phase in which a local media which only has a temporary id has + // then be replaced by a local id. At this point, we now have a remote Id and we can replace the local + // media id with the remote media id. + fun replaceLocalMediaIdKeyedSlideWithRemoteMediaIdKeyedSlide( localIdKey: Int, remoteIdKey: Long, localSiteId: Long @@ -230,7 +233,10 @@ class StoriesPrefs @Inject constructor( } } - fun replaceTempMediaIdKeyedSlideWithLocalMediaIdKeyedSlide_Phase1( + // Phase 1: this method is likely used at the beginning when a local media which only has a temporary id needs now + // to be assigned with a localMediaId. At a later point when the media is uploaded to the server, it will be + // assigned a remote Id which will replace this localId. + fun replaceTempMediaIdKeyedSlideWithLocalMediaIdKeyedSlide( tempId: TempId, localId: LocalId, localSiteId: Long From d7986d0f8c6152424c4f700bbd28af4252116649 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 21 Oct 2020 15:55:06 -0300 Subject: [PATCH 180/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt Co-authored-by: Joel Dean --- .../android/ui/stories/media/StoryMediaSaveUploadBridge.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index 5b6c2fc6de9a..51917328a1a5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -235,7 +235,7 @@ class StoryMediaSaveUploadBridge @Inject constructor( // yet. if (isStorySavingComplete(event) && !event.isRetry) { // only remove it if it was successful - we want to keep it and show a snackbar once when the user - // comes back to the app if it wasn't, see MySiteFrament for details. + // comes back to the app if it wasn't, see MySiteFragment for details. eventBusWrapper.removeStickyEvent(event) editPostRepository.loadPostByLocalPostId(it.getInt(StoryComposerActivity.KEY_POST_LOCAL_ID)) // media upload tracking already in addLocalMediaToPostUseCase.addNewMediaToEditorAsync From 695fed6d0b3d819d531ef6f8e3efba6d1795ee15 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:56:05 -0300 Subject: [PATCH 181/343] added const prefix --- .../wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index a485fa2b088b..adb44991713a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -18,11 +18,11 @@ class StoriesPrefs @Inject constructor( private val context: Context ) { companion object { - private val KEY_STORIES_SLIDE_INCREMENTAL_ID = "incremental_id" - private val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" - private val KEY_PREFIX_TEMP_MEDIA_ID = "t-" - private val KEY_PREFIX_LOCAL_MEDIA_ID = "l-" - private val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" + private const val KEY_STORIES_SLIDE_INCREMENTAL_ID = "incremental_id" + private const val KEY_PREFIX_STORIES_SLIDE_ID = "story_slide_id-" + private const val KEY_PREFIX_TEMP_MEDIA_ID = "t-" + private const val KEY_PREFIX_LOCAL_MEDIA_ID = "l-" + private const val KEY_PREFIX_REMOTE_MEDIA_ID = "r-" } private fun buildSlideKey(siteId: Long, mediaId: RemoteId): String { From 2bcc13e08d4c59e9aa6ff0ec8be21260c7376901 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 15:56:55 -0300 Subject: [PATCH 182/343] added private scope modifier to private fun --- .../java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index adb44991713a..f885dde01f71 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -51,7 +51,7 @@ class StoriesPrefs @Inject constructor( return currentIncrementalId } - fun getIncrementalTempId(): Long { + private fun getIncrementalTempId(): Long { return PreferenceManager.getDefaultSharedPreferences(context).getLong( KEY_STORIES_SLIDE_INCREMENTAL_ID, 0 From c5e23b3216ceee2c06495effbfc91961d2a098a3 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 21 Oct 2020 16:02:58 -0300 Subject: [PATCH 183/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt Co-authored-by: Joel Dean --- .../java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index f885dde01f71..98defa4ee552 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -63,7 +63,7 @@ class StoriesPrefs @Inject constructor( return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - fun checkSlideIdExists(siteId: Long, tempId: TempId): Boolean { + private fun checkSlideIdExists(siteId: Long, tempId: TempId): Boolean { val slideIdKey = buildSlideKey(siteId, tempId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } From dc19f996aea577808347af9277ffb26b06547065 Mon Sep 17 00:00:00 2001 From: mzorz Date: Wed, 21 Oct 2020 16:03:08 -0300 Subject: [PATCH 184/343] Update WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt Co-authored-by: Joel Dean --- .../java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 98defa4ee552..7a1ed8842c09 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -68,7 +68,7 @@ class StoriesPrefs @Inject constructor( return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - fun checkSlideIdExists(siteId: Long, localId: LocalId): Boolean { + private fun checkSlideIdExists(siteId: Long, localId: LocalId): Boolean { val slideIdKey = buildSlideKey(siteId, localId) return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } From 798da18e171d81b78ed5de155b073aea5172d09a Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 16:08:47 -0300 Subject: [PATCH 185/343] renamed parameters, simplified logic removing local val and modified scope for checkSlideOriginalBackgroundMediaExists set of methods --- .../android/ui/stories/prefs/StoriesPrefs.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt index 7a1ed8842c09..a96b2cdb8f48 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt @@ -73,19 +73,16 @@ class StoriesPrefs @Inject constructor( return PreferenceManager.getDefaultSharedPreferences(context).contains(slideIdKey) } - fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: RemoteId): Boolean { - val storyFrameItem: StoryFrameItem? = getSlideWithRemoteId(siteId, mediaId) - return checkSlideOriginalBackgroundMediaExists(storyFrameItem) + private fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: RemoteId): Boolean { + return checkSlideOriginalBackgroundMediaExists(getSlideWithRemoteId(siteId, mediaId)) } - fun checkSlideOriginalBackgroundMediaExists(siteId: Long, tempId: TempId): Boolean { - val storyFrameItem: StoryFrameItem? = getSlideWithTempId(siteId, tempId) - return checkSlideOriginalBackgroundMediaExists(storyFrameItem) + private fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: TempId): Boolean { + return checkSlideOriginalBackgroundMediaExists(getSlideWithTempId(siteId, mediaId)) } - fun checkSlideOriginalBackgroundMediaExists(siteId: Long, localId: LocalId): Boolean { - val storyFrameItem: StoryFrameItem? = getSlideWithLocalId(siteId, localId) - return checkSlideOriginalBackgroundMediaExists(storyFrameItem) + private fun checkSlideOriginalBackgroundMediaExists(siteId: Long, mediaId: LocalId): Boolean { + return checkSlideOriginalBackgroundMediaExists(getSlideWithLocalId(siteId, mediaId)) } private fun checkSlideOriginalBackgroundMediaExists(storyFrameItem: StoryFrameItem?): Boolean { From 1f36898147e2699febe0c063ba3ba82a323ea74c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 16:53:25 -0300 Subject: [PATCH 186/343] updated gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index ed36dfc682ec..ba4df52f4482 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit ed36dfc682ec2203fe8f7b9995ca0dc94b90065d +Subproject commit ba4df52f44826817d29f3a3c255963b062b2fde8 From fa5e87a9cd43a807034832220240a1dda5ea4750 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 17:04:31 -0300 Subject: [PATCH 187/343] removed unused var and import --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt index c62f61452dd7..3220ace9b852 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt @@ -12,7 +12,6 @@ import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.MediaStore -import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX import org.wordpress.android.ui.stories.StoryRepositoryWrapper import org.wordpress.android.ui.stories.prefs.StoriesPrefs @@ -25,7 +24,6 @@ class LoadStoryFromStoriesPrefsUseCaseTest { @Mock lateinit var mediaStore: MediaStore @Mock lateinit var storiesPrefs: StoriesPrefs @Mock lateinit var context: Context - @Mock lateinit var postRepository: EditPostRepository private lateinit var siteModel: SiteModel @Before From 74c62e73ebe7566a324b7d4abf02dbe42b222d07 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 18:43:32 -0300 Subject: [PATCH 188/343] renamed site name in test example --- .../ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt index 3220ace9b852..58cac5ff0b71 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt @@ -143,7 +143,7 @@ class LoadStoryFromStoriesPrefsUseCaseTest { for (i in 1..10) { val oneMediaFile = HashMap() oneMediaFile.put("mime", "image/jpeg") - oneMediaFile.put("link", "https://testmzorz3.files.wordpress.com/2020/10/wp-0000000.jpg") + oneMediaFile.put("link", "https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg") oneMediaFile.put("type", "image") oneMediaFile.put("id", i.toString()) mediaFiles.add(oneMediaFile) From f53da473f74f03466620dac4ff3e7c63d9bdf165 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 18:43:56 -0300 Subject: [PATCH 189/343] added unit tests for SaveStoryGutenbergBlockUseCase class --- .../SaveStoryGutenbergBlockUseCaseTest.kt | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt new file mode 100644 index 000000000000..82ece3fc522c --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -0,0 +1,128 @@ +package org.wordpress.android.ui.stories + +import android.content.Context +import com.nhaarman.mockitokotlin2.mock +import kotlinx.coroutines.InternalCoroutinesApi +import org.assertj.core.api.Assertions +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner +import org.wordpress.android.BaseUnitTest +import org.wordpress.android.TEST_DISPATCHER +import org.wordpress.android.fluxc.model.PostModel +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.PostStore +import org.wordpress.android.ui.posts.EditPostRepository +import org.wordpress.android.ui.stories.prefs.StoriesPrefs +import org.wordpress.android.util.helpers.MediaFile + +@RunWith(MockitoJUnitRunner::class) +class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { + private lateinit var saveStoryGutenbergBlockUseCase: SaveStoryGutenbergBlockUseCase + private lateinit var editPostRepository: EditPostRepository + private lateinit var siteModel: SiteModel + @Mock lateinit var storiesPrefs: StoriesPrefs + @Mock lateinit var context: Context + @Mock lateinit var postStore: PostStore + + @InternalCoroutinesApi + @Before + fun setUp() { + saveStoryGutenbergBlockUseCase = SaveStoryGutenbergBlockUseCase(storiesPrefs) + siteModel = SiteModel() + editPostRepository = EditPostRepository( + mock(), + postStore, + mock(), + TEST_DISPATCHER, + TEST_DISPATCHER + ) + } + + @Test + fun `post with empty Story block is set given an empty mediaFiles array`() { + // Given + val mediaFiles: ArrayList = setupFluxCMediaFiles(emptyList = true) + editPostRepository.set { PostModel() } + + // When + saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( + editPostRepository, + mediaFiles + ) + + // Then + Assertions.assertThat(editPostRepository.content).isEqualTo(blockWithEmptyMediaFiles) + } + + @Test + fun `post with non-empty Story block is set given a non-empty mediaFiles array`() { + // Given + val mediaFiles: ArrayList = setupFluxCMediaFiles(emptyList = false) + editPostRepository.set { PostModel() } + + // When + saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockInPost( + editPostRepository, + mediaFiles + ) + + // Then + Assertions.assertThat(editPostRepository.content).isEqualTo(blockWithNonEmptyMediaFiles) + } + + private fun setupFluxCMediaFiles( + emptyList: Boolean + ): ArrayList { + when (emptyList) { + true -> return ArrayList() + false -> { + val mediaFiles = ArrayList() + for (i in 1..10) { + val oneMediaFile = MediaFile() + oneMediaFile.id = i + oneMediaFile.mediaId = (i + 1000).toString() + oneMediaFile.mimeType = "image/jpeg" + oneMediaFile.fileURL = "https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg" + mediaFiles.add(oneMediaFile) + } + return mediaFiles + } + } + } + + companion object { + private const val blockWithEmptyMediaFiles = "\n" + + "
\n" + + "" + private const val blockWithNonEmptyMediaFiles = "\n" + + "
\n" + + "" + } +} From 83ee3c286580c23bb4b6c5181a5b607f7fdd8c7c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 19:10:48 -0300 Subject: [PATCH 190/343] added more tests --- .../SaveStoryGutenbergBlockUseCaseTest.kt | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index 82ece3fc522c..b18fc5ca373f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -15,6 +15,8 @@ import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.PostStore import org.wordpress.android.ui.posts.EditPostRepository +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX +import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.StoryMediaFileData import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.util.helpers.MediaFile @@ -73,6 +75,55 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { Assertions.assertThat(editPostRepository.content).isEqualTo(blockWithNonEmptyMediaFiles) } + @Test + fun `builds non-empty story block string from non-empty mediaFiles array`() { + // Given + val mediaFileDataList: ArrayList = setupMediaFileDataList(emptyList = false) + + // When + val result = saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockStringFromStoryMediaFileData( + mediaFileDataList + ) + + // Then + Assertions.assertThat(result).isEqualTo(blockWithNonEmptyMediaFiles) + } + + @Test + fun `builds empty story block string from empty mediaFiles array`() { + // Given + val mediaFileDataList: ArrayList = setupMediaFileDataList(emptyList = true) + + // When + val result = saveStoryGutenbergBlockUseCase.buildJetpackStoryBlockStringFromStoryMediaFileData( + mediaFileDataList + ) + + // Then + Assertions.assertThat(result).isEqualTo(blockWithEmptyMediaFiles) + } + + @Test + fun `obtain a mediaFileData from a MediaFile with a temporary id`() { + // Given + val mediaFile = getOneMediaFile(1) + + // When + val mediaFileData = saveStoryGutenbergBlockUseCase.buildMediaFileDataWithTemporaryId( + mediaFile, + TEMPORARY_ID_PREFIX + 1 + ) + + // Then + Assertions.assertThat(mediaFileData.alt).isEqualTo("") + Assertions.assertThat(mediaFileData.id).isEqualTo(TEMPORARY_ID_PREFIX + 1) + Assertions.assertThat(mediaFileData.link).isEqualTo("https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg") + Assertions.assertThat(mediaFileData.type).isEqualTo("image") + Assertions.assertThat(mediaFileData.mime).isEqualTo(mediaFile.mimeType) + Assertions.assertThat(mediaFileData.caption).isEqualTo("") + Assertions.assertThat(mediaFileData.url).isEqualTo(mediaFile.fileURL) + } + private fun setupFluxCMediaFiles( emptyList: Boolean ): ArrayList { @@ -93,6 +144,39 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { } } + private fun getOneMediaFile(id: Int): MediaFile { + val oneMediaFile = MediaFile() + oneMediaFile.id = id + oneMediaFile.mediaId = (id + 1000).toString() + oneMediaFile.mimeType = "image/jpeg" + oneMediaFile.fileURL = "https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg" + return oneMediaFile + } + + private fun setupMediaFileDataList( + emptyList: Boolean + ): ArrayList { + when (emptyList) { + true -> return ArrayList() + false -> { + val mediaFiles = ArrayList() + for (i in 1..10) { + val oneMediaFile = StoryMediaFileData( + id = i.toString(), + mime = "image/jpeg", + link = "https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg", + url = "https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg", + alt = "", + type = "image", + caption = "" + ) + mediaFiles.add(oneMediaFile) + } + return mediaFiles + } + } + } + companion object { private const val blockWithEmptyMediaFiles = "\n" + "
\n" + From c59fa66ead937a600231ff94dc83238794bc31dd Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Wed, 21 Oct 2020 19:22:04 -0300 Subject: [PATCH 191/343] added test case --- .../SaveStoryGutenbergBlockUseCaseTest.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index b18fc5ca373f..49b80127192f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -124,6 +124,23 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { Assertions.assertThat(mediaFileData.url).isEqualTo(mediaFile.fileURL) } + @Test + fun `local media id is found and gets replaced with remote media id`() { + // Given + val mediaFile = getOneMediaFile(1) + val onePost = PostModel() + onePost.setContent(blockWithNonEmptyMediaFiles) + + // When + saveStoryGutenbergBlockUseCase.replaceLocalMediaIdsWithRemoteMediaIdsInPost( + onePost, + mediaFile + ) + + // Then + Assertions.assertThat(onePost.content).isEqualTo(blockWithNonEmptyMediaFilesWithOneRemoteId) + } + private fun setupFluxCMediaFiles( emptyList: Boolean ): ArrayList { @@ -208,5 +225,33 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { "ption\":\"\",\"url\":\"https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg\"}]} -->\n" + "
\n" + "" + private const val blockWithNonEmptyMediaFilesWithOneRemoteId = "\n" + + "
\n" + + "" + } } From 773495a9b684db9254bd1a0c6199219d31746437 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 08:59:01 -0300 Subject: [PATCH 192/343] added failing test for review --- .../SaveStoryGutenbergBlockUseCaseTest.kt | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index 49b80127192f..d336ac9bd54f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -1,12 +1,21 @@ package org.wordpress.android.ui.stories import android.content.Context +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.times +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import com.wordpress.stories.compose.frame.StorySaveEvents.SaveResultReason.SaveSuccess +import com.wordpress.stories.compose.story.StoryFrameItem +import com.wordpress.stories.compose.story.StoryFrameItemType.IMAGE import kotlinx.coroutines.InternalCoroutinesApi import org.assertj.core.api.Assertions import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.junit.MockitoJUnitRunner import org.wordpress.android.BaseUnitTest @@ -141,6 +150,32 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { Assertions.assertThat(onePost.content).isEqualTo(blockWithNonEmptyMediaFilesWithOneRemoteId) } + @Test + fun `slides are saved locally`() { + // Given + val frames = ArrayList() + frames.add(getOneStoryFrameItem("1")) + frames.add(getOneStoryFrameItem("2")) + frames.add(getOneStoryFrameItem("3")) +// frames.add(mock()) +// frames.add(mock()) +// frames.add(mock()) + whenever(storiesPrefs.saveSlideWithTempId(ArgumentMatchers.anyLong(), any(), any())) + .then { } + + // When + saveStoryGutenbergBlockUseCase.saveNewLocalFilesToStoriesPrefsTempSlides( + siteModel, + 0, + frames + ) + + // Then + // eq(expectedPostId.value) + verify(storiesPrefs, times(3)).saveSlideWithTempId(siteModel.siteId, mock(), mock()) + // Assertions.assertThat(onePost.content).isEqualTo(blockWithNonEmptyMediaFilesWithOneRemoteId) + } + private fun setupFluxCMediaFiles( emptyList: Boolean ): ArrayList { @@ -170,6 +205,16 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { return oneMediaFile } + private fun getOneStoryFrameItem(id: String): StoryFrameItem { + return StoryFrameItem( + source = mock(), + frameItemType = IMAGE, + saveResultReason = SaveSuccess, + composedFrameFile = mock(), + id = id + ) + } + private fun setupMediaFileDataList( emptyList: Boolean ): ArrayList { @@ -252,6 +297,5 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { "ption\":\"\",\"url\":\"https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg\"}]} -->\n" + "
\n" + "" - } } From 77cb234d2990e88ab88287a6f88d12a197521641 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 17:20:16 -0300 Subject: [PATCH 193/343] fixed test, added needed test dependency --- WordPress/build.gradle | 1 + .../stories/SaveStoryGutenbergBlockUseCaseTest.kt | 14 ++------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 1b74daef3347..73548a0781af 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -184,6 +184,7 @@ androidExtensions { dependencies { implementation project(path:':libs:stories-android:stories') + testImplementation project(path:':photoeditor') implementation project(path:':libs:image-editor::ImageEditor') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index d336ac9bd54f..f81599aa368a 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -151,17 +151,12 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { } @Test - fun `slides are saved locally`() { + fun `slides are saved locally to storiedPrefs`() { // Given val frames = ArrayList() frames.add(getOneStoryFrameItem("1")) frames.add(getOneStoryFrameItem("2")) frames.add(getOneStoryFrameItem("3")) -// frames.add(mock()) -// frames.add(mock()) -// frames.add(mock()) - whenever(storiesPrefs.saveSlideWithTempId(ArgumentMatchers.anyLong(), any(), any())) - .then { } // When saveStoryGutenbergBlockUseCase.saveNewLocalFilesToStoriesPrefsTempSlides( @@ -171,9 +166,7 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { ) // Then - // eq(expectedPostId.value) - verify(storiesPrefs, times(3)).saveSlideWithTempId(siteModel.siteId, mock(), mock()) - // Assertions.assertThat(onePost.content).isEqualTo(blockWithNonEmptyMediaFilesWithOneRemoteId) + verify(storiesPrefs, times(3)).saveSlideWithTempId(any(), any(), any()) } private fun setupFluxCMediaFiles( @@ -208,9 +201,6 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { private fun getOneStoryFrameItem(id: String): StoryFrameItem { return StoryFrameItem( source = mock(), - frameItemType = IMAGE, - saveResultReason = SaveSuccess, - composedFrameFile = mock(), id = id ) } From d9ed477b947dfbfe142f91751c8f4ee489012c31 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 17:34:47 -0300 Subject: [PATCH 194/343] fixed lint warnings --- .../SaveStoryGutenbergBlockUseCaseTest.kt | 11 ++++------ .../LoadStoryFromStoriesPrefsUseCaseTest.kt | 21 +++++++++++++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index f81599aa368a..bf7dd8f8a157 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -2,20 +2,15 @@ package org.wordpress.android.ui.stories import android.content.Context import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.whenever -import com.wordpress.stories.compose.frame.StorySaveEvents.SaveResultReason.SaveSuccess import com.wordpress.stories.compose.story.StoryFrameItem -import com.wordpress.stories.compose.story.StoryFrameItemType.IMAGE import kotlinx.coroutines.InternalCoroutinesApi import org.assertj.core.api.Assertions import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.junit.MockitoJUnitRunner import org.wordpress.android.BaseUnitTest @@ -30,7 +25,7 @@ import org.wordpress.android.ui.stories.prefs.StoriesPrefs import org.wordpress.android.util.helpers.MediaFile @RunWith(MockitoJUnitRunner::class) -class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { +class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { private lateinit var saveStoryGutenbergBlockUseCase: SaveStoryGutenbergBlockUseCase private lateinit var editPostRepository: EditPostRepository private lateinit var siteModel: SiteModel @@ -126,7 +121,9 @@ class SaveStoryGutenbergBlockUseCaseTest: BaseUnitTest() { // Then Assertions.assertThat(mediaFileData.alt).isEqualTo("") Assertions.assertThat(mediaFileData.id).isEqualTo(TEMPORARY_ID_PREFIX + 1) - Assertions.assertThat(mediaFileData.link).isEqualTo("https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg") + Assertions.assertThat(mediaFileData.link).isEqualTo( + "https://testsite.files.wordpress.com/2020/10/wp-0000000.jpg" + ) Assertions.assertThat(mediaFileData.type).isEqualTo("image") Assertions.assertThat(mediaFileData.mime).isEqualTo(mediaFile.mimeType) Assertions.assertThat(mediaFileData.caption).isEqualTo("") diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt index 58cac5ff0b71..374850180a4f 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt @@ -42,7 +42,9 @@ class LoadStoryFromStoriesPrefsUseCaseTest { val mediaFiles: ArrayList> = setupMediaFiles(emptyList = true) // When - val mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles as ArrayList) + val mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles( + mediaFiles as ArrayList + ) // Then Assertions.assertThat(mediaIds).isEmpty() @@ -54,7 +56,9 @@ class LoadStoryFromStoriesPrefsUseCaseTest { val mediaFiles: ArrayList> = setupMediaFiles(emptyList = false) // When - val mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles(mediaFiles as ArrayList) + val mediaIds = loadStoryFromStoriesPrefsUseCase.getMediaIdsFromStoryBlockBridgeMediaFiles( + mediaFiles as ArrayList + ) // Then Assertions.assertThat(mediaIds).isNotEmpty @@ -166,9 +170,18 @@ class LoadStoryFromStoriesPrefsUseCaseTest { if (useTempPrefix) { whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), TempId(mediaId))).thenReturn(sayValid) } else if (useRemoteId) { - whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), RemoteId(mediaId.toLong()))).thenReturn(sayValid) + whenever( + storiesPrefs.isValidSlide( + siteModel.id.toLong(), + RemoteId(mediaId.toLong()) + ) + ).thenReturn(sayValid) } else { - whenever(storiesPrefs.isValidSlide(siteModel.id.toLong(), LocalId(mediaId.toInt()))).thenReturn(sayValid) + whenever(storiesPrefs.isValidSlide( + siteModel.id.toLong(), + LocalId(mediaId.toInt()) + ) + ).thenReturn(sayValid) } } From f8fb3048a9f259a179cba76a820223e614f9a8f5 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 18:01:38 -0300 Subject: [PATCH 195/343] removed unused resources --- WordPress/src/main/res/values/strings.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 6f47ea49191f..7708031650ca 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2910,7 +2910,6 @@ Flip camera Flash Stickers - More Text Sound Flip @@ -2921,8 +2920,6 @@ Saved to photos SHARE Share to - Done - Next Close Saved Retry @@ -2932,7 +2929,6 @@ errored Change text alignment Change text color - Delete slide Delete story slide? This slide will be removed from your story. This slide has not been saved yet. If you delete this slide, you will lose any edits you have made. From 875c8aaa277de2962e8bdddd3fb05675d407632c Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 18:12:06 -0300 Subject: [PATCH 196/343] removed unused resource in stories library --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 621c36488b99..952e70540fa2 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 621c36488b999208617b9e72b3291f4a83c02d0a +Subproject commit 952e70540fa295a7f0458804d5fd33350c5d5417 From 6b27610f4efae24eaa5e350cae772954921870cb Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 18:29:21 -0300 Subject: [PATCH 197/343] mocking siteModel --- .../android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index bf7dd8f8a157..f5d4c160ce94 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -16,7 +16,6 @@ import org.mockito.junit.MockitoJUnitRunner import org.wordpress.android.BaseUnitTest import org.wordpress.android.TEST_DISPATCHER import org.wordpress.android.fluxc.model.PostModel -import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.PostStore import org.wordpress.android.ui.posts.EditPostRepository import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX @@ -28,7 +27,6 @@ import org.wordpress.android.util.helpers.MediaFile class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { private lateinit var saveStoryGutenbergBlockUseCase: SaveStoryGutenbergBlockUseCase private lateinit var editPostRepository: EditPostRepository - private lateinit var siteModel: SiteModel @Mock lateinit var storiesPrefs: StoriesPrefs @Mock lateinit var context: Context @Mock lateinit var postStore: PostStore @@ -37,7 +35,6 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { @Before fun setUp() { saveStoryGutenbergBlockUseCase = SaveStoryGutenbergBlockUseCase(storiesPrefs) - siteModel = SiteModel() editPostRepository = EditPostRepository( mock(), postStore, @@ -157,7 +154,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { // When saveStoryGutenbergBlockUseCase.saveNewLocalFilesToStoriesPrefsTempSlides( - siteModel, + mock(), 0, frames ) From 0575171172ca47dbbb42e376bc3d469e7529a930 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Thu, 22 Oct 2020 18:31:05 -0300 Subject: [PATCH 198/343] applying naming convention for constants :) --- .../SaveStoryGutenbergBlockUseCaseTest.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt index f5d4c160ce94..4b499a9e1f35 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/SaveStoryGutenbergBlockUseCaseTest.kt @@ -57,7 +57,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { ) // Then - Assertions.assertThat(editPostRepository.content).isEqualTo(blockWithEmptyMediaFiles) + Assertions.assertThat(editPostRepository.content).isEqualTo(BLOCK_WITH_EMPTY_MEDIA_FILES) } @Test @@ -73,7 +73,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { ) // Then - Assertions.assertThat(editPostRepository.content).isEqualTo(blockWithNonEmptyMediaFiles) + Assertions.assertThat(editPostRepository.content).isEqualTo(BLOCK_WITH_NON_EMPTY_MEDIA_FILES) } @Test @@ -87,7 +87,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { ) // Then - Assertions.assertThat(result).isEqualTo(blockWithNonEmptyMediaFiles) + Assertions.assertThat(result).isEqualTo(BLOCK_WITH_NON_EMPTY_MEDIA_FILES) } @Test @@ -101,7 +101,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { ) // Then - Assertions.assertThat(result).isEqualTo(blockWithEmptyMediaFiles) + Assertions.assertThat(result).isEqualTo(BLOCK_WITH_EMPTY_MEDIA_FILES) } @Test @@ -132,7 +132,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { // Given val mediaFile = getOneMediaFile(1) val onePost = PostModel() - onePost.setContent(blockWithNonEmptyMediaFiles) + onePost.setContent(BLOCK_WITH_NON_EMPTY_MEDIA_FILES) // When saveStoryGutenbergBlockUseCase.replaceLocalMediaIdsWithRemoteMediaIdsInPost( @@ -141,7 +141,7 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { ) // Then - Assertions.assertThat(onePost.content).isEqualTo(blockWithNonEmptyMediaFilesWithOneRemoteId) + Assertions.assertThat(onePost.content).isEqualTo(BLOCK_WITH_NON_EMPTY_MEDIA_FILES_WITH_ONE_REMOTE_ID) } @Test @@ -224,10 +224,10 @@ class SaveStoryGutenbergBlockUseCaseTest : BaseUnitTest() { } companion object { - private const val blockWithEmptyMediaFiles = "\n" + + private const val BLOCK_WITH_EMPTY_MEDIA_FILES = "\n" + "
\n" + "" - private const val blockWithNonEmptyMediaFiles = "\n" + "
\n" + "" - private const val blockWithNonEmptyMediaFilesWithOneRemoteId = " + app:theme="@style/Widget.LoginFlow.TextInputLayout"> diff --git a/WordPressLoginFlow/src/main/res/layout/login_email_optional_site_creds_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_email_optional_site_creds_screen.xml new file mode 100644 index 000000000000..756018137097 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/layout/login_email_optional_site_creds_screen.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/layout/login_email_password_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_email_password_screen.xml index 8053752cf6e6..5ac4d2f64fce 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_email_password_screen.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_email_password_screen.xml @@ -4,77 +4,66 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginBottom="@dimen/margin_extra_large" android:orientation="vertical" - android:paddingStart="@dimen/margin_extra_large" - android:paddingLeft="@dimen/margin_extra_large" android:paddingEnd="@dimen/margin_extra_large" - android:paddingRight="@dimen/margin_extra_large"> + android:paddingStart="@dimen/margin_extra_large"> - + android:layout_marginBottom="@dimen/margin_medium" + layout="@layout/login_include_email_header" /> - - - - - - - - - - - + android:layout_marginTop="@dimen/margin_extra_large" + android:visibility="gone" + tools:text="@string/enter_wpcom_password" + tools:visibility="visible" /> + + + + + + diff --git a/WordPressLoginFlow/src/main/res/layout/login_email_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_email_screen.xml index 53e8e3050019..ba182e221a8c 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_email_screen.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_email_screen.xml @@ -1,109 +1,141 @@ - - + android:layout_height="match_parent"> - + android:layout_height="wrap_content"> - + + + + - + - + - + - + android:layout_marginBottom="@dimen/margin_small_medium" + app:layout_constraintTop_toBottomOf="@+id/spacer" + app:layout_constraintBottom_toTopOf="@+id/continue_with_google" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintVertical_bias="1.0"> - + - + - + - + + + android:layout_marginStart="@dimen/margin_extra_large" + android:layout_marginEnd="@dimen/margin_extra_large" + android:text="@string/continue_with_google_terms_of_service_text" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> - + - + diff --git a/WordPressLoginFlow/src/main/res/layout/login_email_screen_old.xml b/WordPressLoginFlow/src/main/res/layout/login_email_screen_old.xml new file mode 100644 index 000000000000..2844ae06d7b5 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/layout/login_email_screen_old.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/layout/login_form_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_form_screen.xml index 0621b1cc49cb..9898c35cdc60 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_form_screen.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_form_screen.xml @@ -8,61 +8,57 @@ android:id="@+id/toolbar_container" layout="@layout/toolbar_login" /> - + android:layout_below="@+id/toolbar_container" + android:fillViewport="true"> - - + android:layout_height="match_parent" + android:layout_marginBottom="@dimen/margin_extra_large" + android:layout_marginTop="@dimen/margin_extra_large" /> + - + android:gravity="center_vertical"> - + android:layout_marginBottom="@dimen/margin_medium_large" + android:layout_marginEnd="@dimen/margin_extra_large" + android:layout_marginStart="@dimen/margin_extra_large" + android:layout_marginTop="@dimen/margin_medium_large" + android:text="@string/login_continue" /> + diff --git a/WordPressLoginFlow/src/main/res/layout/login_include_email_header.xml b/WordPressLoginFlow/src/main/res/layout/login_include_email_header.xml new file mode 100644 index 000000000000..544cc2ea24f0 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/layout/login_include_email_header.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/layout/login_input_row.xml b/WordPressLoginFlow/src/main/res/layout/login_input_row.xml index d39dcbb868ea..426cdf5c2ab2 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_input_row.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_input_row.xml @@ -8,7 +8,7 @@ - - - - - + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingEnd="@dimen/margin_extra_large" + android:paddingStart="@dimen/margin_extra_large"> - - + - - + android:paddingEnd="@dimen/margin_none" + android:paddingStart="@dimen/margin_none" + android:text="@string/or_type_your_password" /> - + + + diff --git a/WordPressLoginFlow/src/main/res/layout/login_magic_link_sent_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_magic_link_sent_screen.xml index 9597cd511515..f9192b18a9eb 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_magic_link_sent_screen.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_magic_link_sent_screen.xml @@ -1,44 +1,66 @@ - - - + android:layout_height="0dp" + android:layout_weight="1" + android:overScrollMode="never"> - + + + + + + + + + + + + + + android:layout_marginBottom="@dimen/margin_medium_large" + android:layout_marginEnd="@dimen/margin_extra_large" + android:layout_marginStart="@dimen/margin_extra_large" + android:layout_marginTop="@dimen/margin_medium_large" + android:text="@string/check_email" /> diff --git a/WordPressLoginFlow/src/main/res/layout/login_site_address_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_site_address_screen.xml index 596cd8e6f07c..fc5c3dd0ac28 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_site_address_screen.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_site_address_screen.xml @@ -3,31 +3,32 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/margin_extra_large" android:orientation="vertical" - android:paddingStart="@dimen/margin_extra_large" - android:paddingLeft="@dimen/margin_extra_large" android:paddingEnd="@dimen/margin_extra_large" - android:paddingRight="@dimen/margin_extra_large"> + android:paddingStart="@dimen/margin_extra_large"> + android:inputType="textUri" /> + + diff --git a/WordPressLoginFlow/src/main/res/layout/login_username_password_screen.xml b/WordPressLoginFlow/src/main/res/layout/login_username_password_screen.xml index f2b142305cca..890485da7d78 100644 --- a/WordPressLoginFlow/src/main/res/layout/login_username_password_screen.xml +++ b/WordPressLoginFlow/src/main/res/layout/login_username_password_screen.xml @@ -4,121 +4,42 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/margin_extra_large" android:orientation="vertical" - android:paddingStart="@dimen/margin_extra_large" - android:paddingLeft="@dimen/margin_extra_large" android:paddingEnd="@dimen/margin_extra_large" - android:paddingRight="@dimen/margin_extra_large"> + android:paddingStart="@dimen/margin_extra_large"> - - - - - - - - - - - - - - - - - - - + tools:text="Enter your account information for pamelanguyen.com." /> + android:inputType="textPersonName" /> + app:passwordToggleEnabled="true" /> + + + diff --git a/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml b/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml index 87605487db46..d27de169e604 100644 --- a/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml +++ b/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml @@ -14,7 +14,7 @@ + + + + + + + + + + + + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/layout/signup_email_fragment.xml b/WordPressLoginFlow/src/main/res/layout/signup_email_screen.xml similarity index 71% rename from WordPressLoginFlow/src/main/res/layout/signup_email_fragment.xml rename to WordPressLoginFlow/src/main/res/layout/signup_email_screen.xml index 50061cf17ecd..2741f5e27014 100644 --- a/WordPressLoginFlow/src/main/res/layout/signup_email_fragment.xml +++ b/WordPressLoginFlow/src/main/res/layout/signup_email_screen.xml @@ -1,23 +1,18 @@ - + android:paddingStart="@dimen/margin_extra_large"> @@ -28,7 +23,6 @@ android:hint="@string/email_address" android:imeOptions="actionNext" android:importantForAutofill="noExcludeDescendants" - android:inputType="textEmailAddress" - tools:ignore="UnusedAttribute" /> + android:inputType="textEmailAddress" /> diff --git a/WordPressLoginFlow/src/main/res/layout/signup_magic_link.xml b/WordPressLoginFlow/src/main/res/layout/signup_magic_link.xml deleted file mode 100644 index 951fa81b7e9d..000000000000 --- a/WordPressLoginFlow/src/main/res/layout/signup_magic_link.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/WordPressLoginFlow/src/main/res/layout/signup_magic_link_screen.xml b/WordPressLoginFlow/src/main/res/layout/signup_magic_link_screen.xml new file mode 100644 index 000000000000..ce723cdd451e --- /dev/null +++ b/WordPressLoginFlow/src/main/res/layout/signup_magic_link_screen.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/layout/toolbar_login.xml b/WordPressLoginFlow/src/main/res/layout/toolbar_login.xml index 695c90671df0..6e847604bf2f 100644 --- a/WordPressLoginFlow/src/main/res/layout/toolbar_login.xml +++ b/WordPressLoginFlow/src/main/res/layout/toolbar_login.xml @@ -7,15 +7,18 @@ + android:layout_height="wrap_content"> diff --git a/WordPressLoginFlow/src/main/res/menu/menu_login.xml b/WordPressLoginFlow/src/main/res/menu/menu_login.xml index 84886dd82216..4c34dcd5fb98 100644 --- a/WordPressLoginFlow/src/main/res/menu/menu_login.xml +++ b/WordPressLoginFlow/src/main/res/menu/menu_login.xml @@ -1,11 +1,11 @@ - - + app:iconTint="?attr/colorControlNormal" + app:showAsAction="always" /> diff --git a/WordPressLoginFlow/src/main/res/values-night/colors.xml b/WordPressLoginFlow/src/main/res/values-night/colors.xml new file mode 100644 index 000000000000..3a8511ee4e51 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/values-night/colors.xml @@ -0,0 +1,6 @@ + + + + #61FFFFFF + diff --git a/WordPressLoginFlow/src/main/res/values-night/themes.xml b/WordPressLoginFlow/src/main/res/values-night/themes.xml new file mode 100644 index 000000000000..f5296a5ea887 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/values-night/themes.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/values-v23/themes.xml b/WordPressLoginFlow/src/main/res/values-v23/themes.xml new file mode 100644 index 000000000000..c0fde8bb5167 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/values-v23/themes.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/WordPressLoginFlow/src/main/res/values-v27/styles.xml b/WordPressLoginFlow/src/main/res/values-v27/styles.xml deleted file mode 100644 index 03a359e2ae1f..000000000000 --- a/WordPressLoginFlow/src/main/res/values-v27/styles.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/WordPressLoginFlow/src/main/res/values-v27/themes.xml b/WordPressLoginFlow/src/main/res/values-v27/themes.xml new file mode 100644 index 000000000000..fc3d18102ebf --- /dev/null +++ b/WordPressLoginFlow/src/main/res/values-v27/themes.xml @@ -0,0 +1,10 @@ + + + + + diff --git a/WordPressLoginFlow/src/main/res/values/colors.xml b/WordPressLoginFlow/src/main/res/values/colors.xml index ec74e8753299..2412c4aca542 100644 --- a/WordPressLoginFlow/src/main/res/values/colors.xml +++ b/WordPressLoginFlow/src/main/res/values/colors.xml @@ -1,6 +1,24 @@ - + + #3582c4 + #2271b1 + #0a4b78 + + #c9356e + #8c1749 + + #d63638 + + #ffffff + #121212 + #000000 + + + #61121212 + + + - @color/status_bar - @color/login_background_color - @android:color/white + + - - @style/LoginTheme.BottomSheetDialogStyle - + - @style/LoginTheme.ToolBar + + - - - - - - + - - - - - - - - - - diff --git a/WordPressLoginFlow/src/main/res/values/themes.xml b/WordPressLoginFlow/src/main/res/values/themes.xml new file mode 100644 index 000000000000..10dc1f026ff7 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/values/themes.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + diff --git a/WordPressLoginFlow/src/main/res/values/types.xml b/WordPressLoginFlow/src/main/res/values/types.xml new file mode 100644 index 000000000000..cff70fb995c7 --- /dev/null +++ b/WordPressLoginFlow/src/main/res/values/types.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372aef5e24af05341d49695ee84e5f9b594659..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch literal 58695 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 delta 48522 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�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

%A83HV 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 z \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +64,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +75,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +105,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -105,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -134,27 +154,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282aa688..62bd9b9ccefe 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -8,14 +24,17 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +65,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +78,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line From 92456a47de8fd0f8f7c478511755ee951fdc156d Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Fri, 6 Nov 2020 15:50:18 -0300 Subject: [PATCH 244/343] Add missing listener implementation --- .../java/org/wordpress/android/ui/accounts/LoginActivity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java index d5d56126770e..d68025d7ffce 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java @@ -932,6 +932,10 @@ private void dismissSignupSheet() { return mDispatchingAndroidInjector; } + @Override public void startOver() { + // Not used in WordPress app + } + @Override public void showHelpFindingConnectedEmail() { // Not used in WordPress app From bb7dfbd6c67917c7568c6c4b2c702200ea8dc6dc Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 6 Nov 2020 15:53:05 -0300 Subject: [PATCH 245/343] updated commit hash for gutenberg-mobile and stories libraries --- libs/gutenberg-mobile | 2 +- libs/stories-android | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 43013f169fce..4725701212c1 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 43013f169fcefd07e3da622d7c5bd82da06e29bc +Subproject commit 4725701212c162b831c606dfe6d2acf4d583e363 diff --git a/libs/stories-android b/libs/stories-android index df51ba77abac..62e3ccd42661 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit df51ba77abac884c40aa5d38ec79c64405245f7a +Subproject commit 62e3ccd42661377b223bc6b1ad452b9f53705757 From 5970f7f816bc4a686af7851d73e069380568350c Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Fri, 6 Nov 2020 16:04:01 -0300 Subject: [PATCH 246/343] Add function to set step to UnifiedLoginTracker --- .../org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt index da510218447e..8affb8167a32 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/UnifiedLoginTracker.kt @@ -107,6 +107,10 @@ class UnifiedLoginTracker currentFlow = Flow.values().find { it.value == value } } + fun setStep(step: Step) { + currentStep = step + } + fun setFlowAndStep(flow: Flow, step: Step) { currentFlow = flow currentStep = step From f1848063061f9152ed56152fd42e00f1f019e2ee Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Fri, 6 Nov 2020 16:05:50 -0300 Subject: [PATCH 247/343] Add missing LoginAnalyticsListener implementations --- .../accounts/login/LoginAnalyticsTracker.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java index 86fba8887e71..145bc39fa54f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java @@ -375,4 +375,24 @@ public void trackSocialSignupConfirmationViewed() { public void trackCreateAccountClick() { mUnifiedLoginTracker.trackClick(Click.CREATE_ACCOUNT); } + + @Override public void emailPasswordFormScreenResumed() { + mUnifiedLoginTracker.setStep(Step.START); + } + + @Override public void siteAddressFormScreenResumed() { + mUnifiedLoginTracker.setStep(Step.START); + } + + @Override public void magicLinkRequestScreenResumed() { + mUnifiedLoginTracker.setStep(Step.START); + } + + @Override public void magicLinkSentScreenResumed() { + mUnifiedLoginTracker.setStep(Step.MAGIC_LINK_REQUESTED); + } + + @Override public void usernamePasswordScreenResumed() { + mUnifiedLoginTracker.setStep(Step.USERNAME_PASSWORD); + } } From 166c7818847dcfe06595ffbd365c46cd660f7365 Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Fri, 6 Nov 2020 16:50:49 -0300 Subject: [PATCH 248/343] Add missing login strings --- WordPress/src/main/res/values/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 2fc7d284fc7c..61192788b008 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2466,6 +2466,7 @@ Your site address appears in the bar at the top of the screen when you visit your site in Chrome. Need more help? Find your site address + Find your connected email Checking site address Error while adding site. Error code: %s Log in to WordPress.com to access the post. @@ -2480,6 +2481,7 @@ My sites Log in with Google. Continue with Google + Continue with store credentials or Close There\'s no WordPress.com account matching this Google account. From 559471b565ce3905360a333606fb1e86f29a2ebe Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Fri, 6 Nov 2020 20:53:50 -0300 Subject: [PATCH 249/343] removed unused strings --- WordPress/src/main/res/values/strings.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 2fc7d284fc7c..f2a9a6c98507 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2937,7 +2937,6 @@ Flip camera Flash Stickers - More Text Sound Flip @@ -2948,8 +2947,6 @@ Saved to photos SHARE Share to - Done - Next Close Saved Retry From f5a1c1b518dde5c57560298ff0936511b1f0ac0c Mon Sep 17 00:00:00 2001 From: develric Date: Mon, 9 Nov 2020 10:53:20 +0100 Subject: [PATCH 250/343] Separating Site visibility from Photon capability --- .../android/ui/reader/utils/ReaderUtils.java | 40 ++++++++++++++++--- .../ui/reader/utils/ReaderUtilsWrapper.kt | 3 ++ .../ui/reader/utils/SiteAccessibilityInfo.kt | 9 +++++ .../org/wordpress/android/util/SiteUtils.java | 17 ++++++++ .../android/util/SiteUtilsWrapper.kt | 4 ++ 5 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/reader/utils/SiteAccessibilityInfo.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java index 3acdf75876a0..2fc12feae6a1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java @@ -49,17 +49,39 @@ public static String getResizedImageUrl(final String imageUrl, if (isPrivate && !isPrivateAtomic) { - return getPrivateImageForDisplay(unescapedUrl, width, height); + return getImageForDisplayWithoutPhoton(unescapedUrl, width, height, true); } else { return PhotonUtils.getPhotonImageUrl(unescapedUrl, width, height, quality, isPrivateAtomic); } } + public static String getResizedImageUrl(final String imageUrl, + int width, + int height, + SiteAccessibilityInfo siteAccessibilityInfo) { + return getResizedImageUrl(imageUrl, width, height, siteAccessibilityInfo, PhotonUtils.Quality.MEDIUM); + } + + public static String getResizedImageUrl(final String imageUrl, + int width, + int height, + SiteAccessibilityInfo siteAccessibilityInfo, + PhotonUtils.Quality quality) { + final String unescapedUrl = StringEscapeUtils.unescapeHtml4(imageUrl); + + if (siteAccessibilityInfo.isPhotonCapable()) { + return PhotonUtils.getPhotonImageUrl(unescapedUrl, width, height, quality, siteAccessibilityInfo.getSiteVisibility() == SiteVisibility.PRIVATE_ATOMIC); + } else { + return getImageForDisplayWithoutPhoton(unescapedUrl, width, height, siteAccessibilityInfo.getSiteVisibility() == SiteVisibility.PRIVATE); + } + } + /* - * use this to request a reduced size image from a private post - images in private posts can't - * use photon but these are usually wp images so they support the h= and w= query params + * use this to request a reduced size image from not photon capable sites + * (i.e. a private post - images in private posts can't use photon + * but these are usually wp images so they support the h= and w= query params) */ - private static String getPrivateImageForDisplay(final String imageUrl, int width, int height) { + private static String getImageForDisplayWithoutPhoton(final String imageUrl, int width, int height, boolean forceHttps) { if (TextUtils.isEmpty(imageUrl)) { return ""; } @@ -74,8 +96,14 @@ private static String getPrivateImageForDisplay(final String imageUrl, int width } else { query = ""; } - // remove the existing query string, add the new one, and make sure the url is https: - return UrlUtils.removeQuery(UrlUtils.makeHttps(imageUrl)) + query; + + if (forceHttps) { + // remove the existing query string, add the new one, and make sure the url is https: + return UrlUtils.removeQuery(UrlUtils.makeHttps(imageUrl)) + query; + } else { + // remove the existing query string, add the new one + return UrlUtils.removeQuery(imageUrl) + query; + } } /* diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt index d720cf996ded..01ac5fa93701 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt @@ -22,6 +22,9 @@ class ReaderUtilsWrapper @Inject constructor( fun getResizedImageUrl(imageUrl: String?, width: Int, height: Int, isPrivate: Boolean, isAtomic: Boolean): String? = ReaderUtils.getResizedImageUrl(imageUrl, width, height, isPrivate, isAtomic) + fun getResizedImageUrl(imageUrl: String?, width: Int, height: Int, siteAccessibilityInfo: SiteAccessibilityInfo): String? = + ReaderUtils.getResizedImageUrl(imageUrl, width, height, siteAccessibilityInfo) + fun getTagFromTagName(tagName: String, tagType: ReaderTagType): ReaderTag = ReaderUtils.getTagFromTagName(tagName, tagType) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/SiteAccessibilityInfo.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/SiteAccessibilityInfo.kt new file mode 100644 index 000000000000..9f328bfaac55 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/SiteAccessibilityInfo.kt @@ -0,0 +1,9 @@ +package org.wordpress.android.ui.reader.utils + +enum class SiteVisibility { + PRIVATE, + PRIVATE_ATOMIC, + PUBLIC +} + +data class SiteAccessibilityInfo(val siteVisibility: SiteVisibility, val isPhotonCapable: Boolean) diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java index 9f2b9d0bb389..a28116123296 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java @@ -5,6 +5,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.jetbrains.annotations.NotNull; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker.Stat; import org.wordpress.android.fluxc.Dispatcher; @@ -16,6 +17,8 @@ import org.wordpress.android.fluxc.store.SiteStore.DesignateMobileEditorPayload; import org.wordpress.android.ui.plans.PlansConstants; import org.wordpress.android.ui.prefs.AppPrefs; +import org.wordpress.android.ui.reader.utils.SiteAccessibilityInfo; +import org.wordpress.android.ui.reader.utils.SiteVisibility; import org.wordpress.android.util.analytics.AnalyticsUtils; import org.wordpress.android.util.analytics.AnalyticsUtils.BlockEditorEnabledSource; import org.wordpress.android.util.helpers.Version; @@ -246,6 +249,20 @@ public static String getSiteIconUrl(SiteModel site, int size) { site.isPrivateWPComAtomic()); } + public static SiteAccessibilityInfo getAccessibilityInfoFromSite(@NotNull SiteModel site) { + SiteVisibility siteVisibility; + + if (site.isPrivateWPComAtomic()) { + siteVisibility = SiteVisibility.PRIVATE_ATOMIC; + } else if (site.isPrivate()) { + siteVisibility = SiteVisibility.PRIVATE; + } else { + siteVisibility = SiteVisibility.PUBLIC; + } + + return new SiteAccessibilityInfo(siteVisibility, isPhotonCapable(site)); + } + public static ArrayList getCurrentSiteIds(SiteStore siteStore, boolean selfhostedOnly) { ArrayList siteIDs = new ArrayList<>(); List sites = selfhostedOnly ? siteStore.getSitesAccessedViaXMLRPC() : siteStore.getSites(); diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt index 5bb5c4dbc8ad..9f6546fc5850 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt @@ -1,7 +1,9 @@ package org.wordpress.android.util import dagger.Reusable +import org.jetbrains.annotations.NotNull import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.ui.reader.utils.SiteAccessibilityInfo import javax.inject.Inject /** @@ -14,4 +16,6 @@ import javax.inject.Inject @Reusable class SiteUtilsWrapper @Inject constructor() { fun isPhotonCapable(site: SiteModel): Boolean = SiteUtils.isPhotonCapable(site) + fun getAccessibilityInfoFromSite(site: SiteModel): SiteAccessibilityInfo + = SiteUtils.getAccessibilityInfoFromSite(site) } From 7baaff0629d3f8967ade4f520c8c21759c832a73 Mon Sep 17 00:00:00 2001 From: develric Date: Mon, 9 Nov 2020 10:53:52 +0100 Subject: [PATCH 251/343] Using accessibility info in feature image for posts. --- .../java/org/wordpress/android/ui/posts/FeaturedImageHelper.kt | 3 +-- .../org/wordpress/android/viewmodel/posts/PostListViewModel.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/FeaturedImageHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/FeaturedImageHelper.kt index a28b43ba73a3..0b6fd2e1613d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/FeaturedImageHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/FeaturedImageHelper.kt @@ -169,8 +169,7 @@ class FeaturedImageHelper @Inject constructor( mediaUri, maxDimen, maxDimen, - !siteUtilsWrapper.isPhotonCapable(site), - site.isPrivateWPComAtomic + siteUtilsWrapper.getAccessibilityInfoFromSite(site) ) return FeaturedImageData(FeaturedImageState.REMOTE_IMAGE_LOADING, photonUrl) } diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt index ba20cd0e63ce..31bd86d90471 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt @@ -395,8 +395,7 @@ class PostListViewModel @Inject constructor( featuredImageUrl, photonWidth, photonHeight, - !SiteUtils.isPhotonCapable(connector.site), - connector.site.isPrivateWPComAtomic + SiteUtils.getAccessibilityInfoFromSite(connector.site) ) fun updateAuthorFilterIfNotSearch(authorFilterSelection: AuthorFilterSelection): Boolean { From 5bb5387333dd9d790d8ffea845644f9e4888f628 Mon Sep 17 00:00:00 2001 From: develric Date: Mon, 9 Nov 2020 11:24:23 +0100 Subject: [PATCH 252/343] Fixing linter. --- .../android/ui/reader/utils/ReaderUtils.java | 22 ++++++++++++++++--- .../ui/reader/utils/ReaderUtilsWrapper.kt | 8 +++++-- .../android/util/SiteUtilsWrapper.kt | 5 ++--- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java index 2fc12feae6a1..980a830bd2d8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtils.java @@ -70,9 +70,20 @@ public static String getResizedImageUrl(final String imageUrl, final String unescapedUrl = StringEscapeUtils.unescapeHtml4(imageUrl); if (siteAccessibilityInfo.isPhotonCapable()) { - return PhotonUtils.getPhotonImageUrl(unescapedUrl, width, height, quality, siteAccessibilityInfo.getSiteVisibility() == SiteVisibility.PRIVATE_ATOMIC); + return PhotonUtils.getPhotonImageUrl( + unescapedUrl, + width, + height, + quality, + siteAccessibilityInfo.getSiteVisibility() == SiteVisibility.PRIVATE_ATOMIC + ); } else { - return getImageForDisplayWithoutPhoton(unescapedUrl, width, height, siteAccessibilityInfo.getSiteVisibility() == SiteVisibility.PRIVATE); + return getImageForDisplayWithoutPhoton( + unescapedUrl, + width, + height, + siteAccessibilityInfo.getSiteVisibility() == SiteVisibility.PRIVATE + ); } } @@ -81,7 +92,12 @@ public static String getResizedImageUrl(final String imageUrl, * (i.e. a private post - images in private posts can't use photon * but these are usually wp images so they support the h= and w= query params) */ - private static String getImageForDisplayWithoutPhoton(final String imageUrl, int width, int height, boolean forceHttps) { + private static String getImageForDisplayWithoutPhoton( + final String imageUrl, + int width, + int height, + boolean forceHttps + ) { if (TextUtils.isEmpty(imageUrl)) { return ""; } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt index 01ac5fa93701..371dac9a1d4c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderUtilsWrapper.kt @@ -22,8 +22,12 @@ class ReaderUtilsWrapper @Inject constructor( fun getResizedImageUrl(imageUrl: String?, width: Int, height: Int, isPrivate: Boolean, isAtomic: Boolean): String? = ReaderUtils.getResizedImageUrl(imageUrl, width, height, isPrivate, isAtomic) - fun getResizedImageUrl(imageUrl: String?, width: Int, height: Int, siteAccessibilityInfo: SiteAccessibilityInfo): String? = - ReaderUtils.getResizedImageUrl(imageUrl, width, height, siteAccessibilityInfo) + fun getResizedImageUrl( + imageUrl: String?, + width: Int, + height: Int, + siteAccessibilityInfo: SiteAccessibilityInfo + ): String? = ReaderUtils.getResizedImageUrl(imageUrl, width, height, siteAccessibilityInfo) fun getTagFromTagName(tagName: String, tagType: ReaderTagType): ReaderTag = ReaderUtils.getTagFromTagName(tagName, tagType) diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt index 9f6546fc5850..6d4a7f21d214 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtilsWrapper.kt @@ -1,7 +1,6 @@ package org.wordpress.android.util import dagger.Reusable -import org.jetbrains.annotations.NotNull import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.ui.reader.utils.SiteAccessibilityInfo import javax.inject.Inject @@ -16,6 +15,6 @@ import javax.inject.Inject @Reusable class SiteUtilsWrapper @Inject constructor() { fun isPhotonCapable(site: SiteModel): Boolean = SiteUtils.isPhotonCapable(site) - fun getAccessibilityInfoFromSite(site: SiteModel): SiteAccessibilityInfo - = SiteUtils.getAccessibilityInfoFromSite(site) + fun getAccessibilityInfoFromSite(site: SiteModel): SiteAccessibilityInfo = + SiteUtils.getAccessibilityInfoFromSite(site) } From 7b30d3072e1c6adba02583383e482fe11bcc7c6d Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 11:41:08 +0100 Subject: [PATCH 253/343] Update FluxC version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bbba39c675eb..92fc9e2f6de2 100644 --- a/build.gradle +++ b/build.gradle @@ -129,7 +129,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '1.6.26-beta-3' + fluxCVersion = '5685adcdb9fd5a858a413eab0e677156bf373e41' appCompatVersion = '1.0.2' From 962cce2bec4a1ed0faae4f1ed1861341520f2722 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 11:41:25 +0100 Subject: [PATCH 254/343] Add additional tracking information on media upload error --- .../org/wordpress/android/ui/uploads/MediaUploadHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadHandler.java b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadHandler.java index f50e936e1a5a..51f4b83af306 100755 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadHandler.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/MediaUploadHandler.java @@ -196,6 +196,9 @@ private void handleOnMediaUploadedError(@NonNull OnMediaUploaded event) { Map properties = new HashMap<>(); properties.put("error_type", event.error.type.name()); + properties.put("error_message", event.error.message); + properties.put("error_log", event.error.logMessage); + properties.put("error_status_code", event.error.statusCode); trackUploadMediaEvents(AnalyticsTracker.Stat.MEDIA_UPLOAD_ERROR, media, properties); completeUploadWithId(event.media.getId()); From d1e9989c7aebad712fcb3331d0218d190e745953 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 12:16:08 +0100 Subject: [PATCH 255/343] Update FluxC version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 92fc9e2f6de2..6d4d69b847c8 100644 --- a/build.gradle +++ b/build.gradle @@ -129,7 +129,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '5685adcdb9fd5a858a413eab0e677156bf373e41' + fluxCVersion = '0ccf37b7e71aa6c4f027e2d973cc85e4a3f9627c' appCompatVersion = '1.0.2' From 9bf32842790008a90635d9fe7cf5db4cc2223d90 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 12:34:36 +0100 Subject: [PATCH 256/343] Remove unnecessary annotation --- .../ui/reader/repository/ReaderDiscoverDataProviderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt index f12514b51a0f..82a69f482f2c 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt @@ -229,7 +229,6 @@ class ReaderDiscoverDataProviderTest { Assertions.assertThat(data?.cards?.size).isEqualTo(NUMBER_OF_ITEMS) } - @ExperimentalCoroutinesApi @Test fun `when followed tags change the discover feed gets refreshed`() = test { // Act From 9c24f0d0032d66296caf522f5d9a5c8b5ba1936b Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 13:01:34 +0100 Subject: [PATCH 257/343] Update stories version to fix lint issues --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index b4ed83bbab31..d5a2798d4d20 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit b4ed83bbab31fac1096371b3023bb643a64cf09d +Subproject commit d5a2798d4d204738eb733e95e4ea7d4706b4a77e From 3a796b5a1c96148052c6aac4168b4dd1dd6c56ff Mon Sep 17 00:00:00 2001 From: develric Date: Mon, 9 Nov 2020 13:08:03 +0100 Subject: [PATCH 258/343] Fixing Unit Testing. --- .../android/ui/posts/FeaturedImageHelperTest.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt index 9379846e44d9..50ddaf7167b5 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt @@ -1,6 +1,7 @@ package org.wordpress.android.ui.posts import com.nhaarman.mockitokotlin2.KArgumentCaptor +import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.argThat import com.nhaarman.mockitokotlin2.argumentCaptor @@ -29,6 +30,7 @@ import org.wordpress.android.fluxc.store.UploadStore import org.wordpress.android.ui.posts.FeaturedImageHelper.EnqueueFeaturedImageResult import org.wordpress.android.ui.posts.FeaturedImageHelper.FeaturedImageState import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper +import org.wordpress.android.ui.reader.utils.SiteAccessibilityInfo import org.wordpress.android.ui.uploads.UploadServiceFacade import org.wordpress.android.util.FluxCUtilsWrapper import org.wordpress.android.util.SiteUtilsWrapper @@ -43,12 +45,14 @@ class FeaturedImageHelperTest { private val readerUtilsWrapper: ReaderUtilsWrapper = mock() private val fluxCUtilsWrapper: FluxCUtilsWrapper = mock() private val siteUtilsWrapper: SiteUtilsWrapper = mock() + private val siteAccessibilityInfo: SiteAccessibilityInfo = mock() private val dispatcher: Dispatcher = mock() private lateinit var featuredImageHelper: FeaturedImageHelper @Before fun setUp() { + whenever(siteUtilsWrapper.getAccessibilityInfoFromSite(any())).thenReturn(siteAccessibilityInfo) featuredImageHelper = FeaturedImageHelper( uploadStore, mediaStore, @@ -318,8 +322,7 @@ class FeaturedImageHelperTest { eq("https://testing.com/url.jpg"), anyInt(), anyInt(), - anyBoolean(), - anyBoolean() + eq(siteAccessibilityInfo) ) } @@ -344,8 +347,7 @@ class FeaturedImageHelperTest { "https://testing.com/thumbnail.jpg"), anyInt(), anyInt(), - anyBoolean(), - anyBoolean() + eq(siteAccessibilityInfo) ) } From f4ea8bd01ee60ef4e743141711b86feaa0d871a7 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Mon, 9 Nov 2020 09:11:50 -0300 Subject: [PATCH 259/343] removed unused resource --- WordPress/src/main/res/values/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index f2a9a6c98507..cf3bf3256131 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2956,7 +2956,6 @@ errored Change text alignment Change text color - Delete slide Delete story slide? This slide will be removed from your story. This slide has not been saved yet. If you delete this slide, you will lose any edits you have made. From ecada2ba02c9e99ff93f546ca96f811588eb8144 Mon Sep 17 00:00:00 2001 From: develric Date: Mon, 9 Nov 2020 13:12:50 +0100 Subject: [PATCH 260/343] Updating release-notes. --- RELEASE-NOTES.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index d515be3dc7da..f2c9400719be 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 16.2 ----- - +* [*] Posts List: fixed bug that prevented showing the Featured Image of a post for pure self-hosted sites [https://github.com/wordpress-mobile/WordPress-Android/pull/13323] + 16.1 ----- * [***] Block Editor: Adds new option to select from a variety of predefined page templates when creating a new page for a Gutenberg site. From 35206c88de807a91a92cb89bd0235cbfe0cb1509 Mon Sep 17 00:00:00 2001 From: develric Date: Mon, 9 Nov 2020 14:03:05 +0100 Subject: [PATCH 261/343] Fixing Lint. --- .../org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt index 50ddaf7167b5..d2a001f62b58 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/FeaturedImageHelperTest.kt @@ -14,7 +14,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.junit.MockitoJUnitRunner From 89a149583f36b4cc11948f6ac41f27885f05ffa9 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 17:26:15 +0100 Subject: [PATCH 262/343] Remove flag check when handling a story --- .../java/org/wordpress/android/ui/main/MySiteFragment.kt | 7 +------ .../android/ui/stories/StoriesMediaPickerResultHandler.kt | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt index b116866fd762..44c5a1ad3352 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt @@ -774,12 +774,7 @@ class MySiteFragment : Fragment(), isDomainCreditAvailable = false } RequestCodes.PHOTO_PICKER -> if (resultCode == Activity.RESULT_OK && data != null) { - if (consolidatedMediaPickerFeatureConfig.isEnabled() || - !storiesMediaPickerResultHandler.handleMediaPickerResultForStories( - data, - activity, - selectedSite - )) { + if (!storiesMediaPickerResultHandler.handleMediaPickerResultForStories(data, activity, selectedSite)) { if (data.hasExtra(MediaPickerConstants.EXTRA_MEDIA_ID)) { val mediaId = data.getLongExtra(MediaPickerConstants.EXTRA_MEDIA_ID, 0).toInt() showSiteIconProgressBar(true) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt index 8c17b60d2ce0..cf0532126410 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt @@ -28,7 +28,7 @@ class StoriesMediaPickerResultHandler PagePostCreationSourcesDetail.STORY_FROM_MY_SITE ) return true - } else if (consolidatedMediaPickerFeatureConfig.isEnabled() || isWPStoriesMediaBrowserTypeResult(data)) { + } else if (isWPStoriesMediaBrowserTypeResult(data)) { if (data.hasExtra(MediaBrowserActivity.RESULT_IDS)) { ActivityLauncher.addNewStoryWithMediaIdsForResult( activity, From 1fe7a293a33453d0737c7a932f0304826175482b Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Mon, 9 Nov 2020 17:32:25 +0100 Subject: [PATCH 263/343] Update FluXC to latest develop --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fc1d558f07fa..ad53a57c2fb1 100644 --- a/build.gradle +++ b/build.gradle @@ -129,7 +129,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = 'e1d277cf261ee2e26a3c164fa56a1522848ed9f1' + fluxCVersion = '99bd86efce24d07d2d3b3429c7267a63f700faa0' appCompatVersion = '1.0.2' coreVersion = '1.2.0' From b5c5c31113bb59dd55d21c8839ac968567c4f307 Mon Sep 17 00:00:00 2001 From: Cameron Voell Date: Mon, 9 Nov 2020 17:05:37 -0800 Subject: [PATCH 264/343] Add call to dismiss saving screen when auto-saving from preview --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 1 + .../wordpress/android/ui/posts/editor/StorePostViewModel.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 3dc7a557d26b..d26ec7285468 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3280,6 +3280,7 @@ private boolean isRemoteAutoSaveError() { private PostModel handleRemoteAutoSave(boolean isError, PostModel post) { // We are in the process of remote previewing a post from the editor if (!isError && isUploadingPostForPreview()) { + mViewModel.hideSavingDialog(); // We were uploading post for preview and we got no error: // update post status and preview it in the internal browser updateOnSuccessfulUpload(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt index aa94d0457284..212a203e605d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt @@ -169,6 +169,10 @@ class StorePostViewModel _onFinish.postValue(Event(state)) } + fun hideSavingDialog() { + _savingProgressDialogVisibility.postValue(Hidden) + } + sealed class UpdateResult { object Error : UpdateResult() data class Success(val postTitleOrContentChanged: Boolean) : UpdateResult() From 4d9441c0bbfb59f4b3bd02f8372760aaa79d7a9d Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 10 Nov 2020 10:42:11 +0530 Subject: [PATCH 265/343] Introduce scan feature flag --- WordPress/build.gradle | 1 + .../wordpress/android/util/ScanFeatureConfig.kt | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/util/ScanFeatureConfig.kt diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 50b55228eb91..ba430aca9b54 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -76,6 +76,7 @@ android { buildConfigField "boolean", "WP_STORIES_AVAILABLE", "true" buildConfigField "boolean", "ANY_FILE_UPLOAD", "true" buildConfigField "boolean", "CONSOLIDATED_MEDIA_PICKER", "false" + buildConfigField "boolean", "SCAN_AVAILABLE", "false" buildConfigField "boolean", "ENABLE_FEATURE_CONFIGURATION", "true" } diff --git a/WordPress/src/main/java/org/wordpress/android/util/ScanFeatureConfig.kt b/WordPress/src/main/java/org/wordpress/android/util/ScanFeatureConfig.kt new file mode 100644 index 000000000000..26ed0f19cc35 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/util/ScanFeatureConfig.kt @@ -0,0 +1,17 @@ +package org.wordpress.android.util + +import org.wordpress.android.BuildConfig +import org.wordpress.android.annotation.FeatureInDevelopment +import org.wordpress.android.util.config.AppConfig +import org.wordpress.android.util.config.FeatureConfig +import javax.inject.Inject + +/** + * Configuration of the Scan feature. + */ +@FeatureInDevelopment +class ScanFeatureConfig +@Inject constructor(appConfig: AppConfig) : FeatureConfig( + appConfig, + BuildConfig.SCAN_AVAILABLE +) From ddba12686ac373b637ab36b13b09c9bd1ae7a323 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 10 Nov 2020 10:43:35 +0530 Subject: [PATCH 266/343] Scan menu on My site: display based on scan feature config --- .../android/ui/main/MySiteFragment.kt | 10 ++++++++++ .../res/drawable/ic_scan_alt_white_24dp.xml | 9 +++++++++ .../src/main/res/layout/my_site_fragment.xml | 18 ++++++++++++++++++ WordPress/src/main/res/values/strings.xml | 3 +++ 4 files changed, 40 insertions(+) create mode 100644 WordPress/src/main/res/drawable/ic_scan_alt_white_24dp.xml diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt index b116866fd762..0122fb0ae44b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt @@ -151,6 +151,7 @@ import org.wordpress.android.util.QuickStartUtils.Companion.getNextUncompletedQu import org.wordpress.android.util.QuickStartUtils.Companion.isQuickStartInProgress import org.wordpress.android.util.QuickStartUtils.Companion.removeQuickStartFocusPoint import org.wordpress.android.util.QuickStartUtils.Companion.stylizeQuickStartPrompt +import org.wordpress.android.util.ScanFeatureConfig import org.wordpress.android.util.SiteUtils import org.wordpress.android.util.ToastUtils import org.wordpress.android.util.ToastUtils.Duration.SHORT @@ -162,6 +163,7 @@ import org.wordpress.android.util.image.ImageManager import org.wordpress.android.util.image.ImageType.BLAVATAR import org.wordpress.android.util.image.ImageType.USER import org.wordpress.android.util.requestEmailValidation +import org.wordpress.android.util.setVisible import org.wordpress.android.widgets.WPDialogSnackbar import org.wordpress.android.widgets.WPSnackbar import java.io.File @@ -197,6 +199,8 @@ class MySiteFragment : Fragment(), @Inject lateinit var mediaPickerLauncher: MediaPickerLauncher @Inject lateinit var storiesMediaPickerResultHandler: StoriesMediaPickerResultHandler @Inject lateinit var consolidatedMediaPickerFeatureConfig: ConsolidatedMediaPickerFeatureConfig + @Inject lateinit var scanFeatureConfig: ScanFeatureConfig + val selectedSite: SiteModel? get() { return (activity as? WPMainActivity)?.selectedSite @@ -232,6 +236,8 @@ class MySiteFragment : Fragment(), // Site details may have changed (e.g. via Settings and returning to this Fragment) so update the UI refreshSelectedSiteDetails(selectedSite) selectedSite?.let { site -> + updateScanMenuVisibility() + val isNotAdmin = !site.hasCapabilityManageOptions val isSelfHostedWithoutJetpack = !SiteUtils.isAccessedViaWPComRest( site @@ -251,6 +257,10 @@ class MySiteFragment : Fragment(), showQuickStartNoticeIfNecessary() } + private fun updateScanMenuVisibility() { + row_scan.setVisible(scanFeatureConfig.isEnabled()) + } + private fun showQuickStartNoticeIfNecessary() { if (!isQuickStartInProgress(quickStartStore) || !AppPrefs.isQuickStartNoticeRequired()) { return diff --git a/WordPress/src/main/res/drawable/ic_scan_alt_white_24dp.xml b/WordPress/src/main/res/drawable/ic_scan_alt_white_24dp.xml new file mode 100644 index 000000000000..23bfd937c8de --- /dev/null +++ b/WordPress/src/main/res/drawable/ic_scan_alt_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/WordPress/src/main/res/layout/my_site_fragment.xml b/WordPress/src/main/res/layout/my_site_fragment.xml index d1f122a35f57..7550589c10a4 100644 --- a/WordPress/src/main/res/layout/my_site_fragment.xml +++ b/WordPress/src/main/res/layout/my_site_fragment.xml @@ -408,6 +408,24 @@ + + + + + + + + + Are you sure you want to rewind your site back to %1$s at %2$s? This will remove all content and options created or changed since then. Since you\'re on a free plan, you\'ll see limited events in your activity. + + Scan + Today Yesterday From 0d5d1d2cd458f257bb03a84eebfb29d1a146913d Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 09:56:24 +0100 Subject: [PATCH 267/343] Fix build with latest FluxC develop changes --- .../android/ui/sitecreation/usecases/CreateSiteUseCase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt index 1165ed0691b1..3c301a5ec8b3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt @@ -53,6 +53,7 @@ class CreateSiteUseCase @Inject constructor( languageWordPressId, siteVisibility, siteData.segmentId, + null, dryRun ) continuation = cont From 605cf887627d8b00bed73eb8196df1074ec8c8da Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 10:05:14 +0100 Subject: [PATCH 268/343] Fix changed FluxC API --- .../android/ui/sitecreation/usecases/CreateSiteUseCase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt index 1165ed0691b1..3c301a5ec8b3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt @@ -53,6 +53,7 @@ class CreateSiteUseCase @Inject constructor( languageWordPressId, siteVisibility, siteData.segmentId, + null, dryRun ) continuation = cont From 5341e3802920271a54539b8dd3de04185f23f75e Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 10:53:20 +0100 Subject: [PATCH 269/343] Hide saving dialog on saving finished before preview --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 1 + .../wordpress/android/ui/posts/editor/StorePostViewModel.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index b54b5d41b072..994b9892de6c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3270,6 +3270,7 @@ private boolean isRemoteAutoSaveError() { private PostModel handleRemoteAutoSave(boolean isError, PostModel post) { // We are in the process of remote previewing a post from the editor if (!isError && isUploadingPostForPreview()) { + mViewModel.hideSavingDialog(); // We were uploading post for preview and we got no error: // update post status and preview it in the internal browser updateOnSuccessfulUpload(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt index aa94d0457284..212a203e605d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StorePostViewModel.kt @@ -169,6 +169,10 @@ class StorePostViewModel _onFinish.postValue(Event(state)) } + fun hideSavingDialog() { + _savingProgressDialogVisibility.postValue(Hidden) + } + sealed class UpdateResult { object Error : UpdateResult() data class Success(val postTitleOrContentChanged: Boolean) : UpdateResult() From 80c5a10a3990d08d6bff4dbb7de798fe98e6f1a2 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 11:27:10 +0100 Subject: [PATCH 270/343] Trigger hideSavingDialog even on error --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 994b9892de6c..b4b750f785da 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3268,9 +3268,9 @@ private boolean isRemoteAutoSaveError() { @Nullable private PostModel handleRemoteAutoSave(boolean isError, PostModel post) { + mViewModel.hideSavingDialog(); // We are in the process of remote previewing a post from the editor if (!isError && isUploadingPostForPreview()) { - mViewModel.hideSavingDialog(); // We were uploading post for preview and we got no error: // update post status and preview it in the internal browser updateOnSuccessfulUpload(); From a91b5fb36dc5c5c70fc2c3e551c5acd9fd6d76d0 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 12:16:54 +0100 Subject: [PATCH 271/343] Rollback FluxC fix --- .../android/ui/sitecreation/usecases/CreateSiteUseCase.kt | 1 - build.gradle | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt index 3c301a5ec8b3..1165ed0691b1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt @@ -53,7 +53,6 @@ class CreateSiteUseCase @Inject constructor( languageWordPressId, siteVisibility, siteData.segmentId, - null, dryRun ) continuation = cont diff --git a/build.gradle b/build.gradle index 849bd57ec696..7141dbfa8183 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '99bd86efce24d07d2d3b3429c7267a63f700faa0' + fluxCVersion = '39d28a7db4e238fce646a7ea54293a96bd618e4f' appCompatVersion = '1.0.2' coreVersion = '1.2.0' From b24e2b3a89fd9a652aa69060812adf16708b561e Mon Sep 17 00:00:00 2001 From: Ceyhun Ozugur Date: Tue, 10 Nov 2020 13:25:34 +0100 Subject: [PATCH 272/343] Save previous gutenberg bundle --- .circleci/config.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 147f85c17f66..f9a3f327ac8c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,18 +85,7 @@ jobs: steps: - git/shallow-checkout - restore-gutenberg-bundle-cache - - run: - name: Abort If JS Bundle Exists - command: | - if [ -f "libs/gutenberg-mobile/gutenberg/packages/react-native-bridge/android/build/assets/index.android.bundle" ]; then - echo "Gutenberg-Mobile bundle already in cache, no need to create a new one." - circleci-agent step halt - else - echo "Gutenberg-Mobile bundle not found in cache. Proceeding to generate new bundle" - fi - checkout-submodules - - npm-install - - npm-bundle-android - run: name: Ensure assets folder exists command: mkdir -p libs/gutenberg-mobile/gutenberg/packages/react-native-bridge/android/build/assets From 3818ac1af49b2027dd0944a528e2545152f61c33 Mon Sep 17 00:00:00 2001 From: Ceyhun Ozugur Date: Tue, 10 Nov 2020 13:42:19 +0100 Subject: [PATCH 273/343] Revert "Save previous gutenberg bundle" This reverts commit b24e2b3a89fd9a652aa69060812adf16708b561e. --- .circleci/config.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f9a3f327ac8c..147f85c17f66 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,7 +85,18 @@ jobs: steps: - git/shallow-checkout - restore-gutenberg-bundle-cache + - run: + name: Abort If JS Bundle Exists + command: | + if [ -f "libs/gutenberg-mobile/gutenberg/packages/react-native-bridge/android/build/assets/index.android.bundle" ]; then + echo "Gutenberg-Mobile bundle already in cache, no need to create a new one." + circleci-agent step halt + else + echo "Gutenberg-Mobile bundle not found in cache. Proceeding to generate new bundle" + fi - checkout-submodules + - npm-install + - npm-bundle-android - run: name: Ensure assets folder exists command: mkdir -p libs/gutenberg-mobile/gutenberg/packages/react-native-bridge/android/build/assets From 1bbc74568b72aaf1a6d4ca156b38f2d7cf534d54 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 14:52:16 +0100 Subject: [PATCH 274/343] Introduce MY_SITE_IMPROVEMENTS flag --- WordPress/build.gradle | 1 + .../config/MySiteImprovementsFeatureConfig.kt | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/util/config/MySiteImprovementsFeatureConfig.kt diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 1660b4a665e9..dcc9ee3fa728 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -78,6 +78,7 @@ android { buildConfigField "boolean", "CONSOLIDATED_MEDIA_PICKER", "false" buildConfigField "boolean", "ACTIVITY_LOG_FILTERS", "false" buildConfigField "boolean", "ENABLE_FEATURE_CONFIGURATION", "true" + buildConfigField "boolean", "MY_SITE_IMPROVEMENTS", "false" } // Gutenberg's dependency - react-native-video is using diff --git a/WordPress/src/main/java/org/wordpress/android/util/config/MySiteImprovementsFeatureConfig.kt b/WordPress/src/main/java/org/wordpress/android/util/config/MySiteImprovementsFeatureConfig.kt new file mode 100644 index 000000000000..c3dda5b697b4 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/util/config/MySiteImprovementsFeatureConfig.kt @@ -0,0 +1,15 @@ +package org.wordpress.android.util.config + +import org.wordpress.android.BuildConfig +import org.wordpress.android.annotation.FeatureInDevelopment +import javax.inject.Inject + +/** + * Configuration of the my site infrastructure improvements + */ +@FeatureInDevelopment +class MySiteImprovementsFeatureConfig +@Inject constructor(appConfig: AppConfig) : FeatureConfig( + appConfig, + BuildConfig.MY_SITE_IMPROVEMENTS +) From 32cf7cb8036bba2e972d1dba91b7013522b658b7 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Tue, 10 Nov 2020 15:30:15 +0100 Subject: [PATCH 275/343] Bump version to 16.0.1/951 --- WordPress/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 50013c3bb149..1ae2dabf9c36 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -92,9 +92,9 @@ android { dimension "buildType" // Only set the release version if one isn't provided if (!project.hasProperty("versionName")) { - versionName "16.0" + versionName "16.0.1" } - versionCode 946 + versionCode 951 buildConfigField "boolean", "ME_ACTIVITY_AVAILABLE", "false" buildConfigField "boolean", "TENOR_AVAILABLE", "false" buildConfigField "long", "REMOTE_CONFIG_FETCH_INTERVAL", "3600" From d91c1e53b533bb4427ff868387b160de4048dda0 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 15:42:47 +0100 Subject: [PATCH 276/343] Open new my site fragment when flag is enabled --- .../android/ui/main/ImprovedMySiteFragment.kt | 11 +++++++++++ .../wordpress/android/ui/main/WPMainActivity.java | 4 +++- .../android/ui/main/WPMainNavigationView.kt | 12 ++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt new file mode 100644 index 000000000000..86866e1baa8e --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt @@ -0,0 +1,11 @@ +package org.wordpress.android.ui.main + +import androidx.fragment.app.Fragment + +class ImprovedMySiteFragment : Fragment() { + companion object { + fun newInstance(): ImprovedMySiteFragment { + return ImprovedMySiteFragment() + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index d2e193482809..5a466ec342f8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -126,6 +126,7 @@ import org.wordpress.android.util.analytics.AnalyticsUtils; import org.wordpress.android.util.analytics.service.InstallationReferrerServiceStarter; import org.wordpress.android.util.config.ModalLayoutPickerFeatureConfig; +import org.wordpress.android.util.config.MySiteImprovementsFeatureConfig; import org.wordpress.android.viewmodel.main.WPMainActivityViewModel; import org.wordpress.android.viewmodel.mlp.ModalLayoutPickerViewModel; import org.wordpress.android.widgets.AppRatingDialog; @@ -210,6 +211,7 @@ public class WPMainActivity extends LocaleAwareActivity implements @Inject ModalLayoutPickerFeatureConfig mModalLayoutPickerFeatureConfig; @Inject ReaderTracker mReaderTracker; @Inject MediaPickerLauncher mMediaPickerLauncher; + @Inject MySiteImprovementsFeatureConfig mMySiteImprovementsFeatureConfig; /* * fragments implement this if their contents can be scrolled, called when user @@ -237,7 +239,7 @@ public void onCreate(Bundle savedInstanceState) { mBottomNav = findViewById(R.id.bottom_navigation); - mBottomNav.init(getSupportFragmentManager(), this); + mBottomNav.init(getSupportFragmentManager(), this, mMySiteImprovementsFeatureConfig.isEnabled()); mConnectionBar = findViewById(R.id.connection_bar); mConnectionBar.setOnClickListener(v -> { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt index cb6d2606aaf3..4350f4ee0a81 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt @@ -67,11 +67,11 @@ class WPMainNavigationView @JvmOverloads constructor( fun onNewPostButtonClicked() } - fun init(fm: FragmentManager, listener: OnPageListener) { + fun init(fm: FragmentManager, listener: OnPageListener, showNewMySiteFragment: Boolean) { fragmentManager = fm pageListener = listener - navAdapter = NavAdapter() + navAdapter = NavAdapter(showNewMySiteFragment) assignNavigationListeners(true) disableShiftMode() @@ -278,10 +278,14 @@ class WPMainNavigationView @JvmOverloads constructor( return position in 0 until numPages() } - private inner class NavAdapter { + private inner class NavAdapter(val showNewMySiteFragment: Boolean) { private fun createFragment(pageType: PageType): Fragment { val fragment = when (pageType) { - MY_SITE -> MySiteFragment.newInstance() + MY_SITE -> if (showNewMySiteFragment) { + ImprovedMySiteFragment.newInstance() + } else { + MySiteFragment.newInstance() + } READER -> ReaderFragment() NOTIFS -> NotificationsListFragment.newInstance() } From 201f5a59dd2bb88e6ddcbe1406cf774c5875be87 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 15:51:25 +0100 Subject: [PATCH 277/343] Add TODOs for handling MySiteFragment replacement in WPMainActivity --- .../java/org/wordpress/android/ui/main/WPMainActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index 5a466ec342f8..3497981fa0ec 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -1087,6 +1087,7 @@ public void onClick(View v) { case RequestCodes.STORIES_PHOTO_PICKER: case RequestCodes.PHOTO_PICKER: Fragment fragment = mBottomNav.getActiveFragment(); + // TODO move this logic directly to the fragment if (fragment instanceof MySiteFragment) { fragment.onActivityResult(requestCode, resultCode, data); } @@ -1148,6 +1149,8 @@ private MySiteFragment getMySiteFragment() { if (fragment instanceof MySiteFragment) { return (MySiteFragment) fragment; } + + // TODO consider the new my site fragment return null; } From d011f3ed5510c7dcd8a82ff78055e82db0422e9b Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 15:53:06 +0100 Subject: [PATCH 278/343] Move ImprovedMySiteFragment.kt to mysite package --- .../java/org/wordpress/android/ui/main/WPMainNavigationView.kt | 1 + .../android/ui/{main => mysite}/ImprovedMySiteFragment.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename WordPress/src/main/java/org/wordpress/android/ui/{main => mysite}/ImprovedMySiteFragment.kt (84%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt index 4350f4ee0a81..abb9159c7c5a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainNavigationView.kt @@ -24,6 +24,7 @@ import org.wordpress.android.ui.main.WPMainActivity.OnScrollToTopListener import org.wordpress.android.ui.main.WPMainNavigationView.PageType.MY_SITE import org.wordpress.android.ui.main.WPMainNavigationView.PageType.NOTIFS import org.wordpress.android.ui.main.WPMainNavigationView.PageType.READER +import org.wordpress.android.ui.mysite.ImprovedMySiteFragment import org.wordpress.android.ui.notifications.NotificationsListFragment import org.wordpress.android.ui.prefs.AppPrefs import org.wordpress.android.ui.reader.ReaderFragment diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/ImprovedMySiteFragment.kt similarity index 84% rename from WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt rename to WordPress/src/main/java/org/wordpress/android/ui/mysite/ImprovedMySiteFragment.kt index 86866e1baa8e..6a009ee6140a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/ImprovedMySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/ImprovedMySiteFragment.kt @@ -1,4 +1,4 @@ -package org.wordpress.android.ui.main +package org.wordpress.android.ui.mysite import androidx.fragment.app.Fragment From dc13ceb846bcb845096d468f7bc13c3cd2c7d4ec Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Tue, 10 Nov 2020 15:57:53 +0100 Subject: [PATCH 279/343] Fix merge conflict --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 26a18b9c724a..7a2f0cdb6351 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3281,7 +3281,6 @@ private PostModel handleRemoteAutoSave(boolean isError, PostModel post) { mViewModel.hideSavingDialog(); // We are in the process of remote previewing a post from the editor if (!isError && isUploadingPostForPreview()) { - mViewModel.hideSavingDialog(); // We were uploading post for preview and we got no error: // update post status and preview it in the internal browser updateOnSuccessfulUpload(); From c1c9c9166952ceaea191ce448cdf59549dec0d3a Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Tue, 10 Nov 2020 17:17:09 +0100 Subject: [PATCH 280/343] Update FluxC to tag version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7141dbfa8183..8bff9dde1eda 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '39d28a7db4e238fce646a7ea54293a96bd618e4f' + fluxCVersion = '1.6.26-beta-4' appCompatVersion = '1.0.2' coreVersion = '1.2.0' From 2b16a7e42f8391a50f002c58c5ab35691b0bc2e5 Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Tue, 10 Nov 2020 14:28:34 -0300 Subject: [PATCH 281/343] Remove unused strings --- WordPress/src/main/res/values/strings.xml | 1 - libs/login/WordPressLoginFlow/src/main/res/values/strings.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 61192788b008..03efcfec3cdb 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2492,7 +2492,6 @@ Please enter a WordPress.com or Jetpack-connected self-hosted WordPress site The website at this address is not a WordPress site. For us to connect to it, the site must have WordPress installed. Need help finding the email you connected with? - Send verification email Log in with your %1$s site credentials Enter your account information for %1$s. Log in with site credentials. diff --git a/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml b/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml index 9597cc57f937..3bc927d63110 100644 --- a/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml +++ b/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml @@ -6,7 +6,6 @@ Send link Send link by email Create account - Send verification email Enter your password instead Or type your password Username From c89ce437e7146f31988a2908d8e8c412afe43b8a Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Tue, 10 Nov 2020 14:28:42 -0300 Subject: [PATCH 282/343] Remove unused dimensions --- WordPress/src/main/res/values/dimens.xml | 1 - libs/login/WordPressLoginFlow/src/main/res/values/dimens.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index e0aad713e178..be38ad280a93 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -365,7 +365,6 @@ 4dp - 120dp 5dp diff --git a/libs/login/WordPressLoginFlow/src/main/res/values/dimens.xml b/libs/login/WordPressLoginFlow/src/main/res/values/dimens.xml index c29f2334f88a..fd302ba4844e 100644 --- a/libs/login/WordPressLoginFlow/src/main/res/values/dimens.xml +++ b/libs/login/WordPressLoginFlow/src/main/res/values/dimens.xml @@ -36,7 +36,6 @@ 20dp 92dp 4dp - 120dp 24dp 9dp From 25ce829d964ecb625c1276a6e2b2a51482663cae Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Tue, 10 Nov 2020 14:29:00 -0300 Subject: [PATCH 283/343] Remove unused drawables --- .../img_illustration_email_alert_120dp.xml | 270 ------------------ WordPress/src/main/res/values/drawables.xml | 9 - 2 files changed, 279 deletions(-) delete mode 100644 WordPress/src/main/res/drawable/img_illustration_email_alert_120dp.xml delete mode 100644 WordPress/src/main/res/values/drawables.xml diff --git a/WordPress/src/main/res/drawable/img_illustration_email_alert_120dp.xml b/WordPress/src/main/res/drawable/img_illustration_email_alert_120dp.xml deleted file mode 100644 index 240923e34c09..000000000000 --- a/WordPress/src/main/res/drawable/img_illustration_email_alert_120dp.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/values/drawables.xml b/WordPress/src/main/res/values/drawables.xml deleted file mode 100644 index 3ecca15ada27..000000000000 --- a/WordPress/src/main/res/values/drawables.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - @drawable/img_illustration_email_alert_120dp - - From aa4608871e4149d227aa392d8ff57fb6b4e5cabe Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 10 Nov 2020 16:00:12 -0300 Subject: [PATCH 284/343] updated gutenberg-mobile with latest Stories block code from jetpack/gutenberg --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 7fc9cfc828ee..3e94d94eea66 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 7fc9cfc828eecb52cd9c3e506a26ee730a13a30f +Subproject commit 3e94d94eea667ed3b4c8f435916b2e8312f56db7 From f66556cc643869a160dfc56a27e19585d5e2c985 Mon Sep 17 00:00:00 2001 From: Mario Zorz Date: Tue, 10 Nov 2020 16:00:29 -0300 Subject: [PATCH 285/343] updated stories library commit hash --- libs/stories-android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/stories-android b/libs/stories-android index 0946cae3a775..130daec678db 160000 --- a/libs/stories-android +++ b/libs/stories-android @@ -1 +1 @@ -Subproject commit 0946cae3a775ea9c49b2ceb57324bd4f4ce228d9 +Subproject commit 130daec678db776512eaac37e7f1422289b05a40 From 46b149ae8523b10a3d24c2c1711b949f28512796 Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Wed, 11 Nov 2020 07:45:20 +0900 Subject: [PATCH 286/343] Squashed 'libs/utils/' changes from 40225ecccc..f9d0196b83 f9d0196b83 Run unit tests in CI (#42) da6e11beb1 Add CI step to run the tests d0e8cdbb40 Remove trailing whitespaces from CircleCI config 2aade32249 Merge pull request #39 from wordpress-mobile/fix-bintray-upload-missing-artifacts a4fd2e4ab4 Adds components.release to Bintray publication b3921b9359 Merge pull request #38 from wordpress-mobile/updates-from-wpandroid-with-gradle-6 4c5e2bcb83 Updates from wpandroid with gradle 6 853a26bdc3 Merge pull request #37 from wordpress-mobile/update-version-to-1.29 2b28190305 Update version to 1.29 a6401c4dc0 Merge pull request #36 from wordpress-mobile/upgrade-version-to-1.28 f79ccd177d Update version number to 1.28 d70f573de8 Merge pull request #35 from wordpress-mobile/merge/wpandroid-12813 0ee53abc26 Adds DisplayUtils.getDisplayPixelWidth 806693e808 Merge pull request #34 from wordpress-mobile/manually-update-changes-from-wpandroid 0d527acf54 Update gradle wrapper files a396ad49ef Manually applied consolidated changes from WPAndroid a28f653460 Merge pull request #33 from wordpress-mobile/merge/WordPress-Android/12569 6caa352edd Bumping utils version. 87167d8bbd Limit storage percentage to 100% git-subtree-dir: libs/utils git-subtree-split: f9d0196b83addc26eab2447cc962d489824f5bcf --- .circleci/config.yml | 30 ++- README.md | 7 +- WordPressUtils/build.gradle | 54 +++-- WordPressUtils/gradle.properties-example | 1 + .../wordpress/android/util/DisplayUtils.java | 5 + .../wordpress/android/util/FormatUtils.java | 19 +- .../org/wordpress/android/util/HtmlUtils.java | 11 +- .../wordpress/android/util/MediaUtils.java | 7 +- .../org/wordpress/android/util/UrlUtils.java | 16 ++ .../{LogFileHelpers.kt => LogFileProvider.kt} | 0 gradle.properties-example | 1 + gradle/wrapper/gradle-wrapper.jar | Bin 54783 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 57 +++--- gradlew.bat | 187 ++++++++++-------- 15 files changed, 263 insertions(+), 135 deletions(-) rename WordPressUtils/src/main/java/org/wordpress/android/util/helpers/logfile/{LogFileHelpers.kt => LogFileProvider.kt} (100%) create mode 100644 gradle.properties-example diff --git a/.circleci/config.yml b/.circleci/config.yml index d2e16d4f447e..b863a75292c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,25 +3,46 @@ version: 2.1 orbs: android: wordpress-mobile/android@0.0.22 +commands: + copy-gradle-properties: + steps: + - run: + name: Setup gradle.properties + command: cp gradle.properties-example gradle.properties + jobs: Lint: - executor: + executor: name: android/default api-version: "27" steps: - checkout + - copy-gradle-properties - android/restore-gradle-cache - run: name: Lint & Checkstyle command: ./gradlew --stacktrace lint checkstyle - android/save-gradle-cache - android/save-lint-results + Test: + executor: + name: android/default + api-version: "27" + steps: + - checkout + - copy-gradle-properties + - android/restore-gradle-cache + - run: + name: Test + command: ./gradlew --stacktrace test + - android/save-gradle-cache Build: - executor: + executor: name: android/default api-version: "27" steps: - checkout + - copy-gradle-properties - android/restore-gradle-cache - run: name: Build @@ -32,4 +53,7 @@ workflows: WordPress-Utils-Android: jobs: - Lint - - Build + - Test + - Build: + requires: + - Test diff --git a/README.md b/README.md index 7eb92f06804b..9a17a4008c5a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Collection of utility methods for Android and WordPress. * In your build.gradle: ```groovy dependencies { - compile 'org.wordpress:utils:1.22.0' // use version 1.22.0 + compile 'org.wordpress:utils:1.30.0' // use version 1.30.0 } ``` @@ -21,10 +21,11 @@ $ ./gradlew assemble test publishToMavenLocal ## Publish it to Bintray -When a new version is ready to be published to the remote repository, use the following command to upload it to Bintray: +When a new version is ready to be published to the remote repository, use the following command to publish it to Bintray: ```shell -$ ./gradlew assemble test bintrayUpload -PbintrayUser=FIXME -PbintrayKey=FIXME -PdryRun=false +$ ./gradlew clean build +$ ./gradlew bintrayUpload -PbintrayUser=FIXME -PbintrayKey=FIXME ``` ## Apps and libraries using WordPress-Utils-Android: diff --git a/WordPressUtils/build.gradle b/WordPressUtils/build.gradle index 0491f88b1e09..9dc920e34f25 100644 --- a/WordPressUtils/build.gradle +++ b/WordPressUtils/build.gradle @@ -8,15 +8,16 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath 'com.android.tools.build:gradle:3.5.1' - classpath 'com.novoda:bintray-release:0.9.1' + classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5' } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -apply plugin: 'com.novoda.bintray-release' +apply plugin: 'maven-publish' +apply plugin: 'com.jfrog.bintray' repositories { google() @@ -35,7 +36,7 @@ dependencies { testImplementation 'junit:junit:4.12' testImplementation 'org.assertj:assertj-core:3.11.1' - testImplementation "org.robolectric:robolectric:4.3.1" + testImplementation "org.robolectric:robolectric:4.4" testImplementation 'androidx.test:core:1.0.0' lintChecks 'org.wordpress:lint:1.0.1' @@ -50,10 +51,9 @@ android { useLibrary 'org.apache.http.legacy' compileSdkVersion 28 - buildToolsVersion '28.0.3' defaultConfig { - versionName "1.26" + versionName "1.30" minSdkVersion 18 targetSdkVersion 26 @@ -87,15 +87,35 @@ android.libraryVariants.all { variant -> } } -publish { - artifactId = 'utils' - userOrg = 'wordpress-mobile' - groupId = 'org.wordpress' - uploadName = 'utils' - desc = 'Utils library for Android' - publishVersion = android.defaultConfig.versionName - licences = ['MIT', 'GPL'] - website = 'https://github.com/wordpress-mobile/WordPress-Utils-Android/' - dryRun = 'false' - autoPublish = 'true' +bintray { + user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') + key = project.hasProperty('bintrayKey') ? project.property('bintrayKey') : System.getenv('BINTRAY_KEY') + publications = ['UtilsPublication'] + publish = true + pkg { + repo = 'maven' + name = 'utils' + userOrg = 'wordpress-mobile' + licenses = ['MIT', 'GPL'] + vcsUrl = 'https://github.com/wordpress-mobile/WordPress-Utils-Android.git' + version { + name = android.defaultConfig.versionName + desc = 'Utils library for Android' + released = new Date() + } + } } + +project.afterEvaluate { + publishing { + publications { + UtilsPublication(MavenPublication) { + from components.release + groupId 'org.wordpress' + artifactId 'utils' + version android.defaultConfig.versionName + } + } + } +} + diff --git a/WordPressUtils/gradle.properties-example b/WordPressUtils/gradle.properties-example index 5281d935c4cf..c9edd062db8b 100644 --- a/WordPressUtils/gradle.properties-example +++ b/WordPressUtils/gradle.properties-example @@ -1,3 +1,4 @@ +android.useAndroidX=true ossrhUsername=hello ossrhPassword=world diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/DisplayUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/DisplayUtils.java index 1f4ab00994ca..60877b7082f5 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/DisplayUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/DisplayUtils.java @@ -2,6 +2,7 @@ import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -39,6 +40,10 @@ public static int getDisplayPixelHeight(Context context) { return (size.y); } + public static int getDisplayPixelWidth() { + return Resources.getSystem().getDisplayMetrics().widthPixels; + } + public static float spToPx(Context context, float sp) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); final float scale = displayMetrics.scaledDensity; diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/FormatUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/FormatUtils.java index a9a3b4570e0b..c2850e58d11c 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/FormatUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/FormatUtils.java @@ -55,8 +55,25 @@ public static final String formatFileSize(long size, final String[] unitStrings) * returns the passed double percentage (0 to 1) formatted as an human readable percentage. Ex: 0.25 returns 25% */ public static final String formatPercentage(double value) { + return formatPercentageLimit100(value, false); + } + + /* + * returns the passed double percentage (0 to 1) formatted as an human readable percentage. Ex: 0.251 returns 25.1% + * if limit100 is true, it limits the percentage to 100% + */ + public static final String formatPercentageLimit100(double value, boolean limit100) { + double limit = 1.0001; + NumberFormat percentFormat = NumberFormat.getPercentInstance(); percentFormat.setMaximumFractionDigits(1); - return percentFormat.format(value); + + if (limit100 && value > limit) { + value = limit; + } + + String percentage = percentFormat.format(value); + + return percentage; } } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/HtmlUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/HtmlUtils.java index e35972d4112e..e91024b12587 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/HtmlUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/HtmlUtils.java @@ -14,6 +14,8 @@ import org.wordpress.android.util.helpers.WPHtmlTagHandler; import org.wordpress.android.util.helpers.WPQuoteSpan; +import static org.wordpress.android.util.AppLog.T.UTILS; + public class HtmlUtils { /** * Removes html from the passed string - relies on Html.fromHtml which handles invalid HTML, @@ -126,8 +128,15 @@ public static SpannableStringBuilder fromHtml(String source, ImageGetter imageGe html = (SpannableStringBuilder) Html.fromHtml(source, imageGetter, new WPHtmlTagHandler()); } catch (RuntimeException runtimeException) { // In case our tag handler fails - html = (SpannableStringBuilder) Html.fromHtml(source, imageGetter, null); + try { + html = (SpannableStringBuilder) Html.fromHtml(source, imageGetter, null); + } catch (IllegalArgumentException illegalArgumentException) { + // In case the html is missing a required parameter (for example: "src" missing from img) + html = new SpannableStringBuilder(""); + AppLog.w(UTILS, "Could not parse html"); + } } + EmoticonsUtils.replaceEmoticonsWithEmoji(html); QuoteSpan[] spans = html.getSpans(0, html.length(), QuoteSpan.class); for (QuoteSpan span : spans) { diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java index a23ab7200819..d052d0758dc1 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java @@ -443,12 +443,17 @@ private static String getDocumentProviderPathKitkatOrHigher(final Context contex // TODO handle non-primary volumes } else if (isDownloadsDocument(uri)) { - final String id = DocumentsContract.getDocumentId(uri); + String id = DocumentsContract.getDocumentId(uri); if (id != null && id.startsWith("raw:")) { return id.substring(4); } + // https://github.com/Javernaut/WhatTheCodec/issues/2 + if (id != null && id.startsWith("msf:")) { + id = id.substring(4); + } + String[] contentUriPrefixesToTry = new String[]{ "content://downloads/public_downloads", "content://downloads/my_downloads", diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/UrlUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/UrlUtils.java index de16bb91cce8..0068ac9d4d8b 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/UrlUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/UrlUtils.java @@ -5,6 +5,8 @@ import android.webkit.MimeTypeMap; import android.webkit.URLUtil; +import androidx.annotation.Nullable; + import org.wordpress.android.util.AppLog.T; import java.io.UnsupportedEncodingException; @@ -257,6 +259,20 @@ public static boolean isImageUrl(String url) { || cleanedUrl.endsWith("gif") || cleanedUrl.endsWith("png"); } + public static @Nullable String getPageJumpOrNull(String url) { + if (TextUtils.isEmpty(url)) { + return null; + } + + if (url.contains("#") + && url.indexOf("#") < url.length() - 1 + && url.split("#").length == 2) { + return url.substring(url.indexOf('#') + 1); + } + + return null; + } + private static boolean isAtomicImageProxyUrl(String urlString) { return urlString.startsWith(ATOMIC_MEDIA_PROXY_URL_PREFIX) && urlString.endsWith(ATOMIC_MEDIA_PROXY_URL_SUFFIX); } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/logfile/LogFileHelpers.kt b/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/logfile/LogFileProvider.kt similarity index 100% rename from WordPressUtils/src/main/java/org/wordpress/android/util/helpers/logfile/LogFileHelpers.kt rename to WordPressUtils/src/main/java/org/wordpress/android/util/helpers/logfile/LogFileProvider.kt diff --git a/gradle.properties-example b/gradle.properties-example new file mode 100644 index 000000000000..5bac8ac50462 --- /dev/null +++ b/gradle.properties-example @@ -0,0 +1 @@ +android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a118be48be882fa18946b374de0bf77a651f374b..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch literal 58695 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 literal 54783 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From aa0bc75039fa4c3469a8c727d65b9727cba93b91 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Wed, 11 Nov 2020 10:28:40 +0100 Subject: [PATCH 287/343] Remove remaining media picker changes related to stories --- .../ui/photopicker/MediaPickerLauncher.kt | 17 +++-------------- .../android/ui/posts/PostsListActivity.kt | 10 +--------- .../stories/StoriesMediaPickerResultHandler.kt | 3 +-- .../android/ui/stories/StoryComposerActivity.kt | 6 +----- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/MediaPickerLauncher.kt b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/MediaPickerLauncher.kt index 917fb7caa312..28c0b39a3b6b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/MediaPickerLauncher.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/MediaPickerLauncher.kt @@ -13,6 +13,7 @@ import org.wordpress.android.ui.media.MediaBrowserType import org.wordpress.android.ui.media.MediaBrowserType.FEATURED_IMAGE_PICKER import org.wordpress.android.ui.media.MediaBrowserType.GRAVATAR_IMAGE_PICKER import org.wordpress.android.ui.media.MediaBrowserType.SITE_ICON_PICKER +import org.wordpress.android.ui.media.MediaBrowserType.WP_STORIES_MEDIA_PICKER import org.wordpress.android.ui.mediapicker.MediaPickerActivity import org.wordpress.android.ui.mediapicker.MediaPickerSetup import org.wordpress.android.ui.mediapicker.MediaPickerSetup.CameraSetup.ENABLED @@ -129,21 +130,9 @@ class MediaPickerLauncher @Inject constructor( fun showStoriesPhotoPickerForResult( activity: Activity, - site: SiteModel?, - requestCode: Int = RequestCodes.STORIES_PHOTO_PICKER + site: SiteModel? ) { - val browserType = MediaBrowserType.WP_STORIES_MEDIA_PICKER - // Temporarily disable new media picker for stories - if (false) { - val intent = MediaPickerActivity.buildIntent( - activity, - buildLocalMediaPickerSetup(browserType), - site - ) - activity.startActivityForResult(intent, requestCode) - } else { - ActivityLauncher.showPhotoPickerForResult(activity, browserType, site, null) - } + ActivityLauncher.showPhotoPickerForResult(activity, WP_STORIES_MEDIA_PICKER, site, null) } fun showGravatarPicker(fragment: Fragment) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt index 8a6b1abaa36c..ab1b535e36d1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListActivity.kt @@ -60,7 +60,6 @@ import org.wordpress.android.ui.utils.UiString import org.wordpress.android.util.AppLog import org.wordpress.android.util.SnackbarItem import org.wordpress.android.util.SnackbarSequencer -import org.wordpress.android.util.config.ConsolidatedMediaPickerFeatureConfig import org.wordpress.android.util.redirectContextClickToLongPressListener import org.wordpress.android.util.setLiftOnScrollTargetViewIdAndRequestLayout import org.wordpress.android.viewmodel.posts.PostListCreateMenuViewModel @@ -91,7 +90,6 @@ class PostsListActivity : LocaleAwareActivity(), @Inject internal lateinit var editPostRepository: EditPostRepository @Inject internal lateinit var mediaPickerLauncher: MediaPickerLauncher @Inject internal lateinit var storiesMediaPickerResultHandler: StoriesMediaPickerResultHandler - @Inject internal lateinit var consolidatedMediaPickerFeatureConfig: ConsolidatedMediaPickerFeatureConfig private lateinit var site: SiteModel @@ -447,13 +445,7 @@ class PostsListActivity : LocaleAwareActivity(), viewModel.handleEditPostResult(data) } else if (requestCode == RequestCodes.REMOTE_PREVIEW_POST) { viewModel.handleRemotePreviewClosing() - } else if (!consolidatedMediaPickerFeatureConfig.isEnabled() && - requestCode == RequestCodes.PHOTO_PICKER && - resultCode == Activity.RESULT_OK && - data != null) { - storiesMediaPickerResultHandler.handleMediaPickerResultForStories(data, this, site) - } else if (consolidatedMediaPickerFeatureConfig.isEnabled() && - requestCode == RequestCodes.STORIES_PHOTO_PICKER && + } else if (requestCode == RequestCodes.PHOTO_PICKER && resultCode == Activity.RESULT_OK && data != null) { storiesMediaPickerResultHandler.handleMediaPickerResultForStories(data, this, site) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt index cf0532126410..1ce2fb4b42ab 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoriesMediaPickerResultHandler.kt @@ -10,11 +10,10 @@ import org.wordpress.android.ui.media.MediaBrowserType import org.wordpress.android.ui.photopicker.MediaPickerConstants import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T.UTILS -import org.wordpress.android.util.config.ConsolidatedMediaPickerFeatureConfig import javax.inject.Inject class StoriesMediaPickerResultHandler -@Inject constructor(private val consolidatedMediaPickerFeatureConfig: ConsolidatedMediaPickerFeatureConfig) { +@Inject constructor() { /* return true if MediaPickerResult was handled */ fun handleMediaPickerResultForStories( data: Intent, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 08fc213f505a..de571566113b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -260,13 +260,9 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), } override fun showProvidedMediaPicker() { - // the ComposeLoopFrameActivity currently only knows about RequestCodes.PHOTO_PICKER and not about - // RequestCodes.STORIES_PHOTO_PICKER; as a temporary solution we are feeding the ComposeLoopFragmentActivity - // with the PHOTO_PICKER. We should add the STORIES_PHOTO_PICKER to the stories lib mediaPickerLauncher.showStoriesPhotoPickerForResult( this, - site, - RequestCodes.PHOTO_PICKER + site ) } From 22f54e7857a41b391b059202e04fe0364adf7d75 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 11 Nov 2020 13:24:31 +0200 Subject: [PATCH 288/343] Prevents saving post locally in preview mode --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 67acd59eb1c0..3a7ade10de3e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -860,7 +860,9 @@ private void startObserving() { return null; })); mEditPostRepository.getPostChanged().observe(this, postEvent -> postEvent.applyIfNotHandled(post -> { - mViewModel.savePostToDb(mEditPostRepository, mSite); + if (!mIsPreview) { + mViewModel.savePostToDb(mEditPostRepository, mSite); + } return null; })); } From da3cace84dbd9b34cfec6b632d47cf1b1883b0a5 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Wed, 11 Nov 2020 12:41:30 +0100 Subject: [PATCH 289/343] Fix issues in MockingInterceptor.kt --- .../java/org/wordpress/android/support/MockingInterceptor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/androidTest/java/org/wordpress/android/support/MockingInterceptor.kt b/WordPress/src/androidTest/java/org/wordpress/android/support/MockingInterceptor.kt index d16bbd809a3a..a22ddf0dfbb6 100644 --- a/WordPress/src/androidTest/java/org/wordpress/android/support/MockingInterceptor.kt +++ b/WordPress/src/androidTest/java/org/wordpress/android/support/MockingInterceptor.kt @@ -12,8 +12,8 @@ class MockingInterceptor : Interceptor { val request = chain.request() // Redirect all WordPress.com API requests to local mock server - if (request.url().host() == "public-api.wordpress.com") { - val newUrl = request.url().newBuilder() + if (request.url.host == "public-api.wordpress.com") { + val newUrl = request.url.newBuilder() .scheme("http") .host("localhost") .port(BaseTest.WIREMOCK_PORT) From 87fea95827bc2e3e98a048b596e1b5eefbf8c32c Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Wed, 11 Nov 2020 16:41:47 +0100 Subject: [PATCH 290/343] Copy media from system picker before they are passed down --- .../ui/mediapicker/MediaPickerActivity.kt | 14 ++-------- .../ui/mediapicker/MediaPickerFragment.kt | 6 +++++ .../ui/mediapicker/MediaPickerViewModel.kt | 9 +++++++ .../insert/DeviceListInsertUseCase.kt | 27 ++++++++----------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerActivity.kt index a7de91beec52..05dc7c34b7bf 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerActivity.kt @@ -24,7 +24,6 @@ import org.wordpress.android.ui.gif.GifPickerActivity import org.wordpress.android.ui.media.MediaBrowserActivity import org.wordpress.android.ui.mediapicker.MediaItem.Identifier import org.wordpress.android.ui.mediapicker.MediaPickerActivity.MediaPickerMediaSource.ANDROID_CAMERA -import org.wordpress.android.ui.mediapicker.MediaPickerActivity.MediaPickerMediaSource.ANDROID_PICKER import org.wordpress.android.ui.mediapicker.MediaPickerActivity.MediaPickerMediaSource.APP_PICKER import org.wordpress.android.ui.mediapicker.MediaPickerFragment.Companion.newInstance import org.wordpress.android.ui.mediapicker.MediaPickerFragment.MediaPickerAction @@ -185,18 +184,9 @@ class MediaPickerActivity : LocaleAwareActivity(), MediaPickerListener { val intent: Intent? = when (requestCode) { MEDIA_LIBRARY -> { data?.let { - val intent = Intent() val uris = WPMediaUtils.retrieveMediaUris(data) - if (mediaPickerSetup.queueResults) { - intent.putQueuedUris(uris) - } else { - intent.putUris(uris) - } - intent.putExtra( - EXTRA_MEDIA_SOURCE, - ANDROID_PICKER.name - ) - intent + pickerFragment?.urisSelectedFromSystemPicker(uris) + return } } TAKE_PHOTO -> { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerFragment.kt index 4ef9128396af..1a392f1d910a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerFragment.kt @@ -4,6 +4,7 @@ import android.Manifest.permission import android.app.Activity import android.content.Intent.ACTION_GET_CONTENT import android.content.Intent.ACTION_OPEN_DOCUMENT +import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.text.Html @@ -68,6 +69,7 @@ import org.wordpress.android.util.SnackbarItem import org.wordpress.android.util.SnackbarItem.Action import org.wordpress.android.util.SnackbarItem.Info import org.wordpress.android.util.SnackbarSequencer +import org.wordpress.android.util.UriWrapper import org.wordpress.android.util.WPLinkMovementMethod import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils @@ -384,6 +386,10 @@ class MediaPickerFragment : Fragment() { return true } + fun urisSelectedFromSystemPicker(uris: List) { + viewModel.urisSelectedFromSystemPicker(uris.map { UriWrapper(it) }) + } + private fun initializeSearchView(actionMenuItem: MenuItem) { var isExpanding = false actionMenuItem.setOnActionExpandListener(object : OnActionExpandListener { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt index d6d31c215061..eadbfde721f6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt @@ -74,6 +74,7 @@ import org.wordpress.android.ui.utils.UiString.UiStringResWithParams import org.wordpress.android.ui.utils.UiString.UiStringText import org.wordpress.android.util.LocaleManagerWrapper import org.wordpress.android.util.MediaUtilsWrapper +import org.wordpress.android.util.UriWrapper import org.wordpress.android.util.WPPermissionUtils import org.wordpress.android.util.distinct import org.wordpress.android.util.merge @@ -416,6 +417,10 @@ class MediaPickerViewModel @Inject constructor( fun performInsertAction() { val ids = selectedIdentifiers() + insertIdentifiers(ids) + } + + private fun insertIdentifiers(ids: List) { var job: Job? = null job = launch { var progressDialogJob: Job? = null @@ -635,6 +640,10 @@ class MediaPickerViewModel @Inject constructor( searchJob?.cancel() } + fun urisSelectedFromSystemPicker(uris: List) { + insertIdentifiers(uris.map { LocalUri(it) }) + } + data class MediaPickerUiState( val photoListUiModel: PhotoListUiModel, val softAskViewUiModel: SoftAskViewUiModel, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/insert/DeviceListInsertUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/insert/DeviceListInsertUseCase.kt index db36f5cf1e7d..0c71c85ca133 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/insert/DeviceListInsertUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/insert/DeviceListInsertUseCase.kt @@ -14,25 +14,20 @@ class DeviceListInsertUseCase( ) : MediaInsertUseCase { override suspend fun insert(identifiers: List) = flow { val localUris = identifiers.mapNotNull { it as? LocalUri } - val result = if (queueResults) { - emit(InsertModel.Progress(actionTitle)) - var failed = false - val fetchedUris = localUris.mapNotNull { localUri -> - val fetchedUri = wpMediaUtilsWrapper.fetchMedia(localUri.value.uri) - if (fetchedUri == null) { - failed = true - } - fetchedUri - } - if (failed) { - InsertModel.Error("Failed to fetch local media") - } else { - InsertModel.Success(fetchedUris.map { LocalUri(UriWrapper(it), queueResults) }) + emit(InsertModel.Progress(actionTitle)) + var failed = false + val fetchedUris = localUris.mapNotNull { localUri -> + val fetchedUri = wpMediaUtilsWrapper.fetchMedia(localUri.value.uri) + if (fetchedUri == null) { + failed = true } + fetchedUri + } + if (failed) { + emit(InsertModel.Error("Failed to fetch local media")) } else { - InsertModel.Success(localUris) + emit(InsertModel.Success(fetchedUris.map { LocalUri(UriWrapper(it), queueResults) })) } - emit(result) } class DeviceListInsertUseCaseFactory From db0230cbda10078a61fe3170ab5ed64b9be00948 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 11 Nov 2020 20:29:26 +0200 Subject: [PATCH 291/343] Prevents saving post locally in preview mode (#13363) --- .../java/org/wordpress/android/ui/posts/EditPostActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 7a2f0cdb6351..a6ddb16a82b8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -830,7 +830,9 @@ private void startObserving() { return null; })); mEditPostRepository.getPostChanged().observe(this, postEvent -> postEvent.applyIfNotHandled(post -> { - mViewModel.savePostToDb(mEditPostRepository, mSite); + if (!mIsPreview) { + mViewModel.savePostToDb(mEditPostRepository, mSite); + } return null; })); } From 03ef108995b89522a16b371dd33de87bef38fadd Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Thu, 12 Nov 2020 10:30:12 +0900 Subject: [PATCH 292/343] Update gutenberg-mobile commit hash --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index 3e94d94eea66..8294ef6c33b5 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit 3e94d94eea667ed3b4c8f435916b2e8312f56db7 +Subproject commit 8294ef6c33b560aaf100d2c1a047bbafe202205a From d8f8b828cd24e70394737ea7bc1b24eee9cd228d Mon Sep 17 00:00:00 2001 From: Alex Forcier Date: Thu, 12 Nov 2020 14:30:16 +0900 Subject: [PATCH 293/343] Update minimum Jetpack Stories version 9.1 is the first Jetpack release with the Story block as a production block (and not a beta block). --- .../src/main/java/org/wordpress/android/util/SiteUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java index a28116123296..571c279effde 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java @@ -30,8 +30,7 @@ public class SiteUtils { public static final String GB_EDITOR_NAME = "gutenberg"; public static final String AZTEC_EDITOR_NAME = "aztec"; public static final String WP_STORIES_CREATOR_NAME = "wp_stories_creator"; - // TODO Update to the first version with the story block as a production and not a beta block - public static final String WP_STORIES_JETPACK_VERSION = "8.8"; + public static final String WP_STORIES_JETPACK_VERSION = "9.1"; private static final int GB_ROLLOUT_PERCENTAGE_PHASE_1 = 100; private static final int GB_ROLLOUT_PERCENTAGE_PHASE_2 = 100; From 99bbe7d19aac27811d0acd0eb71771c35774b17d Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 12 Nov 2020 10:02:23 +0100 Subject: [PATCH 294/343] Run insert job on background --- .../ui/mediapicker/MediaPickerViewModel.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt index eadbfde721f6..08aa00b23f15 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt @@ -8,8 +8,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.wordpress.android.R import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.SiteModel @@ -349,11 +349,9 @@ class MediaPickerViewModel @Inject constructor( mediaPickerTracker.trackMediaPickerOpened(mediaPickerSetup) this.mediaLoader = mediaLoaderFactory.build(mediaPickerSetup, site) this.mediaInsertHandler = mediaInsertHandlerFactory.build(mediaPickerSetup, site) - launch(bgDispatcher) { - mediaLoader.loadMedia(loadActions).collect { domainModel -> - withContext(mainDispatcher) { - _domainModel.value = domainModel - } + launch { + mediaLoader.loadMedia(loadActions).flowOn(bgDispatcher).collect { domainModel -> + _domainModel.value = domainModel } } launch(bgDispatcher) { @@ -424,7 +422,7 @@ class MediaPickerViewModel @Inject constructor( var job: Job? = null job = launch { var progressDialogJob: Job? = null - mediaInsertHandler.insertMedia(ids).collect { + mediaInsertHandler.insertMedia(ids).flowOn(bgDispatcher).collect { when (it) { is InsertModel.Progress -> { progressDialogJob = launch { @@ -641,7 +639,10 @@ class MediaPickerViewModel @Inject constructor( } fun urisSelectedFromSystemPicker(uris: List) { - insertIdentifiers(uris.map { LocalUri(it) }) + launch { + delay(100) + insertIdentifiers(uris.map { LocalUri(it) }) + } } data class MediaPickerUiState( From 95f516f0a4f1b647ab015a538638b2e214fc17a2 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 12 Nov 2020 10:43:54 +0100 Subject: [PATCH 295/343] Enable consolidated media picker --- .../config/ConsolidatedMediaPickerFeatureConfig.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/config/ConsolidatedMediaPickerFeatureConfig.kt b/WordPress/src/main/java/org/wordpress/android/util/config/ConsolidatedMediaPickerFeatureConfig.kt index 72b19eac6453..7ed88d6e494f 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/config/ConsolidatedMediaPickerFeatureConfig.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/config/ConsolidatedMediaPickerFeatureConfig.kt @@ -1,15 +1,21 @@ package org.wordpress.android.util.config import org.wordpress.android.BuildConfig -import org.wordpress.android.annotation.FeatureInDevelopment +import org.wordpress.android.annotation.Feature +import org.wordpress.android.util.config.ConsolidatedMediaPickerFeatureConfig.Companion.CONSOLIDATED_MEDIA_PICKER_REMOTE_FIELD import javax.inject.Inject /** * Configuration of the Consolidated media picker */ -@FeatureInDevelopment +@Feature(CONSOLIDATED_MEDIA_PICKER_REMOTE_FIELD, true) class ConsolidatedMediaPickerFeatureConfig @Inject constructor(appConfig: AppConfig) : FeatureConfig( appConfig, - BuildConfig.CONSOLIDATED_MEDIA_PICKER -) + BuildConfig.CONSOLIDATED_MEDIA_PICKER, + CONSOLIDATED_MEDIA_PICKER_REMOTE_FIELD +) { + companion object { + const val CONSOLIDATED_MEDIA_PICKER_REMOTE_FIELD = "consolidated_media_picker_enabled" + } +} From ded7315e595c97c60b0ba8fc9dc572830d74c8e6 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:13:38 +0100 Subject: [PATCH 296/343] Refactor discover welcome card logic --- .../ui/reader/discover/ReaderDiscoverViewModel.kt | 4 ---- .../repository/usecases/GetDiscoverCardsUseCase.kt | 8 ++++---- .../reader/discover/ReaderDiscoverViewModelTest.kt | 13 +------------ .../usecases/GetDiscoverCardsUseCaseTest.kt | 10 ++++++++++ 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index a6d64fc001a9..e6706025e8e7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -131,9 +131,6 @@ class ReaderDiscoverViewModel @Inject constructor( _uiState.value = ShowFollowInterestsEmptyUiState } else { if (posts != null && posts.cards.isNotEmpty()) { - val discoverFeedContainsOnlyWelcomeCard = posts.cards.size == 1 && - posts.cards.filterIsInstance().isNotEmpty() - if (!discoverFeedContainsOnlyWelcomeCard) { _uiState.value = ContentUiState( convertCardsToUiStates(posts), reloadProgressVisibility = false, @@ -142,7 +139,6 @@ class ReaderDiscoverViewModel @Inject constructor( ) swipeToRefreshTriggered = false } - } } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCase.kt index 7ad5f24c079b..5dbdc45b6327 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCase.kt @@ -39,10 +39,6 @@ class GetDiscoverCardsUseCase @Inject constructor( cardJsonList ) - if (!appPrefsWrapper.readerDiscoverWelcomeBannerShown) { - cards.add(WelcomeBannerCard) - } - forLoop@ for (i in 0 until jsonObjects.length()) { val cardJson = jsonObjects.getJSONObject(i) when (cardJson.getString(ReaderConstants.JSON_CARD_TYPE)) { @@ -72,6 +68,10 @@ class GetDiscoverCardsUseCase @Inject constructor( } } } + + if (cards.isNotEmpty() && !appPrefsWrapper.readerDiscoverWelcomeBannerShown) { + cards.add(0, WelcomeBannerCard) + } } return@withContext ReaderDiscoverCards(cards) } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt index b0715fa86fe0..2918ac45cf2c 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt @@ -275,7 +275,7 @@ class ReaderDiscoverViewModelTest { } @Test - fun `if welcome card exists with other cards, it is present in ContentUiState`() = test { + fun `if welcome card exists then ReaderWelcomeBannerCardUiState will be present`() = test { // Arrange val uiStates = init(autoUpdateFeed = false).uiStates // Act @@ -288,17 +288,6 @@ class ReaderDiscoverViewModelTest { assertThat(contentUiState.cards.first()).isInstanceOf(ReaderWelcomeBannerCardUiState::class.java) } - @Test - fun `if welcome card exists as the only card in the feed then ContentUiState is not shown`() = test { - // Arrange - val uiStates = init(autoUpdateFeed = false).uiStates - // Act - fakeDiscoverFeed.value = ReaderDiscoverCards(createWelcomeBannerCard()) - // Assert - assertThat(uiStates.size).isEqualTo(1) - assertThat(uiStates[0]).isInstanceOf(LoadingUiState::class.java) - } - @Test fun `WelcomeBannerCard has welcome title set to it`() = test { // Arrange diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCaseTest.kt index 377a43111138..bee12a284b2c 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/usecases/GetDiscoverCardsUseCaseTest.kt @@ -97,6 +97,16 @@ class GetDiscoverCardsUseCaseTest { assertThat(result.cards.filterIsInstance()).size().isEqualTo(0) } + @Test + fun `welcome card is not added to the list of cards when there are no other cards`() = test { + // Arrange + whenever(mockedJsonArray.length()).thenReturn(0) + // Act + val result = useCase.get() + // Assert + assertThat(result.cards.filterIsInstance()).size().isEqualTo(0) + } + @Test fun `interest you might like card json is transformed into InterestsYouMayLikeCard object`() = test { // Arrange From 51ab64c045d5f80a6562574abec502c9324afc68 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:14:59 +0100 Subject: [PATCH 297/343] Stop saving lastUpdated flag when /cards response is empty --- .../ui/reader/services/discover/ReaderDiscoverLogic.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt index 9b54ec0ebf41..f66c29658cb7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt @@ -150,7 +150,9 @@ class ReaderDiscoverLogic( val nextPageHandle = parseDiscoverCardsJsonUseCase.parseNextPageHandle(json) appPrefsWrapper.readerCardsPageHandle = nextPageHandle - readerTagTableWrapper.setTagLastUpdated(ReaderTag.createDiscoverPostCardsTag()) + if (cards.isNotEmpty()) { + readerTagTableWrapper.setTagLastUpdated(ReaderTag.createDiscoverPostCardsTag()) + } resultListener.onUpdateResult(HAS_NEW) } From 784041ae515b7b8b7fd452da64889f0078f1b7aa Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:16:08 +0100 Subject: [PATCH 298/343] Stop updating discover feed when local cache is empty --- .../ui/reader/repository/ReaderDiscoverDataProvider.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt index 3723fea6f94a..f2d44d6554f2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt @@ -117,7 +117,9 @@ class ReaderDiscoverDataProvider @Inject constructor( val refresh = shouldAutoUpdateTagUseCase.get(readerTag) if (forceReload || !existsInMemory) { val result = getDiscoverCardsUseCase.get() - _discoverFeed.postValue(result) + if (result.cards.isNotEmpty()) { + _discoverFeed.postValue(result) + } } if (refresh) { From 4feab315ce5705f1e947999e9b828ba97bb480e9 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:16:56 +0100 Subject: [PATCH 299/343] Remove R.string import --- .../android/ui/reader/discover/ReaderDiscoverViewModel.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index e6706025e8e7..2fb17d335118 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.Observer import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.launch import org.wordpress.android.R -import org.wordpress.android.R.string import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_DISCOVER_PAGINATED import org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_DISCOVER_TOPIC_TAPPED @@ -167,7 +166,7 @@ class ReaderDiscoverViewModel @Inject constructor( return posts.cards.map { card -> when (card) { is WelcomeBannerCard -> ReaderWelcomeBannerCardUiState( - titleRes = string.reader_welcome_banner + titleRes = R.string.reader_welcome_banner ) is ReaderPostCard -> postUiStateBuilder.mapPostToUiState( post = card.post, @@ -247,7 +246,7 @@ class ReaderDiscoverViewModel @Inject constructor( _snackbarEvents.postValue( Event( SnackbarMessageHolder( - UiStringRes(string.reader_error_request_failed_title) + UiStringRes(R.string.reader_error_request_failed_title) ) ) ) From 9b18ad0f8a6ca2e0a422342bd1e7d5a6ece74c65 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:20:32 +0100 Subject: [PATCH 300/343] Rename empty ui state in discover vm --- .../android/ui/reader/discover/ReaderDiscoverViewModel.kt | 8 ++++---- .../ui/reader/discover/ReaderDiscoverViewModelTest.kt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index 2fb17d335118..81eb60485f04 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -26,7 +26,7 @@ import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderPostUiSt import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderRecommendedBlogsCardUiState.ReaderRecommendedBlogUiState import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderWelcomeBannerCardUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState -import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowFollowInterestsEmptyUiState +import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoFollowedTagsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ErrorUiState.RequestFailedErrorUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview @@ -53,7 +53,7 @@ import javax.inject.Inject import javax.inject.Named const val INITIATE_LOAD_MORE_OFFSET = 3 -const val PHOTON_WIDTH_QUALITY_RATION = 0.5 // load images in 1/2 screen width to save users's data +const val PHOTON_WIDTH_QUALITY_RATION = 0.5 // load images in 1/2 screen width to save users' data const val FEATURED_IMAGE_HEIGHT_WIDTH_RATION = 0.56 // 9:16 class ReaderDiscoverViewModel @Inject constructor( @@ -127,7 +127,7 @@ class ReaderDiscoverViewModel @Inject constructor( launch { val userTags = getFollowedTagsUseCase.get() if (userTags.isEmpty()) { - _uiState.value = ShowFollowInterestsEmptyUiState + _uiState.value = ShowNoFollowedTagsUiState } else { if (posts != null && posts.cards.isNotEmpty()) { _uiState.value = ContentUiState( @@ -449,7 +449,7 @@ class ReaderDiscoverViewModel @Inject constructor( ) } sealed class EmptyUiState constructor(val titleResId: Int) : DiscoverUiState(fullscreenEmptyVisibility = true) { - object ShowFollowInterestsEmptyUiState : EmptyUiState( + object ShowNoFollowedTagsUiState : EmptyUiState( titleResId = R.string.reader_discover_empty_title ) } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt index 2918ac45cf2c..05681dd96d87 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt @@ -41,7 +41,7 @@ import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderRecommen import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderWelcomeBannerCardUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState -import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowFollowInterestsEmptyUiState +import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoFollowedTagsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.OpenEditorForReblog import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview @@ -209,7 +209,7 @@ class ReaderDiscoverViewModelTest { viewModel.start(parentViewModel) // Assert assertThat(uiStates.size).isEqualTo(2) - assertThat(uiStates[1]).isInstanceOf(ShowFollowInterestsEmptyUiState::class.java) + assertThat(uiStates[1]).isInstanceOf(ShowNoFollowedTagsUiState::class.java) } @Test From f2eb0548af52f393dce258f773aa2d74ce2d2ccd Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:56:57 +0100 Subject: [PATCH 301/343] Add support for showing empty screen when /cards response is empty --- .../discover/ReaderDiscoverViewModel.kt | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index 81eb60485f04..85902035100b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -27,6 +27,7 @@ import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderRecommen import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderWelcomeBannerCardUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoFollowedTagsUiState +import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoPostsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ErrorUiState.RequestFailedErrorUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview @@ -130,14 +131,16 @@ class ReaderDiscoverViewModel @Inject constructor( _uiState.value = ShowNoFollowedTagsUiState } else { if (posts != null && posts.cards.isNotEmpty()) { - _uiState.value = ContentUiState( - convertCardsToUiStates(posts), - reloadProgressVisibility = false, - loadMoreProgressVisibility = false, - scrollToTop = swipeToRefreshTriggered - ) - swipeToRefreshTriggered = false - } + _uiState.value = ContentUiState( + convertCardsToUiStates(posts), + reloadProgressVisibility = false, + loadMoreProgressVisibility = false, + scrollToTop = swipeToRefreshTriggered + ) + swipeToRefreshTriggered = false + } else { + _uiState.value = ShowNoPostsUiState + } } } } @@ -452,6 +455,9 @@ class ReaderDiscoverViewModel @Inject constructor( object ShowNoFollowedTagsUiState : EmptyUiState( titleResId = R.string.reader_discover_empty_title ) + object ShowNoPostsUiState : EmptyUiState( + titleResId = R.string.reader_discover_no_posts_title + ) } } } From b46159dc6970cffdd43c19f90600b3ad53565215 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 11:58:07 +0100 Subject: [PATCH 302/343] Add throttle to discoverFeed --- .../ui/reader/repository/ReaderDiscoverDataProvider.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt index f2d44d6554f2..9f16704b2f06 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt @@ -33,6 +33,7 @@ import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T.READER import org.wordpress.android.util.EventBusWrapper import org.wordpress.android.util.perform +import org.wordpress.android.util.throttle import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.ReactiveMutableLiveData import java.util.concurrent.atomic.AtomicBoolean @@ -40,6 +41,8 @@ import javax.inject.Inject import javax.inject.Named import kotlin.coroutines.CoroutineContext +private const val DISCOVER_FEED_THROTTLE = 500L + class ReaderDiscoverDataProvider @Inject constructor( @Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher, private val eventBusWrapper: EventBusWrapper, @@ -60,6 +63,11 @@ class ReaderDiscoverDataProvider @Inject constructor( private val _discoverFeed = ReactiveMutableLiveData( onActive = { onActiveDiscoverFeed() }, onInactive = { onInactiveDiscoverFeed() }) val discoverFeed: LiveData = _discoverFeed + /* Since we listen to all updates of the database the feed is sometimes updated several times within a few + ms. For example, when we are about to insert posts, we delete them first. However, we don't need/want + to propagate this state to the VM. */ + .throttle(this, offset = DISCOVER_FEED_THROTTLE) + private var hasMoreCards = true private val _communicationChannel = MutableLiveData>() From d39755dd1bece52d957c7e21cf2b72a8db909848 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 12:17:40 +0100 Subject: [PATCH 303/343] Clear tagLastUpdated flag on empty server response --- .../ui/reader/services/discover/ReaderDiscoverLogic.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt index f66c29658cb7..023188f2a4ff 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/discover/ReaderDiscoverLogic.kt @@ -150,7 +150,9 @@ class ReaderDiscoverLogic( val nextPageHandle = parseDiscoverCardsJsonUseCase.parseNextPageHandle(json) appPrefsWrapper.readerCardsPageHandle = nextPageHandle - if (cards.isNotEmpty()) { + if (cards.isEmpty()) { + readerTagTableWrapper.clearTagLastUpdated(ReaderTag.createDiscoverPostCardsTag()) + } else { readerTagTableWrapper.setTagLastUpdated(ReaderTag.createDiscoverPostCardsTag()) } From bc5e1c9b5de021db1bd801a737a39ef337669911 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Thu, 12 Nov 2020 12:55:56 +0100 Subject: [PATCH 304/343] Update translations --- WordPress/src/main/res/values-ar/strings.xml | 66 +- WordPress/src/main/res/values-bg/strings.xml | 1 - WordPress/src/main/res/values-cs/strings.xml | 2 - WordPress/src/main/res/values-cy/strings.xml | 1 - WordPress/src/main/res/values-da/strings.xml | 1 - WordPress/src/main/res/values-de/strings.xml | 33 +- WordPress/src/main/res/values-el/strings.xml | 3 - .../src/main/res/values-en-rAU/strings.xml | 2 - .../src/main/res/values-en-rCA/strings.xml | 7 - .../src/main/res/values-en-rGB/strings.xml | 50 +- .../src/main/res/values-es-rCL/strings.xml | 2 - .../src/main/res/values-es-rCO/strings.xml | 1952 ++++++++++++++-- .../src/main/res/values-es-rMX/strings.xml | 50 +- .../src/main/res/values-es-rVE/strings.xml | 50 +- WordPress/src/main/res/values-es/strings.xml | 50 +- WordPress/src/main/res/values-eu/strings.xml | 1 - WordPress/src/main/res/values-fr/strings.xml | 54 +- WordPress/src/main/res/values-gd/strings.xml | 1 - WordPress/src/main/res/values-gl/strings.xml | 3 - WordPress/src/main/res/values-he/strings.xml | 8 - WordPress/src/main/res/values-hr/strings.xml | 1 - WordPress/src/main/res/values-id/strings.xml | 56 +- WordPress/src/main/res/values-is/strings.xml | 1 - WordPress/src/main/res/values-it/strings.xml | 40 +- WordPress/src/main/res/values-ja/strings.xml | 56 +- WordPress/src/main/res/values-kmr/strings.xml | 2023 +++++++++-------- WordPress/src/main/res/values-ko/strings.xml | 52 +- WordPress/src/main/res/values-ms/strings.xml | 1 - WordPress/src/main/res/values-nb/strings.xml | 40 +- WordPress/src/main/res/values-nl/strings.xml | 34 +- WordPress/src/main/res/values-pl/strings.xml | 37 +- .../src/main/res/values-pt-rBR/strings.xml | 56 +- WordPress/src/main/res/values-ro/strings.xml | 52 +- WordPress/src/main/res/values-ru/strings.xml | 50 +- WordPress/src/main/res/values-sk/strings.xml | 2 - WordPress/src/main/res/values-sq/strings.xml | 48 +- WordPress/src/main/res/values-sr/strings.xml | 1651 +++++++++++++- WordPress/src/main/res/values-sv/strings.xml | 50 +- WordPress/src/main/res/values-th/strings.xml | 1 - WordPress/src/main/res/values-tr/strings.xml | 56 +- WordPress/src/main/res/values-vi/strings.xml | 1 - .../src/main/res/values-zh-rCN/strings.xml | 8 - .../src/main/res/values-zh-rHK/strings.xml | 56 +- .../src/main/res/values-zh-rTW/strings.xml | 55 +- 44 files changed, 5329 insertions(+), 1435 deletions(-) diff --git a/WordPress/src/main/res/values-ar/strings.xml b/WordPress/src/main/res/values-ar/strings.xml index 06d3e0aa80bb..23fb13058a8d 100644 --- a/WordPress/src/main/res/values-ar/strings.xml +++ b/WordPress/src/main/res/values-ar/strings.xml @@ -1,18 +1,72 @@ + تخطي + اختيار + اختر تخطيط الصفحة الرئيسية المفضَّل لديك. يمكنك تخصيصه أو تغييره لاحقًا. + اختيار تصميم + أضف تصنيفاً جديداً + التصنيفات + لم يتم التعيين + التصنيفات + إضافة تصنيف + التخطيطات غير متوفرة بسبب خطأ + التخطيطات غير متوفرة دون اتصال انترنت + أخبار الويب + باميلا نجوين + الطهي + الفن + زراعة الحدائق + الموسيقى + السياسة + أفضل عشرة مقاهي + أفضل المعجبين بالعالم + المتاحف في لندن + كرة القدم + موسيقى Rock n\' Roll الأسبوعية + الحصول على الإلهام + مرحبًا بك في أداة إنشاء مواقع الويب الأكثر رواجًا في العالم. + باستخدام المحرر الضخم والقوي، يمكنك النشر أثناء التنقّل. + شاهد التعليقات والإشعارات في الوقت الفعلي. + مواقع لمتابعتها + فشل تحميل الوسائط + نعمل بجد لإضافة المزيد من المكوِّنات مع كل إصدار. + لم يتم دعم \"%s\" بالكامل زر المساعدة تحرير باستخدام محرر الويب اختيار الصور + إنشاء مقالة قصة + تُنشر المقالة كمقالة جديدة على موقعك حتى لا يفوِّت جمهورك أي شيء. + اجمع بين الصور ومقاطع الفيديو والنصوص لإنشاء مقالات قصة جذابة وقابلة للنقر سيحبها زائروك. + مقالات القصة لا تختفي + تتوافر القصص الآن للجميع + مثال لعنوان قصة + كيفية إنشاء مقالة قصة + يحق لك الوصول مبكرًا إلى مقالات القصص، ويسعدنا أن نجربها. + مقدمة لمقالات القصص + تم إنشاء صفحة فارغة + تم إنشاء صفحة + معاينة التخطيط + ⁦%1$s⁩ تم رفض الوصول إلى صورك. لإصلاح هذا، حرِّر صلاحياتك وشغِّل ⁦%2$s⁩ و⁦%3$s⁩. + فشل إدراج الوسائط. + فشل إدراج الوسائط: %s + الاختيار من مكتبة وسائط ووردبريس رجوع البدء بواسطة + اتباع الموضوعات لاكتشاف مدونات جديدة فتح الموقع الإلكتروني + يتعر وسم هذا المرجع بأنه بريد مزعج + عدم الوسم بأنه بريد مزعج + وضع علامة مزعج + رفع وسائط بتنسيق gif + رفع وسائط المخزون + رفع الوسائط ابحث أو اكتب رابطاً تحديد عنصر أضف رابط الهاتف هذا @@ -129,14 +183,13 @@ Language: ar استبدال صورة أو فيديو استبدال الفيديو المتابعة باستخدام WordPress.com - أدخل رابط موقعك - شعار ووردبريس تم إزالة المكوّن اختيار صورة اختيار صورة أو فيديو اختيار فيديو إذا تابعت مع Google وليس لديك حساب WordPress.com بالفعل، فأنت تقوم بإنشاء حساب وتوافق على %1$sشروط الخدمة%2$s. تأكيد التسجيل + أدخل رابط موقعك الحالي بالمتابعة، أنت توافق على %1$sشروط الخدمة%2$s. سنرسل لك عبر البريد الإلكتروني رابطًا لإنشاء حسابك الجديد على WordPress.com. سنستخدم عنوان البريد الإلكتروني هذا لإنشاء حسابك الجديد على WordPress.com. @@ -150,8 +203,6 @@ Language: ar لم تشاهد البريد الإلكتروني؟ تحقّق من مجلد البريد المزعج أو البريد العشوائي. سنرسل لك عبر البريد الإلكتروني رابطًا يسجّل دخولك على الفور، دون الحاجة إلى كلمة مرور. إعادة تعيين كلمة المرور الخاصة بك - 37\% من مواقع الويب مبنية على ووردبريس. - أطلق العنان لقدرات منشئ المواقع الأكثر مرونة. إرسال الرابط عبر البريد الإلكتروني إنشاء حساب أو اكتب كلمة المرور الخاصة بك @@ -461,7 +512,6 @@ Language: ar ستتم مزامنة هذه المقالة فورًا. هل أنت جاهز للمزامنة؟ هذا النطاق غير متاح. - المقدمة -%s تعذر علينا الوصول إلى موقعك. سيتعين عليك الوصول إلى مضيفك لحل هذا الأمر. تعذر علينا الوصول إلى موقعك بسبب حدوث مشكلة في <b>شهادة SSL</b>. سيتعين عليك الوصول إلى مضيفك لحل هذا الأمر. @@ -1286,7 +1336,6 @@ Language: ar تحرير الصورة اختيار الموقع حساب جديد - تسجيل الدخول إذا كنت بحاجة إلى مزيد من المساحة، ففكر في ترقية خطة ووردبريس الخاصة بك. تم تسجيل الدخول كـ تفاصيل الشخص @@ -2221,7 +2270,6 @@ Language: ar تم نسخ سجلات التطبيق إلى الحافظة مقالات جديدة حدث خطأ أثناء نسخ النص إلى الحافظة - مواقع قد تحوز إعجابك هذا الموقع فارغ جاري رفع المقالة %1$d من الشهور @@ -2343,7 +2391,6 @@ Language: ar التغيرات المحلية إعدادات المقالة تمت الموافقة - مطلوب ترخيص هذه المدونة مخفية و لايمكن تحميلها . قم بتفعيلها مرة اخرى من الإعدادات وأعد المحاولة. حدث خطأ اثناء إنشاء قاعدة بيانات التطبيق. حاول إعادة تنصيب التطبيق. سلة المهملات @@ -2366,6 +2413,7 @@ Language: ar إلغاء التحرير غير قادر على إزالة هذا الموضوع غير قادر على إضافة هذا الموضوع + التخويل مطلوب مشاركة الرابط يتم الآن جلب المقالات … رد diff --git a/WordPress/src/main/res/values-bg/strings.xml b/WordPress/src/main/res/values-bg/strings.xml index 6dc094a03946..06745b9fbb98 100644 --- a/WordPress/src/main/res/values-bg/strings.xml +++ b/WordPress/src/main/res/values-bg/strings.xml @@ -663,7 +663,6 @@ Language: bg Нови публикации Качване Възникна грешка при копиране на текста в clipboard-а - Блогове които бихте харесали Логовете на приложението бяха копирани в буфера за обмен на данни Видеа Ден diff --git a/WordPress/src/main/res/values-cs/strings.xml b/WordPress/src/main/res/values-cs/strings.xml index 22d128f6ccf2..9651adec0374 100644 --- a/WordPress/src/main/res/values-cs/strings.xml +++ b/WordPress/src/main/res/values-cs/strings.xml @@ -419,7 +419,6 @@ Language: cs_CZ Upravit fotku Vybrat stránku Nový účet - Přihlášení Přihlášen jako Osobní profil Detail souboru @@ -1348,7 +1347,6 @@ Language: cs_CZ Aplikační protokoly byly zkopírovány do schránky Tento web je prázdný Nové příspěvky - Stránky které by se vám mohly líbit Při kopírování textu do schránky nastala chyba Nahrávám příspěvek %1$d roky diff --git a/WordPress/src/main/res/values-cy/strings.xml b/WordPress/src/main/res/values-cy/strings.xml index d467024f85b7..42b8c884594e 100644 --- a/WordPress/src/main/res/values-cy/strings.xml +++ b/WordPress/src/main/res/values-cy/strings.xml @@ -422,7 +422,6 @@ Language: cy_GB Cofnodion y rhaglen wedi\'u copïo i\'r clipfwrdd Cofnodion newydd Digwyddodd gwall wrth gopïo testun i\'r clipfwrdd - Gwefannau fyddech chi\'n eu hoffi %1$d mis Blwyddyn %1$d blwyddyn diff --git a/WordPress/src/main/res/values-da/strings.xml b/WordPress/src/main/res/values-da/strings.xml index 2cb09b1f779d..b16136acba0d 100644 --- a/WordPress/src/main/res/values-da/strings.xml +++ b/WordPress/src/main/res/values-da/strings.xml @@ -105,7 +105,6 @@ Language: da_DK Nye indlæg Log-filerne er kopieret til udklipsholderen Der skete en fejl under kopieringen af teksten til udklipsholderen - Websteder du måske vil synes om sekunder siden et minut siden En dag diff --git a/WordPress/src/main/res/values-de/strings.xml b/WordPress/src/main/res/values-de/strings.xml index e58ca2dcde5c..ba225e4a92d7 100644 --- a/WordPress/src/main/res/values-de/strings.xml +++ b/WordPress/src/main/res/values-de/strings.xml @@ -1,6 +1,6 @@ + <b>Johan Brandt</b> responded to your post + You received <b>50 likes</b> on your site today + <b>Madison Ruiz</b> liked your post + Scrollable block menu opened. Select a block. + Scrollable block menu closed. + Skip + Choose + Pick your favourite homepage layout. You can customise or change it later. + Choose a design + Tap retry when you\'re back online, or create a blank page using the button below. + Layouts not available while offline + Tap retry or create a blank page using the button below. + Layouts not available due to an error + Add Category + Add New Category + Categories + Not set + Categories + Museums in London + The World\'s Best Fans + My Top Ten Cafés + Politics + Music + Gardening + Football + Cooking + Art + Rock ‘n’ Roll Weekly + Web News + Pamela Nguyen + I am so inspired by photographer Cameron Karsten’s work. I will be trying these techniques on my next + Getting Inspired + Follow your favourite sites and discover new reads. + Watch your audience grow with in-depth analytics. + See comments and notifications in real time. + With the powerful editor, you can post on the go. + Welcome to the world’s most popular website builder. + Media loading failed + Sites to follow We are working hard to add more blocks with each release. \'%s\' is not fully supported Help button @@ -20,7 +59,6 @@ Language: en_GB How to create a story post You\'ve got early access to Story Posts and we\'d love for you to give it a try. Introducing Story Posts - An error occurred while fetching the layouts Blank page created Page created Layout Preview @@ -158,8 +196,7 @@ Language: en_GB Choose image or video Choose image Block removed - WordPress Logo - Enter your site address + Enter your existing site address Continue with WordPress.com Signup confirmation If you continue with Google and don\'t already have a WordPress.com account, you are creating an account and you agree to our %1$sTerms of Service%2$s. @@ -181,8 +218,6 @@ Language: en_GB Or type your password Create account Send link by email - Unlock the power of the most flexible website builder. - 37\% of the web is built on WordPress. Reset your password There was a problem handling the request. Please try again later. Give your site a name that reflects its personality and topic. First impressions count! @@ -487,7 +522,6 @@ Language: en_GB This post will be synced immediately. Ready to Sync? This domain is unavailable - Introduction -%s We were unable to access your site. You will need to reach out to your host to resolve this. We were unable to access your site because of a problem with the <b>SSL Certificate</b>. You will need to reach out to your host to resolve this. @@ -1313,7 +1347,6 @@ Language: en_GB Edit Photo Pick site New account - Login Logged in as Person detail File details @@ -2247,7 +2280,6 @@ Language: en_GB Application logs have been copied to the clipboard This site is empty New posts - Sites you may like An error occurred while copying text to clipboard Uploading post %1$d years diff --git a/WordPress/src/main/res/values-es-rCL/strings.xml b/WordPress/src/main/res/values-es-rCL/strings.xml index 247bd255b33d..e9d693764c57 100644 --- a/WordPress/src/main/res/values-es-rCL/strings.xml +++ b/WordPress/src/main/res/values-es-rCL/strings.xml @@ -485,7 +485,6 @@ Language: es_CL Comentario marcado como no spam Comentario marcado como spam Cuenta nueva - Iniciar Sesión Comentario aprobado Comentario eliminado Me gusta el comentario @@ -1421,7 +1420,6 @@ Language: es_CL Los informes de la aplicación se han copiado al portapapeles Nuevas entradas Ocurrió un error al copiar el texto en el portapapeles - Sitios que te podrían gustar Este sitio está vacío Subiendo entrada Obteniendo temas… diff --git a/WordPress/src/main/res/values-es-rCO/strings.xml b/WordPress/src/main/res/values-es-rCO/strings.xml index 57802f345a9e..578788760f6b 100644 --- a/WordPress/src/main/res/values-es-rCO/strings.xml +++ b/WordPress/src/main/res/values-es-rCO/strings.xml @@ -1,24 +1,1570 @@ + Se ha abierto el menú de bloques desplazable. Selecciona un bloque. + Se ha cerrado el menú de bloques desplazable. + Omitir + Elegir + Elige tu diseño de página de inicio favorito. Puedes personalizarlo o cambiarlo más tarde. + Elige un diseño + Toca «Reintentar» cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. + Los diseños no están disponibles sin conexión + Toca «Reintentar» o crea una página en blanco usando el botón a continuación. + Los diseños no están disponibles debido a un error + Añadir categoría + Añadir una nueva categoría + Categorías + No establecido + Categorías + Museos en Londres + Los mejores fanáticos del mundo + Mis diez mejores cafés + Política + Música + Jardinería + Fútbol + Cocina + Arte + Rock n\' roll semanal + Noticias web + Pamela Nguyen + Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo + Inspírate + Sigue tus sitios favoritos y descubre nuevas lecturas. + Observa cómo crece tu audiencia con analíticas avanzadas. + Con el potente editor puedes publicar sobre la marcha. + Bienvenido al maquetador web más popular del mundo. + La carga del medio ha fallado + Sitios a seguir + Estamos trabajando duro para añadir más bloques con cada versión. + «%s» no es totalmente compatible + Botón de ayuda + Editar usando el editor web + Elegir las imágenes + Crear una entrada de historia + Son publicados como una nueva entrada de blog en tu sitio para que tu audiencia nunca se pierda nada. + Las entradas de historias no desaparecen + Combina fotos, vídeos y texto para crear entradas de historias atractivas y accesibles que les encantarán a tus visitantes. + Ahora las historias son para todos + Título de la entrada de historia + Cómo crear una entrada de historias + Tienes acceso anticipado a las entradas de historias y nos encantaría que las probaras. + Presentación de las entradas de historias + Página en blanco creada + Página creada + Vista previa del diseño + %1$s ha denegado el acceso a tus fotos. Para corregirlo, edita tus permisos y activa %2$s y %3$s. + Inserción del medio fallida. + Ha fallado la inserción del medio: %s + Elige desde la biblioteca de medios de WordPress + Volver + Primeros pasos + Sigue temáticas y descubre nuevos blogs + Por + Este referido no puede ser marcado como spam + Desmarcar como spam + Marcar como spam + Abrir la web + Subiendo medios GIF + Subiendo medios de inventarios + Subiendo medios + Seleccionar un elemento + Busca o escribe la URL + Añadir este enlace de teléfono + Añadir este enlace + Añadir este enlace de correo electrónico + «No hay conexión a Internet.\nNo están disponibles las sugerencias.» + Negrita + Moderno + Alegre + Fuerte + Clásico + Casual + Tienes que conceder permisos de grabación de audio a la aplicación para grabar vídeo + %s + %s seleccionado + Obtener un enlace de acceso por correo electrónico + Vaya, no encontramos una cuenta de WordPress.com conectada a esta dirección de correo electrónico. + Micrófono + No se puede mostrar este comentario + Navegar por elementos Informar de esta entrada Ángulo Bienvenido al Lector. Descubre millones de blogs a tu alcance. Ha ocurrido un error interno del servidor Tu acción no está permitida %1$s elementos más + Seleccionar un diseño + Nota: el diseño de la columna puede variar entre temas y tamaños de pantalla + Crear una entrada o historia + Crear una página + Crear una entrada + \@string/contact_support + Puede que te guste + Ocultar + Leyenda del vídeo. Vacía + Actualiza el título. + Pegar el bloque después + Título de la página. %s + Título de la página. Vacío + Ha ocurrido un error al reproducir tu vídeo + Este dispositivo no es compatible con la API de Camera2. + No se ha podido guardar el vídeo + Error al guardar la imagen + Operación en progreso, inténtalo de nuevo + No se ha podido encontrar la diapositiva de la historia + Ver el almacenamiento + Tenemos que guardar la historia en tu dispositivo antes de que pueda ser publicada. Revisa tus ajustes de almacenamiento y elimina archivos para ganar espacio. + Insuficiente almacenamiento en el dispositivo + Intenta volver a guardar o borrar las diapositivas y, después, intenta volver a publicar tu historia. + No se han podido guardar %1$d diapositivas + No se ha podido guardar 1 diapositiva + Gestionar + %1$d diapositivas necesitan una acción + 1 diapositiva necesita una acción + No se ha podido subir «%1$s» + No se ha podido subir «%1$s» + Publicado «%1$s» + Subiendo «%1$s»… + Quedan %1$d diapositivas + Queda 1 diapositiva + varias historias + Guardando «%1$s»… + Sin título + Descartar + La entrada de tu historia no se guardará como borrador. + ¿Descartar la entrada de la historia? + Borrar + Esta diapositiva aún no ha sido guardada. Si borras esta diapositiva, perderás cualquier edición que hayas hecho. + Esta diapositiva será eliminada de tu historia. + ¿Borrar la diapositiva de la historia? + Borrar la diapositiva + Cambiar el color del texto + Cambiar la alineación del texto + con error + seleccionado + sin seleccionar + Diapositiva + Reintentar + Guardado + Cerrar + Siguiente + Hecho + Compartir con + COMPARTIR + Guardado en fotos + Reintentar + Guardado + Guardando + Flash + Girar + Sonido + Texto + Más + Pegatinas + Flash + Girar la cámara + Capturar + Vista previa + Crear una página + Crear una página en blanco + Empieza eligiendo entre una amplia variedad de diseños de página prefabricados. O simplemente empieza con una página en blanco. + Elegir un diseño + Pon un título a tu historia + Crear una entrada o historia + Crear una entrada, página o historia + Toca crear %1$s. %2$s Después selecciona <b>Entrada del blog</b> + Elegir el dispositivo + Entrada de la historia + Para la edición de los iconos del sitio en sitios WordPress autoalojados se necesita el plugin Jetpack. + No se ha podido encontrar el salto de página enlazado + No se puede subir el archivo.\nSe ha superado la cuota de almacenamiento. + Cuota de almacenamiento superada + Añadir un archivo + Reemplazar el vídeo + Reemplazar la imagen o vídeo + Elegir un vídeo + Elegir una imagen o vídeo + Elegir una imagen + Bloque eliminado + Introduce la dirección de tu sitio existente + Continuar con WordPress.com + Confirmación del registro + Si continúas con Google y aún no tienes una cuenta de WordPress.com, crearás una cuenta y aceptas nuestros %1$stérminos del servicio%2$s. + Si continúas, aceptas nuestros %1$stérminos del servicio%2$s. + Te enviaremos por correo electrónico un enlace de registro para crear tu nueva cuenta de WordPress.com. + Usaremos esta dirección de correo electrónico para crear tu nueva cuenta de WordPress.com. + Te hemos enviado por correo electrónico un enlace de registro para crear tu nueva cuenta de WordPress.com. Comprueba tu correo electrónico en este dispositivo y toca el enlace en el correo electrónico que has recibido de WordPress.com. + Introduce la información de tu cuenta para %1$s. + o + Continuar con Google + Encuentra la dirección de tu sitio + Hecho + ¿No ves el correo electrónico? Comprueba tu carpeta de spam o correo no deseado. + Comprueba tu correo electrónico en este dispositivo y toca el enlace en el correo electrónico que has recibido de WordPress.com. + Te enviaremos por correo electrónico un enlace que te hará acceder automáticamente, sin necesidad de contraseña. + Comprobar el correo electrónico + Primeros pasos + Introduce tu dirección de correo electrónico para acceder o crear una cuenta de WordPress.com. + O escribe tu contraseña + Crear una cuenta + Enviar el enlace por correo electrónico + Restablecer tu contraseña + Ha habido un problema al gestionar la solicitud. Por favor, inténtalo de nuevo más tarde. + Dale a tu sitio un nombre que refleje su personalidad y temática. ¡Las primeras impresiones cuentan! + Configura el título de tu sitio + Toca <b>%1$s</b> para configurar un nuevo título + Al enviar esta entrada a la papelera también se descartarán los cambios locales, ¿estás seguro de que quieres continuar? + Opciones del bloque %s + Duplicar bloque + Copiar bloque + Bloque copiado + Ajustes del bloque + Bloque pegado + Bloque duplicado + Bloque cortado + Bloque copiado + El título del sitio solo puede ser cambiado por un usuario con el perfil de administrador. + El título del sitio se muestra en la barra de título de un navegador web y en la cabecera de la mayoría de los temas. + Debate + No se ha podido actualizar el título del sitio. Comprueba tu conexión de red e inténtalo nuevamente. + Cambios sin guardar + Abrir el enlace en un navegador + Navega para personalizar el degradado + Navega al selector de color personalizado + Tipo de degradado + Toca dos veces para seleccionar la opción + Personalizar el degradado + %1$s · %2$s · %3$s + Autor de la página + Usar este GIF + La miniatura del medio no se ha podido cargar + Estructura del contenido + Todos + Yo + Descartar + No establecido + Las etiquetas ayudan a los lectores diciéndoles de qué se trata la entrada. + Fecha de publicación + Añadir etiquetas + Volver + Guardar ahora + Enviar ahora + Programar ahora + Publicando en + Etiquetas + Fecha de publicación + Cancelar + Mover a borradores + Las entradas en la papelera no se pueden editar. ¿Deseas cambiar el estado de esta entrada a «borrador» para poder trabajar en ella? + ¿Mover entrada a borradores? + Elige tus intereses + Elige tus intereses + Hecho + Selecciona algunos para continuar + Publicado + En la papelera + Programada + Fecha de publicación + Lee el aviso de privacidad de CCPA + La Ley de Privacidad del Consumidor de California («CCPA») nos obliga a que proporcionemos información adicional a los residentes de California sobre las categorías de información personal que recopilamos y compartimos, dónde obtenemos esa información personal y cómo y por qué la usamos. + Aviso de privacidad para usuarios de California + Estado y visibilidad + Actualizar ahora + %1$s · %2$s + Prueba un diseño de inicio + Abrir el menú de acciones de bloques + Insertar una mención + Toca dos veces para abrir la hoja inferior con las opciones disponibles + Toca dos veces para abrir la hoja de acción con las opciones disponibles + No podemos abrir las páginas en este momento. Por favor, inténtalo de nuevo más tarde + Establecer como página de entradas + Establecer como página de inicio + «No hay disponibles usuarios que coincidan.» + «%s no es un usuario válido» + Seleccionar la página + Página de entradas + Página de inicio estática + Blog clásico + La página de inicio seleccionada y la página de entradas no pueden ser la misma. + Ha fallado la actualización de la página de inicio, comprueba tu conexión a internet + No se pueden guardar los ajustes de la página de inicio antes de que las páginas estén cargadas + No se pueden guardar los ajustes de la página de inicio + Aceptar + Ha fallado la carga de las páginas + Elige entre una página de inicio que muestre tus últimas publicaciones (blog clásico) o una página fija/estática. + Ajustes de la página de inicio + Página de inicio + Ha fallado la actualización de la página de entradas + Página de entradas actualizada correctamente + Ha fallado la actualización de la página de inicio + Página de inicio actualizada correctamente + Para establecer la página de entradas, activa «Página de inicio estática» en los ajustes del sitio + Para establecer la página de inicio, activa «Página de inicio estática» en los ajustes del sitio + Bienvenido a nuestro nuevo blog + Seleccionar un color + Toca dos veces para ir a los ajustes del color + Cuando sigas sitios, verás aquí su contenido + Saber más + Qué hay de nuevo en WordPress + Insertar %d + recortar + Fallo al cargar en el archivo, por favor, inténtalo de nuevo. + Vista previa de la miniatura de la imagen + Usar este medio + Usar este video + Elegir el medio + Elegir el video + No se ha podido seleccionar el sitio. Por favor, inténtalo de nuevo. + Continuar + Ha fallado reblog + Gestionar los sitios + Una vez que crees un sitio en WordPress.com, puedes volver a publicar el contenido que te gusta en tu propio sitio. + No hay disponibles sitios WordPress.com + Qué hay de nuevo + Copiada la dirección del enlace + Copiar la dirección del enlace + Compartir en + No se ha podido compartir + Insertar + Continuar + Copiar + Mover el bloque a la derecha desde la posición %1$s a la posición %2$s + Mover el bloque a la derecha + Mover el bloque a la izquierda desde la posición %1$s a la posición %2$s + Mover bloque a la izquierda + Toca dos veces para mover el bloque hacia la derecha + Toca dos veces para mover el bloque hacia la izquierda + Blog + Creando el escritorio + Configurar el tema + Añadiendo las características del sitio + Obteniendo la URL del sitio + Tu sitio estará listo en breve + ¡Hurra!\nCasi está hecho + Cancelar la subida + Ha habido un problema al gestionar la petición + Funciona con Tenor + Buscar en Tenor + Elegir desde Tenor + Sábado + Viernes + Jueves + Miércoles + Martes + Lunes + Domingo + Ha fallado el acceso al contenido de un sitio privado. Algunos medios pueden no estar disponibles + Accediendo al contenido de un sitio privado + No se pudo recortar y guardar la imagen, inténtalo de nuevo. + Fallo al cargar la imagen.\nPor favor, toca para volver a intentarlo. + Previsualizar la imagen + Formato de página desconocido + No hemos podido completar esta acción y no se ha enviado esta página a revisión. + No hemos podido completar esta acción y no se ha programado esta página. + No hemos podido completar esta acción y no se ha enviado esta página privada. + No hemos podido completar esta acción y no se ha publicado esta página. + No hemos podido enviar esta página a revisión, pero lo intentaremos de nuevo más tarde. + No hemos podido programar esta página, pero lo intentaremos de nuevo más tarde. + No hemos podido publicar esta página privada, pero lo intentaremos de nuevo más tarde. + No hemos podido publicar esta página, pero lo intentaremos de nuevo más tarde. + No hemos podido subir este medio y no se ha enviado esta página a revisión. + No hemos podido subir este medio y no se ha programado esta página. + No hemos podido subir este medio y no se ha publicado esta página privada. + No hemos podido subir este medio y no se ha publicado la página. + Guardaremos tu borrador cuando tu dispositivo vuelva a estar online + Publicaremos tu página privada cuando tu dispositivo vuelva a estar online. + Programaremos tu página cuando tu dispositivo vuelva a estar online. + Enviaremos tu página para revisión cuando tu dispositivo vuelva a estar online. + Publicaremos la página cuando tu dispositivo vuelva a estar online. + Página a la espera + Subiendo página + El dispositivo está desconectado. La página se guardará localmente. + Has hecho cambios no guardados en esta página + Tu página se está subiendo + La página falló al subir los medios y se ha guardado localmente + Página guardada en el dispositivo + La página se ha guardado online + Selecciona un blog para el atajo a la publicación rápida + Establecido por el ahorrador de batería + Oscuro + Claro + Apariencia + Recientemente has hecho cambios en esta página, pero no los has guardado. Elige una versión para cargar:\n\n + Lo que dice la gente + Ofrecemos una gama de servicios para ayudarte a conseguir los resultados que buscas. ¿No estás seguro de lo que necesitas o de lo que cuesta? Podemos explicarte qué servicios son los adecuados para ti y decirte nuestros honorarios. Ponte en contacto con nosotros a continuación. + Somos un pequeño equipo de profesionales con talento con una amplia gama de habilidades y experiencia. Nos encanta lo que hacemos y lo hacemos con pasión. Esperamos poder trabajar contigo. + Mensaje de advertencia + ¿Quieres trabajar con nosotros? + Walt Disney + Los visitantes querrán saber quién está al otro lado de la página. Usa este espacio para escribir sobre ti, tu sitio, tu negocio o sobre cualquier cosa que quieras. Usa las recomendaciones que aparecen a continuación para citar a otros hablando de lo mismo - con sus propias palabras. + EE.UU. + Este es un contenido de ejemplo, incluido con la plantilla, para ilustrar sus características. Elimínalo o sustitúyelo con tus propias palabras y medios. + La forma de comenzar es dejar de hablar y empezar a hacer. + Equipo + Mensaje de éxito + Estrategia + Mostrar el contenido de la entrada + Servicios + Samuel el Perro + Sally Smith + Nombre del proyecto + Puesto o título del trabajo + Porfolio + Mostrar solo el extracto + Mi porfolio presenta varios proyectos creados a lo largo de mi carrera. Mira a continuación mi información de contacto y ponte en contacto. + Enlazar a + Hablemos 👋 No dudes en contactar en la siguiente información de contacto o envía un mensaje usando el formulario. + ¡Vamos a construir algo juntos! + ¡Vamos a construir algo juntos! + Juan Pérez + J.K. Rowling + Son nuestras elecciones, Harry, las que muestran lo que realmente somos, mucho más que nuestras habilidades. + Inspiración + Ponte en contacto + Longitud del extracto (palabras) + Envíame un correo electrónico: <a href=\"mailto:correo@ejemplo.com\">correo@ejemplo.com</a> + Dr. Seuss + No llores porque se ha acabado, sonríe porque ha pasado. + PERSONALIZAR + 00000, Ciudad + Añadir un bloque de párrafo + Una breve descripción de los servicios que ofreces. + Una breve biografía sobre tu historial personal, tus logros clave o un hecho interesante. + <a href=\"mailto:correo@ejemplo.com\">correo@ejemplo.com</a> + Una descripción del proyecto y de los trabajos presentados. + (212)123–4567 + Calle Mayor, 10 + Crear una entrada + En la papelera + Programada + Publicada + La conexión de Facebook no puede encontrar ninguna página. «Divulgar» no se puede conectar con los perfiles de Facebook, solo con las páginas publicadas. + No conectado + Dirección del sitio + Me gusta + Seguimientos + Comentarios + No leído + No enviar a la papelera + Papelera + Actividad + Entradas y páginas + General + Añadir una nueva tarjeta + Añadir una nueva tarjeta de estadísticas + Usa el botón de filtro para encontrar entradas sobre temas específicos + Selecciona una etiqueta o sitio, ventana emergente + Seleccionado + Selecciona un sitio o etiqueta para filtrar entradas + Quitar el filtro actual + Gestionar temas y sitios + Acceder a WordPress.com + Accede a WordPress.com para ver las últimas entradas de los temas que sigues + Accede a WordPress.com para ver las últimas entradas de los sitios que sigues + Vista previa de la plantilla + Reemplazar el bloque actual + Añadir al final + Añadir al principio + Añadir el bloque antes + Añadir el bloque después + Añadir un tema + Seguir un sitio + Puedes seguir entradas sobre un tema específico añadiendo un tema + Mira las entradas más recientes de los sitios que sigues + Siguiendo + Filtrar + Leyenda del vídeo. %s + Editar el vídeo + Toca dos veces para seleccionar el diseño + Añadir un shortcode… + Autor de la entrada + Crear una entrada + Has escuchado todas las estadísticas de este período.\n Si vuelves a tocar, se reiniciará desde el principio. + No hay estadísticas en este período. + Actividad de publicación para %1$s + Los días con visitas %1$s para %2$s son: %2$s %3$s. Toca para más. + explora todas las estadísticas para este período + muy altas + altas + medias + bajas +   y %1$d %2$s + %1$s, %2$d %3$s + Mover la imagen hacia adelante + Mover la imagen hacia atrás + Leyenda de la galería. %s + %1$s. El valor actual es %2$s + Crear una entrada o página + Creador de la web + Ahora no + Cualquier cosa que quieras crear o compartir, te ayudaremos a hacerlo aquí mismo. + Bienvenido a WordPress + Biblioteca de fotos + Imagen no seleccionada + , seleccionada + Imagen seleccionada + Miniatura de la imagen + Entrada del blog + Añadir nueva + Publicar + Hemos hecho grandes mejoras en el editor de bloques y creemos que ¡vale la pena probarlo! Lo hemos activado para nuevas entradas y páginas, pero si quieres cambiar al editor clásico, ve a «Mi sitio > Ajustes del sitio». + Sincronizar ahora + Esta entrada se sincronizará inmediatamente. + ¿Preparado para sincronizar? + El dominio no está disponible + -%s + No hemos podido acceder a tu sitio. Tendrás que contactar con tu alojamiento para solucionarlo. + No hemos podido acceder a tu sitio debido a un problema con el <b>certificado SSL</b>. Tendrás que contactar con tu alojamiento para solucionarlo. + No hemos podido acceder a tu sitio porque necesita <b>identificación HTTP</b>. Tendrás que contactar con tu alojamiento para solucionarlo. + No hemos podido acceder en tu sitio al <b>archivo XMLRCP</b>. Tendrás que contactar con tu alojamiento para solucionarlo. + ¡Ya casi estamos! Solo necesitamos verificar tu dirección de correo electrónico conectada a Jetpack <b>%1$s</b> + Accede con las credenciales del sitio. + Accede con las credenciales del sitio %1$s + Enviar el correo electrónico de verificación + Página del sitio + Siguiendo + Me gusta + Descubrir + Guardado + Temas + Sitios + %sQ + %sC + %sT + %sB + %sM + %sk + No podemos abrir las entradas en este momento. Por favor, inténtalo de nuevo más tarde + No podemos cargar los datos para tu sitio en este momento. Por favor, inténtalo de nuevo más tarde + Biblioteca de medios de WordPress + Desagrupar + Traducir + Título: + Toca para ocultar el teclado + Toca aquí para mostrar la ayuda + Haz un vídeo + Haz una foto o un vídeo + Haz una foto + Empieza a escribir… + Tamaño + Título del plugin en la barra lateral + Mostrar la sección + Bloque %s. Este bloque tiene contenido no válido + Bloque %s. Vacío + Cortar bloque + Restablecer el bloque + Eliminar las notas + Problema al abir el video + Problema al mostrar el bloque + Título de la entrada. %s + Título de la entrada. Vacío + Pegar la URL + Bloque de salto de página. %s + Abrir los ajustes + Ninguna aplicación puede manejar esta petición. Por favor, instala un navegador web. + Navegar arriba + Mi panel de prepublicación + Información del estado de mi entrada + Mi panel de publicación de entradas + Panel de ajustes de mis documentos + Mover el bloque hacia arriba, desde la fila %1$s a la fila %2$s + Mover el bloque arriba + Mover el bloque hacia abajo, desde la fila %1$s a la fila %2$s + Mover el bloque abajo + Leyenda de la imagen. %s + Ocultar el teclado + ¡Aquí está el panel de contenido! + Icono de ayuda + Fallo al insertar los medios.\nPor favor, toca para ver las opciones. + Toca dos veces para deshacer el último cambio + Toca dos veces para alternar los ajustes + Toca dos veces para seleccionar una imagen + Toca dos veces para seleccionar un vídeo + Toca dos veces para seleccionar + Toca dos veces para rehacer el último cambio + Toca dos veces para mover el bloque hacia arriba + Toca dos veces para mover el bloque hacia abajo + Toca dos veces para editar este valor + Toca dos veces para cambiar el valor usando el control deslizante + Toca dos veces para añadir un bloque + El valor actual es %s + Contenido… + Elegir desde el dispositivo + Barra lateral de notas + Ha ocurrido un error desconocido. Por favor, inténtalo de nuevo. + Texto alternativo + AÑADIR UN VÍDEO + Añadir la URL + AÑADIR UNA IMAGEN O UN VÍDEO + AÑADIR UNA IMAGEN + AÑADIR EL BLOQUE AQUÍ + Añadir una nota + Añadir una descripción + Toca el botón «Añadir a las entradas guardadas» para guardar una entrada en tu lista. + La lista se ha cargado con %1$d elementos. + Avisos + Desactivados + Activados + Al desactivar los avisos para este sitio, se desactivarán los avisos que se muestran en la pestaña de avisos de este sitio. Puedes ajustar el tipo de aviso que ves después de activar los avisos para este sitio. + Para ver los avisos en la pestaña de avisos de este sitio, activa los avisos para este sitio. + Activar los avisos mostrados en la pestaña de avisos de este sitio + Desactivar los avisos mostrados en la pestaña de avisos de este sitio + Avisos para este sitio + Avisos para este sitio + Ahora estás usando el editor de bloques para las nuevas páginas, ¡genial! Si quieres cambiar al editor clásico, ve a «Mi sitio > Ajustes del sitio». + Ahora estás usando el editor de bloques para las nuevas entradas, ¡genial! Si quieres cambiar al editor clásico, ve a «Mi sitio > Ajustes del sitio». + Añadir imagen o video + No hemos podido enviar esta entrada a revisión, pero lo intentaremos de nuevo más tarde. + No hemos podido programar esta entrada, pero lo intentaremos de nuevo más tarde. + No hemos podido publicar esta entrada privada, pero lo intentaremos de nuevo más tarde. + No hemos podido publicar esta entrada, pero lo intentaremos de nuevo más tarde. + No hemos podido completar esta acción y no se ha enviado esta entrada a revisión. + No hemos podido completar esta acción y no se ha programado esta entrada. + No hemos podido completar esta acción y no se ha enviado esta entrada privada. + No hemos podido completar esta acción y no se ha publicado esta entrada. + No hemos podido subir este medio y no se ha enviado esta entrada a revisión. + No hemos podido subir este medio y no se ha programado esta entrada. + No hemos podido subir este medio y no se ha publicado esta entrada privada. + No hemos podido subir este medio y no se ha publicado la entrada. + No hemos podido subir este medio. + No hemos podido completar esta acción, pero lo intentaremos de nuevo más tarde. + No hemos podido completar esta acción. + No se puede previsualizar un borrador vacío + No se puede previsualizar una página vacía + No se puede previsualizar una entrada vacía + Vista previa no disponible + Error al intentar guardar la entrada antes de previsualizarla + Generando la vista previa… + Guardando… + Has hecho cambios no guardados en esta entrada + La versión desde esta aplicación + La versión desde otro dispositivo + Desde esta aplicación\nGuardado en %s\n\nDesde otro dispositivo\nGuardado en %s\n + Recientemente has hecho cambios en esta entrada, pero no los has guardado. Elige una versión para cargar:\n\n + ¿Qué versión te gustaría editar? + Borrar permanentemente + No guardaremos los últimos cambios en tu borrador. + No programaremos estos cambios. + No enviaremos estos cambios para revisión. + No publicaremos estos cambios. + Guardaremos tu borrador cuando tu dispositivo vuelva a estar online + Publicaremos tu entrada privada cuando tu dispositivo vuelva a estar online. + Programaremos tu entrada cuando tu dispositivo vuelva a estar online. + Enviaremos tu entrada para revisión cuando tu dispositivo vuelva a estar online. + Publicaremos la entrada cuando tu dispositivo vuelva a estar online. + Esta acción no puede cancelarse. Es posible que el nombre de usuario ya haya sido actualizado. + Tu nuevo nombre de usuario es %1$s + Guardando el nombre de usuario… + Cambiar el nombre de usuario + Estás cambiando tu nombre de usuario a %1$s%2$s%3$s. Cambiar tu nombre de usuario también afectará a tu perfil de Gravatar y a las direcciones de perfil de Intense Debate. Para continuar, confirma tu nuevo nombre de usuario. + ¡Cuidado! + Estás a punto de cambiar tu nombre de usuario, que actualmente es %1$s%2$s%3$s. No podrás volver a recuperar tu nombre de usuario. + Ver y cambiar los ajustes de rendimiento de Jetpack + Rendimiento y velocidad + Más + Reemplaza la búsqueda integrada en WordPress con una experiencia mejorada de búsqueda + Búsqueda mejorada + Búsqueda de Jetpack + Alojamiento de vídeo sin anuncios + Medios + Carga las páginas más rápido al permitir a Jetpack optimizar tus imágenes y archivos estáticos (como CSS y JavaScript). + Archivos estáticos más rápidos + Imágenes más rápidas + Desactivado + Activado + Acelerador de sitios + Mejora la velocidad de tu sitio al cargar solo las imágenes visibles en la pantalla. + Rendimiento + Descargas + Archivo + Descargas de archivos + Las estadísticas de descarga de archivos no se registraron antes del 28 de Junio de 2019. + Zona horaria del sitio (UTC -%s) + Zona horaria del sitio (UTC +%s) + Zona horaria del sitio (UTC) + Escritorio + Por defecto + Cerrar el diálogo + Seleccionar el tipo de vista previa + Compartir + Volver + Avanzar + «%s» programado para publicar el «%s» en tu aplicación de WordPress\n%s + Entrada programada de WordPress: «%s» + «%s» se publicará en 10 minutos + «%s» se publicará en 1 hora + «%s» ha sido publicado + Entrada programada: recordatorio de 10 minutos + Entrada programada: recordatorio de 1 hora + Entrada programada + El aviso no puede crearse cuando la fecha de publicación ha pasado. + Cuando se publique + 10 minutos antes + 1 hora antes + Desactivado + Añadir al calendario + Aviso + Fecha y hora + ¿Necesitas ayuda para encontrar el correo electrónico con el que te conectaste? + La web en esa dirección no es un sitio WordPress. Para conectarnos a él, el sitio debe tener instalado WordPress. + Por favor, introduce una dirección completa de una web, como example.com. + Accede con WordPress.com para conectar con %1$s + Visitas + Entrada + %1$s: %2$s, %3$s: %4$s + Elemento contraído + Elemento expandido + Contraer + Expandir + Gráfico actualizado. + %1$s %2$s del período: %3$s, cambio desde el período anterior - %4$s + Cargando los datos de la tarjeta seleccionada + Editor + Expandir + Cerrar + Verifica tu dirección de correo electrónico - las instrucciones se enviaron a tu correo electrónico + Verifica tu dirección de correo electrónico - las instrucciones se enviaron a %s + Cancelar + Aceptar + http(s):// + Quitar enlace + Insertar enlace + Reintentar la subida + Subiendo medios.\nPor favor, toca para ver las opciones. + Abrir enlace en una nueva ventana/pestaña + Para ver tus estadísticas accede a la cuenta de WordPress.com. + Ninguna entrada coincide con tu búsqueda + Buscar entradas + Aquí es donde la gente te encuentra en Internet. + Elige un nombre de dominio premium + Todos los planes de WordPress.com incluyen un nombre de dominio personalizado. Registra ahora tu dominio premium gratuito. + De un vistazo + Hoy + Histórico + Visitas esta semana + Por favor, accede a la aplicación WordPress para añadir un widget. + No hay ninguna red disponible + No se pudieron cargar los datos + Tipo + Color + Selecciona tu sitio + Oscuro + Claro + Color + Selecciona tu sitio + Sitio + Histórico + Visitas esta semana + Añadir widget + Está tardando más tiempo del normal recargar los detalles del plugin. Por favor, compruébalo de nuevo más tarde. + Si acabas de registrar un nombre de dominio, por favor, espera hasta que terminemos de configurarlo e inténtalo de nuevo.\n\nEn caso contrario, parece que algo fue mal y la característica del plugin podría no estar disponible para este sitio. + Estado (no disponible) + Al registrar este dominio aceptas nuestros %1$stérminos y condiciones%2$s + Comprueba tu conexión a la red e inténtalo de nuevo. + No se ha podido cargar esta página en este momento. + No se pudieron recuperar los ajustes. Algunas APIs no están disponibles para la cuenta e ID de esta aplicación OAuth. + Al configurar Jetpack aceptas nuestros %1$stérminos y condiciones%2$s + No hay ninguna conexión. La edición está desactivada. + Para volver a conectar la aplicación a tu sitio alojado, introduce aquí la nueva contraseña del sitio. + Contraseña actualizada + Actualizar contraseña + Registrando el nombre de dominio… + Selecciona la provincia + Selecciona el país + seguir + tu nuevo dominio <b>%s</b> se está configurando. ¡Tu sitio está\n dando saltos mortales de emoción! + Felicitaciones + Registrar dominio + Código postal + Provincia + Ciudad + Dirección 2 + Dirección + País + Código del país + Teléfono + Organización (opcional) + Para tu comodidad, hemos precompletado tu información de contacto\n de WordPress.com. Por favor, comprueba que es la información correcta que quieres usar para este dominio. + Información de contacto del dominio + Registrar públicamente + Registrar privadamente con protección de privacidad + Lo propietarios de dominios tienen que compartir información en una base de datos pública de todos los dominios.\n Con la protección de privacidad publicamos nuestra propia información en vez de la tuya, y te redirigiremos de forma privada cualquier comunicación dirigida a ti. + Protección de privacidad + Por favor, introduce un %s válido + Elegir dominio + Nuevo + Descartar + Pruébalo ahora + Elige qué estadísticas ver, y céntrate en los datos que más te preocupen. Toca en %1$s al fondo de las estadísticas para personalizarlas. + Gestiona tus estadísticas + Recuperando revisiones… + Fallo al insertar los medios.\nPor favor, toca para volver a intentarlo. + Tu borrador se está subiendo + Subiendo borrador + Borradores + Ocurrió un error mientras se restauraba la entrada + Retroceder a: %s + Solo ves las estadísticas más relevantes. Añade y organiza tus detalles abajo. + Social + Estadísticas anuales del sitio + Seguidores totales + No se pudieron cargar las sugerencias de dominios + Teclea una palabra clave para más ideas + No se han encontrado sugerencias + Registrar dominio + Ahora que está instalado Jetpack, solo necesitamos que lo configures. Esto solo te llevará un minuto. + Quitar de los detalles + Mover abajo + Mover arriba + Ajustes de los parámetros de las estadísticas + La entrada se está moviendo a borradores + La entrada se está restaurando + Entrada restaurada + La entrada se está enviando a la papelera + Al enviar esta entrada a la papelera también se descartarán los cambios sin guardar, ¿estás seguro de querer continuar? + Cambios locales + Mover a borradores + Cambiar a la vista de lista + Cambiar a la vista de tarjetas + No tienes ninguna entrada en la papelera + No tienes ninguna entrada en borrador + No tienes ninguna entrada programada + Aún no has publicado ninguna entrada + Por favor, accede con tu nombre de usuario y contraseña. + Por favor, accede usando tu nombre de usuario de WordPress.com en vez de tu dirección de correo electrónico. + Promedio de palabras/entrada + Total de palabras + Promedio de me gusta/entrada + Total de me gusta + Promedio de comentarios/entrada + Total de comentarios + Entradas + Año + Este año + El sitio de esta dirección no es un sitio WordPress. Para que nos podamos conectar el sitio debe usar WordPress. + Fallo al comprobar los créditos de dominio disponibles + Comprobando créditos de dominio + Registrar dominio + Para instalar plugins necesitas tener un dominio personalizado asociado a tu sitio. + Instalar plugin + Podrás personalizar la apariencia de tu sitio más adelante + Publicar el: %s + Programa para el: %s + Publicado el: %s + Programado para el: %s + Últimas semanas + Vistas medias por dia + Vistas + Período + Meses y años + Cargar más + Hoy + Mejor hora + Mejor día + Estadísticas del: + No, gracias + Más tarde + Valorar ahora + ¡Qué gusto verte de nuevo! Si estás trabajando con la aplicación nos encantaría que nos puntuases en la Google Play Store. + ¿Estás disfrutando de WordPress? + Entrada devuelta a borrador + Actividad de publicación + El sitio no se ha cargado todavía + Más entradas + Menos entradas + Puedes perder lo que llevas hecho. ¿Estás seguro de que quieres salir? + Ver los planes + Se requiere una conexión a Internet para ver los planes, así que los detalles podrían estar desactualizados. + Se requiere una conexión a Internet para ver los planes. + No podemos cargar los planes en este momento. Por favor, inténtalo de nuevo. + No se pueden cargar los planes + No hay conexión + Cambiar al editor de bloques + Cambiar al editor clásico + Hubo un problema al cargar tus datos, recarga la página e inténtalo de nuevo. + Datos no cargados + Edita las nuevas entradas y páginas con el editor de bloques + Usar editor de bloques + Editor de bloques activado + salir + %1$d de %2$d completo + Haz crecer tu audiencia + Personaliza tu sitio + Siguientes pasos + Completo (%d) + ¡Disfruta tu producto terminado! + Sube un icono del sitio + Tus visitantes verán tu icono en su navegador. Añade un icono personalizado para conseguir un aspecto profesional y refinado. + Toca en %1$s Plan %2$s para ver tu plan actual y otros planes disponibles + Toca en %1$s Añadir página %2$s para crear una nueva página. + Toca en %1$s Páginas %2$s para continuar. + Toca en %1$s Estadísticas %2$s para ver cómo está rindiendo tu sitio. + Toca en %1$s Icono de tu sitio %2$s para subir uno nuevo + Guarda en borrador y publica tu primera entrada. + Te llevaremos por los básicos sobre cómo crear y hacer crecer tu sitio. + Hemos realizado algunos cambios en tu lista de comprobación + Hemos añadido más tareas para ayudarte a hacer crecer tu audiencia. + Explorar planes + Aprende sobre las herramientas de marketing y SEO de nuestros planes de pago. + Activar compartir entradas + Comparte automáticamente las nuevas entradas en tus cuentas de medios sociales. + Configura fuentes, añade imágenes, y más. + Crea una nueva página + Añade una página con contenido clave - una página «Acerca de» es un gran comienzo. + Revisa las estadísticas de tu sitio + Mantente al día sobre el rendimiento de tu sitio. + Explorar temas + Explora docenas de opciones de diseño y descubre el que se ajuste a ti. + Quitar los siguiente pasos ocultará todas las visitas guiadas de este sitio. Esta acción es irreversible. + Quitar los siguientes pasos + Quitar esto + Enhorabuena por completar tu lista. Un trabajo bien hecho. + ¡Todas las tareas están completas! + Saltar tarea + contraer + expandir + Recordatorio + Elige el siguiente periodo + Elige el periodo anterior + %1$d%% de visitas + Hora más popular + %1$s (%2$s%%) + +%1$s (%2$s%%) + Mostrando la vista previa + Vaciar + Parece que tienes una conexión lenta. Si no ves tu nuevo sitio en la lista, inténtalo actualizando. + Cancelar el asistente de creación de sitios + Estamos creando tu nuevo sitio + Hubo un problema + Creación de un sitio + Crear sitio + Buscar dominios + Aquí es donde la gente te encontrará en Internet. + Selecciona un nombre de dominio para tu sitio + No hay direcciones disponibles que coincidan con tu búsqueda + Error durante la comunicación con el servidor. Inténtalo de nuevo + Hubo un problema + Hubo un problema + Esto nos ayuda a hacer recomendaciones. Pero no tienes que estar siempre ahí; ¡los sitios evolucionan! + Cuéntanos qué tipo de sitio te gustaría crear + ¡Se ha creado tu sitio! + %1$d de %2$d + Crear sitio + Sugerencias actualizadas + No se ha podido seleccionar el sitio auto-hospedado que acabas de añadir. + Conflicto de versiones + Activa los informes de errores automáticos para ayudarnos a mejorar el rendimiento de la app. + Informes de errores + Deshacer + Versión web descartada + Versión local descartada + Actualizando contenido + Descartar Web + Descartar local + Local\nGuardado el %s\n\nWeb\nGuardado el %s\n + Este contenido tiene dos versiones en conflicto. Selecciona qué versión quieres descartar.\n + Resolver conflicto de sincronización + No hay datos en este periodo + Eliminar la ubicación de los medios + No podemos abrir las estadísticas en este momento. Por favor, inténtalo de nuevo más tarde + Algunos medios fallan al cargar debido a un error de red. + Ningún medio coincide con tu búsqueda + ¡Busca para encontrar GIF para añadir a tu biblioteca de medios! + Vistas + Autor + Autores + Vistas + Buscar término + Buscar términos + Vistas + Título + Videos + Vistas + País + Paises + Clics + Enlace + Clics + Vistas + Referente + Referentes + Entradas y páginas + Ruta + LinkedIn + Google+ + Tumblr + Twitter + Facebook + Ver más + Compartir entrada + Crear entrada + Han pasado %1$s desde que se publicó %2$s. Así es como ha funcionado la entrada hasta ahora: + Han pasado %1$s desde que se publicó %2$s. Pon la bola a rodar y aumenta las vistas de las entradas compartiendo tu entrada: + Aún no has publicado ninguna entrada. Una vez que empieces a publicar, el resumen de tu última entrada aparecerá aquí: + Etiquetas y categorías + Histórico + %1$s - %2$s + Seguidores + Servicio + %1$s | %2$s + Vistas + Título + Vistas + Título + Comentarios + Título + Autor + Entradas y páginas + Autores + Desde + Seguidor + Total %1$s seguidores: %2$s + Correo electrónico + WordPress.com + Gestionar datos + Aún no se han añadido impresiones + Aún no hay datos + Menú de depuración + Cambiando contraseña… + Tu contraseña debe tener al menos seis caracteres de longitud. Para hacerla más fuerte, use letras mayúsculas y minúsculas, números y símbolos como ! \" ? $ % ^ & ). + Contraseña cambiada con éxito + Cambiar contraseña + Nombre + (sin título) + Vista previa HTML + Vista previa visual + Revisión + Anterior + Siguiente + %1$s utilizado + Por favor, introduce un sitio WordPress WordPress.com o alojado conectado a Jetpack + Cargando revisión + Revisión cargada + Cargar + Entrada creada el %1$s a las %2$s + Página creada el %1$s a las %2$s + Aún no hay histórico + Cuando haces cambios a tu entrada podrás ver aquí el histórico + Cuando haces cambios a tu página podrás ver aquí el histórico + Avatar del usuario + Tamaño completo + Grande + Mediano + Miniatura + Historia + La página seleccionada no está disponible + Pendiente de revisión + No tienes ninguna página en la papelera + No tienes ninguna página programada + No tienes ninguna página en probador + Todavía no has publicado ninguna página + Buscar páginas + Ninguna página coincide con tu búsqueda + Borrar permanentemente + Mover a la papelera + Mover a borradores + Hacer superior + Ver + En la papelera + Programada + Borradores + Publicadas + Hemos hecho demasiados intentos para enviar un código de verificación por SMS - tómate un descanso y solicita uno nuevo dentro de un minuto. + No hay ninguna cuenta de WordPress.com que coincida con esta cuenta de Google. + Ningún sitio coincide con tu búsqueda + Ningún sitio coincide con tu búsqueda + La página superior ha cambiado + La página se ha borrado permanentemente + La página se ha programado + La página se ha publicado + La página se ha enviado a la papelera + La página se ha movido a borradores + Nivel superior + ¿Estás seguro de querer borrar la página %s? + Hubo un problema al cambiar la página superior + Hubo un problema al cambiar el estado de la página + Hubo un problema al borrar la página + Hacer superior + toca aquí + Crea tu sitio + Pon tu sitio en marcha. + Ver tu sitio + Toca en %1$s Ver sitio %2$s para previsualizar tu sitio + Comparte tu sitio + Toca en %1$s Compartir %2$s para continuar + Toca en %1$s Conexiones %2$s para añadir tus cuentas de medios sociales + Conecta a tus cuentas de medios sociales - tu sitio compartirá automáticamente las nuevas entradas. + Publica una entrada + Toca en %1$s Crear entrada %2$s para crear una nueva entrada + ¿Quieres un poco de ayuda para empezar? + Acepto + Nunca + No gracias + Sigue otros sitios + Toca en %1$s Buscar %2$s para buscar sitios con intereses similares + Toca en %1$s Lector %2$s para seguir + Descubre sitios que te inspiren, y síguelos para obtener actualizaciones cuando publiquen. + Personaliza tu sitio + Toca en %1$s Temas %2$s para seguir + Toca en %1$s Personalizar %2$s para empezar a personalizar tu sitio + Elige un tema + Toca en %1$s Temas %2$s para descubrir nuevos temas + Revisa todos nuestros temas para encontrar el que se te ajuste a la perfección. + Ir + Cancelar + Ahora no + Más + No tienes sitios + Temas no seguidos + Añade aquí temas para descubrir entradas sobre tus temáticas favoritas + Accede a la cuenta de WordPress.com que usaste para conectar Jetpack. + Reintentar + Configurar + Jetpack no se pudo instalar en este momento. + Hubo un problema + Jetpack instalado + Instalando Jetpack en tu sitio. Esto puede llevar unos minutos completarse. + Instalando Jetpack + Las credenciales de tu web no se almacenarán, y solo se utilizan para instalar Jetpack. + Instala Jetpack + Jetpack + FAQ de Jetpack + Para usar las estadísticas en tu sitio WordPress necesitas instalar el plugin Jetpack. + No hay temas que coincidan con tu búsqueda + ¿Que te gustaría encontrar? + No hay etiquetas que coincidan con tu búsqueda + No tienes ninguna etiqueta + Añade aquí las etiquetas que uses frecuentemente para poder seleccionarlas rápidamente al etiquetar tus entradas + Crea una etiqueta + No hay medios que coincidan con tu búsqueda + ¿Salir de WordPress? + Tienes cambios en entradas que no se han subido a tu sitio. Salir ahora borrará esos cambios de tu dispositivo. ¿Quieres salir de todos modos? + No hay lectores aún + No hay seguidores por correo electrónico aún + No hay seguidores aún + No hay usuarios aún + Las entradas que te gusten aparecerán aquí + Nada que te gustó aún + Ir a los siguientes + Descubre sitios + Sitios a los que no sigues + No hay me gusta aún + No hay seguidores aún + Como estás en un plan gratuito verás eventos limitados en tu actividad. + Cuando hagas cambios en tu sitio podrás ver el historial de tu actividad aquí + No hay actividad aún + Crea una entrada + Crea una página + Sube medios + No tienes ningún medio + galería de imágenes + icono del sitio + imagen del tema + imagen destacada + Descartar + foto de perfil + Dato transitorio + Correo electrónico + Por favor, introduce tu dirección de correo electrónico + Para continuar, por favor, introduce tu dirección de correo electrónico y nombre + Nuevo mensaje de \'Ayuda y soporte\' + WordPress + No establecido + Correo electrónico de contacto + Registro de la aplicación + Mis tickets + ¿Quieres rebobinar tu sitio al %1$s a las %2$s? Esto borrará todo el contenido y opciones creadas o cambiadas desde entonces. + Rebobinar sitio + Rebobinado en curso + Rebobinando a %1$s %2$s + Actualmente restaurando tu sitio + Tu sitio ha sido restaurado satisfactoriamente + Tu sitio ha sido restaurado satisfactoriamente\nRebobinado a %1$s %2$s + Tu sitio está siendo restaurado\nRebobinando a %1$s %2$s + Botón de acción del registro de actividad + Gestionado automáticamente + Guarda esta entrada y vuelve cuando quieras para leerla. Solo estará disponible en este dispositivo — las entradas guardadas no se sincronizan con otros dispositivos. + Guardar entradas para más tarde + No ha sido posible realizar la búsqueda + No se han encontrado resultados + Lee la entrada de origen + Sitios + Enlace mágico enviado + Registro por correo electrónico + Verificación del código + Credenciales de acceso + Enlace mágico enviado + Acceso por enlace mágico + Acceso mediante la dirección del sitio + Acceso mediante dirección de correo electrónico + Alternativamente: + Toca %s para guardar una entrada en tu lista. + ¡Aún no hay entradas guardadas! + Entrada guardada + Ver todas + Guardada + Guardar + Borrada de las entradas guardadas + Añadir a las entradas guardadas + Entradas guardadas + Borrado + Cambiar icono del sitio + Cancelar + Eliminar + Cambiar + No tienes permiso para editar el icono del sitio. + No tienes permiso para añadir un icono al sitio. + ¿Cómo te gustaría editar el icono? + ¿Te gustaría añadir un icono de sitio? + Icono del sitio + este sitio + Activar + ¿Activar avisos para %1$s%2$s%3$s? + Activar los avisos del sitio + Desactivar los avisos del sitio + Icono de Jetpack + Rebobinar + Evento + Icono de actividad + Registro de actividad + Actividad + Lee la política de privacidad + Usamos otras herramientas de seguimiento, incluidas algunas de terceros. Lee acerca de estas y cómo controlarlas. + Política de terceros + Esta información nos ayuda a mejorar nuestros productos, hacer que el marketing sea más relevante, personalizar tu experiencia en WordPress.com y más, tal como se detalla en nuestra política de privacidad. + Política de privacidad + Comparte información con nuestra herramienta de análisis acerca del uso que haces de los servicios mientras estás conectado a tu cuenta de WordPress.com. + Política de cookies + Ajustes de privacidad + Recopilar información + Entrada enviada + ¿No tienes una cuenta? %1$s Regístrate %2$s + Una característica del plugin requiere que el sitio esté en buen estado. + Una característica del plugin necesita que la suscripción principal del dominio esté asociada con este usuario. + Una característica del plugin necesita privilegios de administrador. + El plugin no puede instalarse en sitios VIP. + El plugin no se puede instalar debido a las limitaciones de espacio del disco. + Una característica del plugin requiere una dirección de correo electrónico verificada. + Una característica del plugin requiere que el sitio sea público. + Una característica del plugin requiere un plan business. + Una característica del plugin requiere un dominio personalizado. + Estamos haciendo la configuración final, está casi listo… + Instalando plugin… + Instalar + Instalar el primer plugin en tu sitio puede tomar hasta 1 minuto. Durante este tiempo, no podrás realizar cambios en tu sitio. + Instalar plugin + Avisos + Enviarme nuevos comentarios por correo electrónico + Semanalmente + Instantáneamente + Diariamente + Entradas nuevas + Recibe avisos de las nuevas entradas de este sitio + Enviarme nuevas entradas por correo electrónico + Todos mis sitios seguidos + Sitios seguidos + Dispositivo de lectura personal con avisos + Gente mirando gráficos y tablas + %s en %s + ¿Seguro que quieres eliminar definitivamente esta publicación? + Importante + General + Utilizar esta foto + %1$d de %2$d + Fotografías facilitadas por %s + Busca para encontrar fotografías gratuitas para añadir a tu biblioteca de medios + Búsqueda gratuita en la biblioteca de fotos + Selecciona de la biblioteca gratuita de fotos + No se puede guardar un borrador vacío + %1$s de ilimitado + Vista previa %d + Añadir %d + Crear etiqueta + navegar hacia arriba + Avisos + Abrir enlace externo + mostrar más + foto + borrar + Reproducir video + reproducir video destacado + logo del plugin + banner del plugin + elige desde medios de WordPress + abrir cámara + elige desde el dispositivo + información del perfil + reproduce + previsualizar imagen + vista previa + audio + reproducir video + papelera + reintentar + vista previa de medios, nombre de archivo %s + eliminar %s + Imagen de perfil de %s + marca de verificación + Registrarse con Google… + Se ha producido un error en la conexión a Jetpack: %s + Ya estás conectado a Jetpack + DESHACER + Cambiado a modo visual + Modo visual + Cambiado a modo HTML + Modo HTML + Vista previa + Guardar como borrador + %s TB + %s GB + %s MB + %s kB + %s B + %1$s de %2$s + Si necesitas más espacio, considera actualizar tu plan de WordPress. + Espacio utilizado + Medios + Comentario marcado como no spam + Comentario marcado como spam + Comentario borrado + Comentario restaurado + Comentario enviado a la papelera + El comentario no ha gustado + El comentario ha gustado + Comentario sin aprobar + Comentario aprobado + Detalle de notificación %s + Editar foto + Elige el sitio + Cuenta nueva + Conectado como + Detalle de la persona + Detalles del archivo + Botones de compartir + Avisos + Lector + Yo + Mi sitio + Ajustes de avisos + Ayuda y soporte + Licencia + Notas de la versión + Tu avatar se ha subido y estará disponible en breve. + Parece que has desactivado los permisos necesarios para esta característica.<br/><br/>Si deseas usarla, edita tus permisos y comprueba que <strong>%s</strong> se ha activado. + Permisos + Destacados + No puedes acceder a los ajustes compartidos porque el módulo Compartir de Jetpack está desactivado. + Módulo Compartir desactivado + Versión %s + © %1$d %2$s + Editor: %s + El sonido escogido tiene una ruta incorrecta. Por favor, elige uno distinto. + QP %s + quedan %1$d páginas / entradas + Queda 1 página + quedan %1$d páginas + quedan %1$d entradas + %1$d páginas / entradas y 1 archivo restantes + %1$d entradas y 1 archivo restantes + %1$d páginas y 1 archivo restantes + 1 entrada y 1 archivo restantes + 1 página y 1 archivo restantes + %1$d páginas / entradas y %2$d de %3$d archivos restantes + %1$d entradas y %2$d de %3$d archivos restantes + quedan %1$d páginas y %2$d de %3$d archivos + queda 1 entrada y %1$d de %2$d archivos + queda 1 página y %1$d de %2$d archivos + %1$d entradas / páginas sin subir + %1$d páginas sin subir + 1 página sin subir + %1$d entradas sin subir + 1 entrada sin subir + %1$d entradas / páginas con %2$d archivos sin subir + %1$d páginas %2$d archivos sin subir + 1 página con %1$d archivos sin subir + %1$d entradas con %2$d archivos sin subir + 1 entrada con %1$d archivos sin subir + %1$d entradas / páginas con 1 archivo sin subir + %1$d páginas con 1 archivo sin subir + 1 página con 1 archivo sin subir + %1$d entradas con 1 archivo sin subir + 1 entrada con 1 archivo sin subir + (sin título) + \@%s + %1$f, %2$f + Crear sitio + Toca para continuar. + ¡Sitio creado! + A Google le llevó demasiado tiempo responder. Puede que tengas que esperar hasta que tengas una conexión a internet más rápida. + Cambiar nombre de usuario + Teclea para obtener más sugerencias + Tu nombre de usuario actual es %1$s%2$s%3$s. Con pocas excepciones, otros solo verán tu nombre a mostrar, %4$s%5$s%6$s. + No se ha sugerido ningún nombre de usuario desde %1$s%2$s%3$s. Por favor, introduce más letras o números para obtener sugerencias. + Ocurrió un error al recuperar sugerencias de nombres de usuario. + ¿Descartas cambiar de nombre de usuario? + Descartar + Guardar + Añadir avatar + Registro con Google + Registro con correo electrónico + El correo electrónico ya existe en WordPress.com.\nAcceder. + Actualizando cuenta… + Al registrarte aceptas nuestros %1$sTérminos del servicio%2$s. + Enviando correo + Reintentar + Cerrar + Hubo algún problema al enviar el correo. Puedes reintentarlo ahora o cerrar e intentarlo más tarde. + Nombre de usuario + Siempre puedes acceder con un enlace como el que acabas de usar, pero también puedes configurar una contraseña si lo prefieres. + Contraseña (opcional) + Nombre a mostrar + Reintentar + Revertir + Hubo algún problema al actualizar tu cuenta. Puedes reintentarlo o revertir tus cambios para continuar. + Hubo algún problema al subir tu avatar. + Para crear tu nueva cuenta de WordPress.com, por favor, introduce tu dirección de correo electrónico. + Hubo algún problema al comprobar la dirección de correo. + Registro en WordPress.com + Accede con tu nombre de usuario. + Accede introduciendo la dirección de tu sitio. + Necesita actualizarse + Buscar plugins + Nuevo + Populares + Ninguna coincidencia + Ver todos + Gestionar + No ha sido posible buscar plugins + Error al instalar %s + %s instalado con éxito + Instalar + Me gusta + Añadir nuevo sitio + Crea un nuevo sitio para tu negocio, revista o blog personal; o conecta con una instalación de WordPress existente. + Para obtener avisos útiles en tu dispositivo desde tu sitio WordPress tendrás que instalar el plugin Jetpack. ¿Te gustaría configurar Jetpack? + Carga diferida de imágenes + Instalar Jetpack + Alternar texto + Tu versión de WordPress + Requiere la versión de WordPress + Actualizado por última vez + Versión + 5 estrellas + 4 estrellas + 3 estrellas + 2 estrellas + 1 estrella + Ninguno + %s descargas + %s valoraciones + Leer valoraciones + Preguntas frecuentes + Novedades + Instalación + Descripción + Ajustes + Instalado + Versión %s instalada + Versión %s + por %s + Cambiar foto + No es posible cargar plugins + Páginas del sitio + Gestiona las etiquetas de tu sitio + Guardando + Borrando + ¿Borrar permanentemente la etiqueta \'%s\'? + Ya existe una etiqueta con este nombre + Añadir nueva etiqueta + Descripción + Etiqueta + Tu sitio WordPress.com es compatible con el uso de páginas aceleradas para móviles, una iniciativa de Google que acelera enormemente la carga de las páginas en dispositivos móviles + Páginas móviles aceleradas (AMP) + No es posible cargar las zonas horarias + Aprende más sobre formatos de fecha y hora + Formato personalizado + Personalizador + Entradas por página + Elige una ciudad en tu zona horaria + Zona horaria + Formato de hora + Formato de fecha + La semana empieza el + Etiquetas + Tráfico + Borrar + Enlace externo + Icono del plugin + Web del plugin + Página del plugin en WordPress.org + ¿Estás seguro de querer borrar %1$s de %2$s?\n\nEsto desactivará el plugin y todos los archivos y datos asociados. + Borrar plugin + Borrando %s… + Desactivando %s… + Ocurrió un error al configurar el plugin: %s + Error al borrar %s + %s borrado con éxito + Error al actualizar %1$s: %2$s + Error al actualzar %s. + %s se ha actualizado correctamente + La versión %s está disponible + Actualizaciones automáticas + Activo + Inactivo + Activo + Plugins + Plugins + Abrir enlace en nueva ventana/pestaña + Enlace a + Ocurrió un error. + Por favor, facilita un código de identificación para continuar. + Por favor, vuelve a teclear tu contraseña para continuar. + Acceso detenido + Por favor, espera mientras se accede. + Acceso en progreso… + Toca para continuar. + ¡Conectado! + No se pudo iniciar el acceso desde Google. + Por favor, introduce una contraseña + Envíame un mensaje con otro código en su lugar + Hemos enviado un mensaje de texto al número de teléfono terminado en %s. Por favor, introduce el código de verificación del SMS. + Tamaño + Queda 1 archivo + Quedan %1$d de %2$d archivos + Quedan 1 entrada + Subiendo… + Escribir entrada + %d archivos subidos con éxito + , %d subido con éxito + 1 archivo subido + 1 archivo no subido + %d archivos subidos + %d archivos no subidos + Quitar de la entrada + ¿Quitamos esta imagen de la entrada? + Personalizar + Detalles del archivo + \n¿Quizás probando con otra cuenta? + Hubo algún problema al conectar con la cuenta de Google. + Cerrar + Acceder con Google. + Para seguir con esta cuenta de Google, por favor, facilita la contraseña correspondiente de WordPress.com. Solo se te pedirá una vez. + Ocurrió un error en la red. Por favor, revisa tu conexión e inténtalo de nuevo. + Quitar imagen destacada + Elegir imagen destacada Accede a WordPress.com para compartir el contenido. Introduce la dirección de tu sitio WordPress en el que quieras compartir el contenido. Error al desconectar el sitio Sitio desconectado Desconectar ¿Estás seguro de querer desconectar Jetpack del sitio? - \"Desconecta de WordPress.com\" + «Desconecta de WordPress.com» Puedes poner en lista blanca una dirección IP o series de direcciones, evitando que las bloquee Jetpack. Se aceptan IPv4 y IPv6. Para especificar un rango introduce un valor inferior y un valor superior separados por guiones. Ejemplo: 12.12.12.1–12.12.12.100 Requiere la identificación en dos pasos Relacionar cuentas usando el correo electrónico @@ -43,7 +1589,7 @@ Language: es_CO ¿Eliminar esta imagen? Detalles del documento Detalles del audio - Detalles del vídeo: + Detalles del video Detalles de la imagen Vista previa Fecha de actualización @@ -54,6 +1600,7 @@ Language: es_CO Nombre del archivo URL Texto alternativo + Conectar un sitio Luz parpadeante Vibración del dispositivo Elige sonido @@ -78,7 +1625,7 @@ Language: es_CO Vista %s para saber más No se pudo encontrar la página en el servidor No se puede publicar una página vacía - La subida de \"%s\" falló + La subida de «%s» ha fallado Los medios se han borrados. ¿Los borramos de esta entrada? Error al abrir el navegador web por defecto. Por favor, elige otra aplicación: No fue posible abrir el enlace @@ -88,11 +1635,12 @@ Language: es_CO Hubo un error al subir los medios a esta página: %s. Hubo un error al subir esta página: %s. Tu entrada se está subiendo + Subiendo medios… Página programada Entrada programada Reintentar Entrada a la espera - Subiendo \"%s\" + Subiendo «%s» Se ha perdido la conexión al servidor Mis sitios Mi sitio @@ -107,12 +1655,15 @@ Language: es_CO ¿Cuál es la dirección de mi sitio? ¿Necesitas ayuda para encontrar la dirección de tu sitio? Dirección del sitio + Introduce la dirección del sitio WordPress con el que te gustaría conectar. Ya estás conectado a WordPress.com Seguir - Introduce tu contraseña de WordPress.com + Conectar otro sitio + Introduce tu contraseña de WordPress.com. Solicitando el correo electrónico de acceso Parece que esta contraseña es incorrecta. Por favor, vuelve a comprobar tu información e inténtalo de nuevo. Solicitando un código de verificación por SMS. + Envíame un mensaje con un código en su lugar ¡Casi lo tenemos! Por favor, introduce un código de verificación desde tu aplicación Authenticator. Abrir correo electrónico Siguiente @@ -136,7 +1687,7 @@ Language: es_CO Privada Borrador Pendiente de revisión - Publicada + Publicar Cambiar ubicación Borrar ubicación No se ha podido abrir el selector de ubicación, no disponible Google Play Services @@ -165,7 +1716,7 @@ Language: es_CO Vídeo demasiado grande para subir La imagen es demasiado grande para subirla. Trata de cambiar la optimización de imágenes en los ajustes de la aplicación Audio - Vídeos + Videos Documentos Imágenes Todos @@ -192,8 +1743,8 @@ Language: es_CO Me gusta Permite que tú y tus lectores den me gusta a todos los comentarios Botones - Editar los botones \"Más\" - Un botón \"Más\" contiene un desplegable que muestra botones de compartir + Editar los botones «Más» + Un botón «Más» contiene un desplegable que muestra botones de compartir Elige qué botones se mostrarán debajo de tus entradas Usuario de Twitter Me gusta a comentarios @@ -214,7 +1765,7 @@ Language: es_CO Reconectar Desconectar Conectar - Conéctate para compartir automáticamente las entradas de tu blog en %s + Conéctate para compartir automáticamente las entradas de tu blog en %s. Divulgar en %s Cuentas conectadas Conecta con tus servicios de medios sociales favoritos para compartir automáticamente las nuevas entradas con tus amigos. @@ -260,6 +1811,7 @@ Language: es_CO La entrada falló al subir los medios y se ha guardado localmente ¿Borrar este sitio de la aplicación? Elegir foto + El dispositivo está desconectado. La entrada se guardará localmente. La entrada se ha guardado online Calidad de las imágenes: cuanto más alto es el valor, más calidad tienen las imágenes. Activa la opción para cambiar el tamaño de las imágenes y comprimirlas @@ -269,6 +1821,7 @@ Language: es_CO Media Baja Se ha subido + Fallo al subir Se ha eliminado Eliminando Subiendo @@ -278,8 +1831,8 @@ Language: es_CO Formato de entrada desconocido Enviar Se ha detectado un sitio duplicado. - Este sitio ya existe en la aplicación, no se puede Agregar. - Ya estás registrado en una cuenta de WordPress.com, no se puede agregar un sitio de WordPress.com a otra cuenta. + Este sitio ya existe en la aplicación, no puedes añadirlo. + Ya estás registrado en una cuenta de WordPress.com, no se puede añadir un sitio de WordPress.com a otra cuenta. No se puede cargar archivos multimedia Se necesita estar conectado para actualizar la biblioteca No tienes permiso para ver o editar archivos multimedia @@ -290,14 +1843,16 @@ Language: es_CO Hubo un error al cargar el archivo multimedia Reintentar Confirmar + No se pudo conectar. Se ha recibido un error 403 cuando se intenta acceder a \n tu endpoint del XMLRPC de sitio. La aplicación lo necesita para comunicarse con tu sitio. Ponte en contacto con tu proveedor para solucionar este problema. No se pudo conectar. Su alojamiento está bloqueando las peticiones POST, y la aplicación las necesita \n para comunicarse con su sitio. Póngase en contacto con su alojamiento para solucionar este problema. Busca en los sitios que sigues - No dejes el trabajo a medias; \"%1$s\" está pendiente de publicarse. - No dejes el trabajo a medias; \"%1$s\" está pendiente de publicarse. - \"%1$s\" se ha guardado como borrador. Acuérdate de publicarla. - El borrador \"%1$s\" te está esperando; no te olvides de publicarlo. - ¿Sabías que \"%1$s\" aún es un borrador? No esperes más y publícala. + ¡No dejes el trabajo a medias! «%1$s» está pendiente de publicarse. + ¡No dejes el trabajo a medias! «%1$s» está pendiente de publicarse. + «%1$s» se ha guardado como borrador. ¡Acuérdate de publicarla! + El borrador «%1$s» te está esperando - ¡asegúrate de publicarlo! + ¿Sabías que «%1$s» está aún en borrador? ¡Publícala! Ayer guardaste \'%1$s\' como borrador. ¡No olvides publicarla! + Visitar el sitio Deslizar para ver más No estás autorizado a ver esta publicación. Prueba a iniciar sesión en WordPress.com primero o en su lugar usa el botón de acción arriba para abrir un navegador. No estás autorizado a ver esta publicación. Prueba a iniciar sesión en WordPress.com primero. @@ -310,7 +1865,7 @@ Language: es_CO No se pudo aprobar el comentario. Por favor, inténtalo de nuevo más tarde. No se puede marcar como me gusta el comentario. Por favor inténtalo de nuevo más tarde. Pulsa para mostrarlos - Nuevas notificaciones + Nuevos avisos Respondiendo… Aprobando… Enlazando… @@ -347,17 +1902,17 @@ Language: es_CO Equipo Invita como máximo a 10 personas con sus correos electrónicos o nombre de usuarios de WordPress.com. A aquellos que necesiten un nombre de usuario se le enviará instrucciones sobre cómo hacerlo. Si eliminas a este espectador, no podrá visitar tu sitio\n\n¿Todavía quieres eliminar a este espectador? - Si lo eliminas, este seguidor dejará de recibir informaciones de tu sitio, a no ser que vuelta a seguirte. \n\n¿Todavía quieres eliminar a este seguidor? + Si lo eliminas, este seguidor dejará de recibir informaciones de tu sitio, a no ser que vuelta a seguirte. \n\n¿Todavía quieres eliminar a este seguidor? Desde %1$s No se pudo quitar el espectador No se pudo quitar al seguidor No se pudieron recuperar los correos electrónicos de los seguidores del sitio No se pudieron recuperar los seguidores del sitio Algunas subidas de medios han fallado. Puedes cambiar al modo HTML \ncuando esto pasa. ¿Borramos todas las subidas fallidas y seguimos? - Cambios guardados Ancho Enlazado a Leyenda + Cambios guardados ¿Descartar cambios sin guardar? ¿Parar la subida? ¡Toca para probar de nuevo! @@ -365,7 +1920,7 @@ Language: es_CO %1$s: %2$s ¡Se han enviado las invitaciones pero ha habido errores! ¡Hubo un error al tratar de enviar la invitación! - No se pudo enviar: Hay nombres de usuario o correos electrónicos no válidos. + No se pudo enviar: Hay nombres de usuario o correos electrónicos no válidos No se pudo enviar: Un nombre de usuario o correo electrónico no es válido Por favor, añade al menos un nombre de usuario (Opcional) Puedes introducir un mensaje personalizado de hasta 500 caracteres que se incluirá en la invitación. @@ -380,14 +1935,16 @@ Language: es_CO Enviar enlace Borrar historial de búsqueda ¿Borrar historial de búsqueda? - Los enlaces están inhabilitados en la pantalla de vista previa + No se han encontrado resultados con %s para tu idioma + Buscar en WordPress + Los enlaces están desactivados en la pantalla de vista previa Enviar - \@%1$s eliminado correctamente. + %1$s se ha eliminado correctamente Si eliminas %1$s, ese usuario ya no será capaz de acceder a este sitio, pero cualquier contenido que fuera creado por %1$s permanecerá en el sitio.\n\n¿Aún te gustaría eliminar este usuario? Eliminar %1$s - Gente Rol - Los sitios de esta lista no han publicado nada últimamente. + Gente + Los sitios de esta lista no han publicado nada últimamente No se pudo eliminar el usuario No se ha podido actualizar el rol del usuario No se pudieron recuperar los espectadores del sitio @@ -399,7 +1956,7 @@ Language: es_CO Actualmente no disponible. Por favor, introduce tu contraseña Accediendo Introduce tu contraseña - Cuando comentes se hará público + Cuando comentes se hará público. Captura o elige imagen Planes Plan @@ -409,17 +1966,19 @@ Language: es_CO Exportando contenido… Comprobando compras Mostrar compras - Tienes mejoras premium en tu sitio. Por favor, cancela tus mejoras antes de eliminar tu sitio. + Tienes mejoras premium en tu sitio. Por favor, cancela tus mejoras antes de eliminar tu sitio. Mejoras premium Algo fue mal. No se pudo realizar la compra. Borrando sitio… Eliminar el sitio Exportar tu sitio a un archivo XML Dominio principal + Hubo un error al eliminar tu sitio. Por favor, contacta con el soporte para más asistencia. Error borrando sitio Exportar contenido - Por favor, escribe en %1$s en el campo arriba para confirmar. Tu sitio desaparecerá para siempre. + Por favor, escribe: %1$s en el campo de abajo para confirmar. Tu sitio desaparecerá para siempre. Confirmar borrado del sitio + Contactar con el soporte Si quieres un sitio, pero no quieres ninguna de las entradas y las páginas que tiene ahora, nuestro equipo de soporte puede borrar sus mensajes, páginas, archivos multimedia y tus comentarios.\n\nEsto mantendrá su sitio y la URL activos, pero tendrás un nuevo comienzo en la creación de contenidos. Sólo tienes que contactar con nosotros para limpiar tu contenido actual.. Déjanos ayudarte Comienza tu sitio encima @@ -427,21 +1986,24 @@ Language: es_CO Ajustes de la app Eliminar subidas fallidas Avanzado + No hay comentarios en la papelera + No hay comentarios pendientes + No hay comentarios aprobados No se pudo conectar. Los métodos requeridos en el XML-RPC faltan en el servidor. - Estado - Video Centrado - Chat - Imagen - Enlace + Video + Estado Estándar Cita + Enlace + Imagen Galería + Chat Audio Minientrada Información sobre cursos y eventos de WordPress.com (online y presenciales). Oportunidades para participar en investigaciones y encuestas en WordPress. - Consejos para sacar el máximo partido a WordPress.com + Consejos para sacar el máximo partido a WordPress.com. Comunidad Investigación Sugerencias @@ -449,13 +2011,13 @@ Language: es_CO Menciones del nombre de usuario Logros del sitio Seguidores del sitio - \"Me gusta\" en mis entradas - \"Me gusta\" en mis comentarios + «Me gusta» en mis entradas + «Me gusta» en mis comentarios Comentarios en mi sitio %d elementos 1 elemento - Comentarios de usuarios conocidos Todos los usuarios + Comentarios de usuarios conocidos Sin comentarios %d comentarios por página 1 comentario por página @@ -467,6 +2029,7 @@ Language: es_CO Se requiere aprobación manual de los comentarios de todos. %d días 1 día + Dirección web Sitio principal Haz click en el enlace de verificación del correo electrónica enviado a %1$s para confirmar tu nueva dirección Actualmente estás subiendo archivos multimedia. Por favor, espera hasta que se complete. @@ -477,8 +2040,9 @@ Language: es_CO ¿Eliminar de forma permanente estos comentarios? ¿Eliminar de forma permanente este comentario? Eliminar - Comentario eliminado Restaurar + Comentario eliminado + No hay comentarios spam Todos No se pudo cargar la página Off @@ -508,20 +2072,20 @@ Language: es_CO No pudo guardar la información del sitio No pudo recuperar la información del sitio Cierra automáticamente - Cierra automáticamente los comentarios en artículos - Rompe los hilos de comentarios en múltiples páginas + Cierra automáticamente los comentarios en artículos. + Rompe los hilos de comentarios en múltiples páginas. Comentarios por página Cerrar los comentarios - Cuando un comentario contenga alguna de estas palabras en su contenido, nombre, URL, correo electrónico o IP, será marcado como spam. Puedes introducir palabras parciales, así \"press\" coincidirá con \"WordPress.\" - Cuando un comentario contenga alguna de las estas palabras en su contenido, nombre, URL, correo electrónico o IP, será puesto en la cola para moderar. Puedes introducir palabras parciales, así \"press\" coincidirá como \"Wordpress\" + Cuando un comentario contenga alguna de estas palabras en su contenido, nombre, URL, correo electrónico o IP, será marcado como spam. Puedes introducir palabras parciales, así «press» coincidirá con «WordPress». + Cuando un comentario contenga alguna de las estas palabras en su contenido, nombre, URL, correo electrónico o IP, será puesto en la cola para moderar. Puedes introducir palabras parciales, así «press» coincidirá con «Wordpress». Introduce una palabra o frase Sin elementos - Puedes sobreescribir esta configuración para entradas individuales - en \"Mejora\" + Puedes sobrescribir estos ajustes en las entradas individuales. + en «Actualización» Novedades: VideoPress para bodas - en \"Aplicaciones\" + en «Aplicaciones» La aplicación WordPress para Android consigue un gran lavado de cara - en \"Móvil\" + en «Móvil» Gran actualización disponible ahora para iPhone/iPad Mostrar imágenes Mostrar cabecera @@ -537,7 +2101,7 @@ Language: es_CO Permitir comentarios anidados hasta cierto nivel Determina el orden de los comentarios mostrados No permitir los comentarios después de un tiempo - Permitir notificaciones de enlace de otros sitios + Permitir avisos de enlace de otros sitios Intenta notificar cualquier sitio enlazado desde el artículo Permitir a los lectores publicar comentarios Ver y cambiar la configuración de discusiones de tu sitio @@ -575,7 +2139,7 @@ Language: es_CO Formato por defecto Categoría por defecto Dirección - Lema + Descripción corta Título del sitio Por defecto para nuevas entradas Escribiendo @@ -620,8 +2184,15 @@ Language: es_CO Entrada publicada Lo sentimos, no se han encontrados temas. Cargar más entradas - Ningún sitio coincide con \"%s\" + Ningún sitio coincide con «%s» Buscar sitios + Ir al Lector + Recibe avisos: comenta en entradas que has leído + Unirse a una conversación: comentar en entradas de blogs que sigues + Reinicia la conversación: escribe una entrada nueva + ¡Actívate! Comenta en entradas de blogs que sigues. + No hay comentarios aún + ¡Te has puesto al día! %s Comentarios 1 Comentario Responder a la entrada… @@ -630,29 +2201,33 @@ Language: es_CO Originalmente publicado por %s Originalmente publicado por %1$s en %2$s %s Me gusta - Me gusta 1 Me gusta + Me gusta + %,d seguidores + Editar temas y sitios Entrada del lector - Opciones para las notificaciones que aparecen en tu dispositivo. - Opciones para las notificaciones que se envían al correo electrónico ligado a tu cuenta. - Opciones para las notificaciones que aparecen en la pestaña de notificaciones. - Las notificaciones de la App han sido desativadas. Pulsa aquí para activarlas en Opciones. - Tipos de notificaciones + Ajustes para los avisos que aparecen en tu dispositivo. + Ajustes para los avisos que se envían al correo electrónico ligado a tu cuenta. + Ajustes para las avisos que aparecen en la pestaña de avisos. + Los avisos de la aplicación han sido desactivados. Toca aquí para activarlos en los ajustes. + Tipos de avisos No se han podido cargar los ajustes de avisos Me gusta al comentario + Avisos de la aplicación Correo electrónico Pestaña de avisos - Avisos de la aplicación Siempre mandamos correos electrónicos importantes relativos a tu cuenta, pero también obtendrás extras útiles. Sumario de la última entrada Sin conexión - Publicar - Editar - Ver - Vista previa + Entrada enviada a la papelera Papelera Estadísticas - Entrada enviada a la papelera + Vista previa + Ver + Publicar + Editar + No tienes autorización para acceder a este sitio + No se pudo encontrar este sitio Deshacer La solicitud ha expirado. Accede a WordPress.com para volver a intentarlo. Ignorar @@ -661,99 +2236,114 @@ Language: es_CO Entradas, vistas y visitantes de todos los tiempos Detalles Salir de WordPress.com - Iniciar/Cerrar sesión Acceder a WordPress.com - \"%s\" no se ocultó porque es el sitio actual + Iniciar/Cerrar sesión Ayuda y soporte Configuración de la cuenta - Mostrar/Ocultar sitios + «%s» no se ha ocultado porque es el sitio actual + Crear sitio en WordPress.com Añadir sitio autoalojado - Cambiar sitio + Añadir sitio nuevo + Mostrar/Ocultar sitios Elegir sitio Ver sitio Ver Administrador - Publicar - Aspecto + Cambiar sitio Ajustes Entradas del blog - Toca para mostrarlos + Publicar + Aspecto Configuración + Toca para mostrarlos + Anular todas las selecciones Seleccionar todo Ocultar Mostrar - Anular todas las selecciones - Idioma - Código de verificación Accede de nuevo para continuar. Código de verificación no válido + Código de verificación + Idioma + No se pudieron recuperar las entradas + Autor: + No se pudo abrir la notificación Términos de búsqueda desconocidos - Recuperando medios… - Recuperando entradas… - Recuperando páginas… - Recuperando comentarios… Términos de búsqueda Autores - No se pudo abrir la notificación - Autor: - No se pudieron recuperar las entradas - Sitios que te podrían gustar - Ocurrió un error al copiar el texto en el portapapeles - Nuevas entradas + Recuperando comentarios… + Recuperando páginas… + Recuperando entradas… + Recuperando medios… Los informes de la aplicación se han copiado al portapapeles + Este sitio está vacío + Nuevas entradas + Ocurrió un error al copiar el texto en el portapapeles Subiendo entrada - Visitantes - Vistas - Años - Me gusta - Países - Seguidores - Difundir - Vídeos - Entradas y páginas - hace unos segundos - hace un minuto + %1$d años + Un año + %1$d meses + Un mes %1$d días Un día %1$d horas hace una hora %1$d minutos - Un mes - %1$d años - Un año - %1$d meses + hace un minuto + hace unos segundos + Seguidores + Difundir + Videos + Entradas y páginas + Países + Me gusta + Visitantes + Vistas + Años Obteniendo temas… Detalles %d seleccionados - Cerrando sesión… - Responder a %s - Aún no se han publicado entradas. ¿Por qué no crear una? - El comentario se ha enviado a la papelera. - Comentario - Me gustó - Hace más de 1 semana - Hace más de 2 días - Más - Hace más de 1 mes - No tienes permiso para ver o editar comentarios. - No tienes permiso para ver o editar páginas. - No tienes permiso para ver o editar entradas. - No se puede publicar una entrada vacía. - %1$d de %2$d - Los comentarios están cerrados + Explora nuestras Preguntas frecuentes + Aún no hay comentarios + No hay entradas con esta temática Me gusta Ver artículo original - Aún no hay comentarios - Explora nuestras Preguntas frecuentes. - No es posible realizar esta acción + Los comentarios están cerrados + %1$d de %2$d + No se puede publicar una entrada vacía + No tienes permisos para ver o editar entradas + No tienes permisos para ver o editar páginas + No tienes permisos para ver o editar comentarios + Más + Hace más de 1 mes + Hace más de 1 semana + Hace más de 2 días + Me gustó + Comentario + El comentario se ha enviado a la papelera + Responder a %s + Aún no se han publicado entradas. ¿Por qué no crear una? + Cerrando sesión… + No ha sido posible realizar esta acción + No es posible bloquear este sitio + Las entradas de este sitio no volverán a mostrarse + Bloquear este sitio Programación Actualizar + Sin sitios recomendados + No se puede dejar de seguir este sitio + No se puede seguir este sitio + Ya estás siguiendo este sitio + No se puede mostrar este sitio + Sitio seguido + Introduce una URL o tema para seguir Sitios que sigues - SI normalmente se conecta sin problemas a este sitio sin problemas, este error puede significar que alguien están intentando suplantar el sitio, por lo que no deberías continuar. ¿Quieres, de todas formas, confiar en el certificado? + Temas seguidos + Sitio del lector + Si normalmente se conecta a este sitio sin problemas, este error puede significar que alguien están intentando suplantar el sitio, por lo que no deberías continuar. ¿Quieres, de todas formas, confiar en el certificado? Certificado SSL no válido ¿Has olvidado la contraseña? Ayuda - El usuario o contraseña que ingresaste no son correctos. - Ingresa una dirección de correo electrónico válida + El nombre de usuario o contraseña que has introducido son incorrectos + Introduce una dirección de correo electrónico válida Tu dirección de correo electrónico no es válida Error al descargar la imagen No se pudo cargar el comentario @@ -761,21 +2351,23 @@ Language: es_CO Ocurrió un error al moderar el comentario Ocurrió un error No se pudieron actualizar los comentarios - Las páginas no pueden ser actualizadas en este momento. + Las páginas no se pudieron actualizar en este momento Las entradas no pueden ser actualizadas en este momento Ocurrió un error al eliminar la entrada - Sin notificaciones + Sin avisos Se necesita una tarjeta SD montada para subir medios - El campo nombre de categoría es necesario - Categoría agregada correctamente. + El campo nombre de categoría es obligatorio + La categoría se ha añadido correctamente No se pudo añadir la categoría No es spam - Ocurrió un error al obtener los temas. + Ha ocurrido un error al obtener los temas Ha ocurrido un error mientras se accedía a este blog - El elemento multimedia no ha podido ser recuperado - No hay ninguna conexión de red disponible + El elemento multimedia no se ha podido recuperar + No hay conexiones de red disponible + No se ha podido eliminar este tema + No se ha podido añadir este tema Registro de la aplicación - Hubo un error al crear la base de datos de la app. Por favor, intenta reinstalar la app. + Ha ocurrido un error al crear la base de datos de la aplicación. Por favor, intenta reinstalar la aplicación. Este blog está oculto y no se puede cargar. Actívalo de nuevo en ajustes y prueba de nuevo. No se pueden actualizar los medios en este momento Blog de WordPress @@ -784,17 +2376,17 @@ Language: es_CO Política de privacidad Nuevo elemento multimedia Nueva entrada - No hay notificaciones… todavía. + No hay avisos… aún. Se necesita autorización Comprueba que la URL del sitio introducida es válida - Licencias Open source + Licencias de código abierto No se pudo crear un archivo temporal para subir el archivo multimedia. Asegúrate que haya suficiente espacio libre en tu dispositivo. Nombre de la categoría Añadir nueva categoría Ver en el navegador Eliminar sitio El comentario no ha cambiado - Comentario obligatorio + El comentario es obligatorio ¿Cancelar la edición de este comentario? Guardando cambios Papelera @@ -812,114 +2404,126 @@ Language: es_CO Spam Pendiente Aprobado - Ajustes de entrada + ¿Borrar página? + ¿Borrar entrada? + Ajustes de la entrada + No ha sido posible encontrar el archivo a cargar. ¿Se ha borrado o cambiado de ubicación? Borrador local Ajustes de página Texto del enlace (opcional) - Algunos elementos multimedia no pudieron ser borrados. Prueba más tarde + Algunos elementos multimedia no se pudieron borrar en este momento. Intenta de nuevo más tarde. No tienes permiso para ver la librería multimedia - Malla (red) de miniaturas + Cuadrícula de miniaturas Aprender más Ocurrió un error al cargar la entrada. Actualiza tus entradas e intenta nuevamente. + Ocurrió un error al acceder a este plugin Cancelar edición Error de conexión Seleccionar categorías Compartir enlace Recuperando entradas… + A ti, y a %,d personas más les gusta esto A %,d personas les gusta esto Responder No se puede compartir en WordPress si no tienes un blog visible Comentado marcado como spam - A ti, y a %,d personas más les gusta esto No fue posible recuperar esta entrada + A ti y a otra persona les gusta esto Elige un vídeo Elige una foto - A ti y a otra persona os gusta esto. - Eliminado %s - A una persona le gusta esto - Compartir - Seguir - Siguiendo - Te gusta esto + Registro Imposible abrir %s Imposible ver la imágen Imposible compartir - No se pudo publicar tu comentario + Este no es un tema válido + Ya estás siguiendo este tema + No se pudo publicar tu comentario + Te gusta esto + A una persona le gusta esto + Eliminado %s Añadido %s - Contestar al comentario + Responder al comentario… + Siguiendo + Seguir + Compartir + Reblog Sin título + No hay comentarios aún Esta lista esta vacía - Días - Semanas Meses - Activar - Compartir - Estadísticas - Clics - Hoy + Semanas + Días Ayer - Círculos - Título - Descripción - Cuadrados + Hoy Referentes Etiquetas y categorías + Clics + Estadísticas + Compartir + Activar No se pudo actualizar + Descripción Leyenda - Pase de diapositivas + Título + Presentación de diapositivas + Círculos Mosaico + Cuadrados Temas Descartar Gestionar - Respuesta publicada - %d nuevas notificaciones y %d más. + %d nuevos avisos Seguimientos + Respuesta publicada Acceder Cargando… Contraseña HTTP Usuario HTTP Se ha producido un error al cargar los archivos - Nombre de usuario o contraseña incorrecta - Contraseña - Nombre de usuario + Nombre de usuario o contraseña incorrecta. Acceder + Nombre de usuario + Contraseña Lector - Anónimo Páginas Entradas + Anónimo No hay red disponible + hecho OK URL + Términos del servicio Versión WordPress para Android - Términos del servicio - Alineamiento + Alineación Refrescar - Sin Título + Sin título Editar Inmediatamente - Nombre del Acceso Directo + Establecer nombre del atajo Ajustes - El nombre del acceso directo no puede ser vacío. + El nombre del atajo no puede estar vacío + Privado Título - Categorías Separa las etiquetas con comas + Categorías Eliminando comentarios - Requiere tarjeta SD + Requiere una tarjeta SD Multimedia Aprobar Eliminar Ninguno - Error - Cancelar - No - Guardar - Vista previa + Publicar ahora Responder en + Vista previa + Error de actualización de categorías + Error + No Ajustes de avisos - Error de actualización de categorías Añadir + Guardar + Cancelar diff --git a/WordPress/src/main/res/values-es-rMX/strings.xml b/WordPress/src/main/res/values-es-rMX/strings.xml index db5f4328e364..9e9f2fa3576a 100644 --- a/WordPress/src/main/res/values-es-rMX/strings.xml +++ b/WordPress/src/main/res/values-es-rMX/strings.xml @@ -1,11 +1,50 @@ + A <b>Madison Ruíz</b> le ha gustado tu entrada + Hoy has recibido <b>50 me gusta</b> en tu sitio + <b>Juan Gómez</b> ha respondido en tu entrada + Se ha abierto el menú de bloques desplazable. Selecciona un bloque. + Se ha cerrado el menú de bloques desplazable. + Omitir + Elegir + Elige tu layout de página de inicio favorito. Puedes personalizarlo o cambiarlo más tarde. + Elige un diseño + Los layouts no están disponibles debido a un error + Añadir categoría + Añadir una nueva categoría + Categorías + No establecido + Categorías + Toca \"Reintentar\" cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. + Toca \"Reintentar\" o crea una página en blanco usando el botón a continuación. + Los layouts no están disponibles offline + Museos en Londres + Los mejores fanáticos del mundo + Mis diez mejores cafés + Política + Música + Jardinería + Fútbol + Cocina + Arte + Rock n\' roll semanal + Noticias web + Pamela Nguyen + Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo + Inspírate + Sigue tus sitios favoritos y descubre nuevas lecturas. + Observa cómo crece tu audiencia con analíticas avanzadas. + Con el potente editor puedes publicar sobre la marcha. + Bienvenido al maquetador web más popular del mundo. + Mira los comentarios y avisos en tiempo real. + La carga del medio ha fallado + Sitios a seguir \"%s\" no es totalmente compatible Estamos trabajando duro para añadir más bloques con cada versión. Son publicados como una nueva entrada de blog en tu sitio para que tu audiencia nunca se pierda nada. @@ -19,7 +58,6 @@ Language: es_MX Vista previa del layout Página creada Página en blanco creada - Ha ocurrido un error al obtener los layouts Presentación de las entradas de historias Tienes acceso anticipado a las entradas de historias y nos encantaría que las probaras. Cómo crear una entrada de historias @@ -157,12 +195,11 @@ Language: es_MX Si continúas con Google y aún no tienes una cuenta de WordPress.com, crearás una cuenta y aceptas nuestros %1$stérminos del servicio%2$s. Confirmación del registro Continuar con WordPress.com - Introduce la dirección de tu sitio - Logotipo de WordPress Bloque eliminado Elegir una imagen Elegir una imagen o video Elegir un video + Ingresa la dirección de tu sitio existente Si continúas, aceptas nuestros %1$stérminos del servicio%2$s. Te enviaremos por correo electrónico un enlace de registro para crear tu nueva cuenta de WordPress.com. Usaremos esta dirección de correo electrónico para crear tu nueva cuenta de WordPress.com. @@ -176,8 +213,6 @@ Language: es_MX o Te enviaremos por correo electrónico un enlace que te hará acceder automáticamente, sin necesidad de contraseña. Restablecer tu contraseña - El 37\% de la web está creada con WordPress. - Desbloquea el poder del creador de webs más flexible. Enviar el enlace por correo electrónico Crear una cuenta O escribe tu contraseña @@ -487,7 +522,6 @@ Language: es_MX Esta entrada se sincronizará inmediatamente. ¿Preparado para sincronizar? Este dominio no está disponible - Introducción -%s No hemos podido acceder a tu sitio. Tendrás que contactar con tu alojamiento para solucionarlo. No hemos podido acceder a tu sitio debido a un problema con el <b>certificado SSL</b>. Tendrás que contactar con tu alojamiento para solucionarlo. @@ -1313,7 +1347,6 @@ Language: es_MX Editar foto Elige el sitio Cuenta nueva - Iniciar sesión Conectado como Detalle de la persona Detalles del archivo @@ -2245,7 +2278,6 @@ Language: es_MX Términos de búsqueda desconocidos No se pudieron recuperar las entradas Subiendo entrada - Sitios que te podrían gustar Ocurrió un error al copiar el texto en el portapapeles Este sitio está vacío Nuevas entradas diff --git a/WordPress/src/main/res/values-es-rVE/strings.xml b/WordPress/src/main/res/values-es-rVE/strings.xml index 4b4e3d96e84d..3a64c06ff865 100644 --- a/WordPress/src/main/res/values-es-rVE/strings.xml +++ b/WordPress/src/main/res/values-es-rVE/strings.xml @@ -1,11 +1,50 @@ + <b>Juan Gómez</b> ha respondido en tu entrada + Hoy has recibido <b>50 me gusta</b> en tu sitio + A <b>Madison Ruíz</b> le ha gustado tu entrada + Se ha abierto el menú de bloques desplazable. Selecciona un bloque. + Se ha cerrado el menú de bloques desplazable. + Omitir + Elegir + Elige tu diseño de página de inicio favorito. Puedes personalizarlo o cambiarlo más tarde. + Elige un diseño + Toca «Reintentar» cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. + Los diseños no están disponibles sin conexión + Toca «Reintentar» o crea una página en blanco usando el botón a continuación. + Los diseños no están disponibles debido a un error + Añadir categoría + Añadir una nueva categoría + Categorías + No establecido + Categorías + Museos en Londres + Los mejores fanáticos del mundo + Mis diez mejores cafés + Política + Música + Jardinería + Fútbol + Cocina + Arte + Rock n\' roll semanal + Noticias web + Pamela Nguyen + Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo + Inspírate + Sigue tus sitios favoritos y descubre nuevas lecturas. + Observa cómo crece tu audiencia con analíticas avanzadas. + Mira los comentarios y avisos en tiempo real. + Con el potente editor puedes publicar sobre la marcha. + Bienvenido al maquetador web más popular del mundo. + La carga del medio ha fallado + Sitios a seguir Estamos trabajando duro para añadir más bloques con cada versión. «%s» no es totalmente compatible Botón de ayuda @@ -20,7 +59,6 @@ Language: es_VE Cómo crear una entrada de historias Tienes acceso anticipado a las entradas de historias y nos encantaría que las probaras. Presentación de las entradas de historias - Ha ocurrido un error al obtener los diseños Página en blanco creada Página creada Vista previa del diseño @@ -158,8 +196,7 @@ Language: es_VE Elegir una imagen o vídeo Elegir una imagen Bloque eliminado - Logotipo de WordPress - Introduce la dirección de tu sitio + Introduce la dirección de tu sitio existente Continuar con WordPress.com Confirmación del registro Si continúas con Google y aún no tienes una cuenta de WordPress.com, crearás una cuenta y aceptas nuestros %1$stérminos del servicio%2$s. @@ -181,8 +218,6 @@ Language: es_VE O escribe tu contraseña Crear una cuenta Enviar el enlace por correo electrónico - Desbloquea el poder del creador de webs más flexible. - El 37\% de la web está creada con WordPress. Restablecer tu contraseña Ha habido un problema al gestionar la solicitud. Por favor, inténtalo de nuevo más tarde. Dale a tu sitio un nombre que refleje su personalidad y temática. ¡Las primeras impresiones cuentan! @@ -487,7 +522,6 @@ Language: es_VE Esta entrada se sincronizará inmediatamente. ¿Preparado para sincronizar? El dominio no está disponible - Introducción -%s No hemos podido acceder a tu sitio. Tendrás que contactar con tu alojamiento para solucionarlo. No hemos podido acceder a tu sitio debido a un problema con el <b>certificado SSL</b>. Tendrás que contactar con tu alojamiento para solucionarlo. @@ -1313,7 +1347,6 @@ Language: es_VE Editar foto Elige el sitio Cuenta nueva - Iniciar sesión Conectado como Detalle de la persona Detalles del archivo @@ -2247,7 +2280,6 @@ Language: es_VE Los informes de la aplicación se han copiado al portapapeles Este sitio está vacío Nuevas entradas - Sitios que te podrían gustar Ocurrió un error al copiar el texto en el portapapeles Subiendo entrada %1$d años diff --git a/WordPress/src/main/res/values-es/strings.xml b/WordPress/src/main/res/values-es/strings.xml index 401d72924172..49084ead8791 100644 --- a/WordPress/src/main/res/values-es/strings.xml +++ b/WordPress/src/main/res/values-es/strings.xml @@ -1,11 +1,50 @@ + <b>Juan Gómez</b> ha respondido en tu entrada + Hoy has recibido <b>50 me gusta</b> en tu sitio + A <b>Madison Ruíz</b> le ha gustado tu entrada + Se ha abierto el menú de bloques desplazable. Selecciona un bloque. + Se ha cerrado el menú de bloques desplazable. + Omitir + Elegir + Elige el diseño de página de inicio que prefieras. Puedes personalizarlo o cambiarlo más adelante. + Elige un diseño + Toca «Reintentar» cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. + Los diseños no están disponibles sin conexión + Toca «Reintentar» o crea una página en blanco usando el botón a continuación. + Los diseños no están disponibles debido a un error + Añadir una categoría + Añadir una nueva categoría + Categorías + No establecido + Categorías + Museos en Londres + Los mejores fanáticos del mundo + Mis diez mejores cafés + Política + Música + Jardinería + Fútbol + Cocina + Arte + Rock n\' roll semanal + Noticias web + Pamela Nguyen + Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo + Inspírate + Sigue tus sitios favoritos y descubre nuevas lecturas. + Observa cómo crece tu audiencia con analíticas avanzadas. + Mira los comentarios y avisos en tiempo real. + Con el potente editor puedes publicar sobre la marcha. + Bienvenido al maquetador web más popular del mundo. + La carga del medio ha fallado + Sitios a seguir Estamos trabajando duro para añadir más bloques con cada versión. «%s» no es totalmente compatible Botón de ayuda @@ -20,7 +59,6 @@ Language: es Cómo crear una entrada de historias Tienes acceso anticipado a las entradas de historias y nos encantaría que las probaras. Presentación de las entradas de historias - Ha ocurrido un error al obtener los diseños Página en blanco creada Página creada Vista previa del diseño @@ -158,8 +196,7 @@ Language: es Elegir una imagen o vídeo Elegir una imagen Bloque eliminado - Logotipo de WordPress - Introduce la dirección de tu sitio + Introduce la dirección de tu sitio existente Continuar con WordPress.com Confirmación del registro Si continúas con Google y aún no tienes una cuenta de WordPress.com, crearás una cuenta y aceptas nuestros %1$stérminos del servicio%2$s. @@ -181,8 +218,6 @@ Language: es O escribe tu contraseña Crear una cuenta Enviar el enlace por correo electrónico - Desbloquea el poder del creador de webs más flexible. - El 37\% de la web está creada con WordPress. Restablecer tu contraseña Ha habido un problema al gestionar la solicitud. Por favor, inténtalo de nuevo más tarde. Dale a tu sitio un nombre que refleje su personalidad y temática. ¡Las primeras impresiones cuentan! @@ -487,7 +522,6 @@ Language: es Esta entrada se sincronizará inmediatamente. ¿Preparado para sincronizar? Este dominio no está disponible - Introducción -%s No hemos podido acceder a tu sitio. Tendrás que contactar con tu alojamiento para solucionarlo. No hemos podido acceder a tu sitio debido a un problema con el <b>certificado SSL</b>. Tendrás que contactar con tu alojamiento para solucionarlo. @@ -1313,7 +1347,6 @@ Language: es Editar foto Elige el sitio Cuenta nueva - Iniciar sesión Conectado como Detalle de la persona Detalles del archivo @@ -2247,7 +2280,6 @@ Language: es Los informes de la aplicación se han copiado al portapapeles Este sitio está vacío Nuevas entradas - Sitios que te podrían gustar Ha ocurrido un error al copiar el texto en el portapapeles Subiendo entrada %1$d años diff --git a/WordPress/src/main/res/values-eu/strings.xml b/WordPress/src/main/res/values-eu/strings.xml index 5c096aefb61d..6801db8d14ad 100644 --- a/WordPress/src/main/res/values-eu/strings.xml +++ b/WordPress/src/main/res/values-eu/strings.xml @@ -273,7 +273,6 @@ Language: eu_ES Bilaketa terminoak Bidalketa berriak Errore bat gertatu da testua arbelera kopiatzean - Gogoko izan ditzakezun guneak Bidalketa igotzen Bidalketak eta orrialdeak Bideoak diff --git a/WordPress/src/main/res/values-fr/strings.xml b/WordPress/src/main/res/values-fr/strings.xml index 9a45de3a2f5c..cf0de24165cb 100644 --- a/WordPress/src/main/res/values-fr/strings.xml +++ b/WordPress/src/main/res/values-fr/strings.xml @@ -1,11 +1,50 @@ + <b>Johan Brandt</b> a répondu à votre article + Vous avez reçu <b>50 « J’aime »</b> sur votre site aujourd\'hui + <b>Madison Ruiz</b> a aimé votre article + Menu de bloc déroulant ouvert. Sélectionner un bloc. + Menu de bloc déroulant fermé. + Passer + Choisir + Sélectionnez votre mise en page de la page d’accueil préférée. Vous pourrez la personnaliser ou la modifier ultérieurement. + Choisir un design + Touchez réessayer quand vous serez de nouveau en ligne ou créer un page blanche en utilisant le bouton ci-dessous. + Les mises en page ne sont pas disponibles hors connexion + Touchez réessayer ou créez une page blanche en utilisant le bouton ci-dessous. + Les mises en page ne sont pas disponibles suite à une erreur. + Ajouter une catégorie + Ajouter une nouvelle catégorie + Catégories + Non définie + Catégories + Musées à Londres + Les meilleurs fans du monde + Mon top 10 des cafés + Politique + Musique + Jardinage + Football + Cuisine + Art + Semaine du Rock n’ Roll + Infos du web + Pamela Nguyen + J’apprécie le travail du photographe Cameron Karsten. J’essaierai ses techniques sur mes prochaines photos. + Inspiration + Suivez vos sites préférés et découvrez de nouvelles lectures. + Regardez votre audience grossir avec les statistiques approfondies. + Voir les commentaires et notifications en temps réel. + Avec cet éditeur puissant, vous pouvez publier de n’importe où. + Bienvenue sur le constructeur de site le plus populaire au monde. + Échec du chargement du média + Sites à suivre Nous travaillons dur pour ajouter plus de blocs à chaque version. « %s » n’est pas entièrement pris en charge Bouton d’aide @@ -20,7 +59,6 @@ Language: fr Comment créer une publication de story Vous avez un accès anticipé aux publications de story et nous aimerions que vous l’essayiez. Présentation des publications de story - Une erreur est survenue lors de l’extraction des mises en page Page vide créée Page créée Aperçu de la mise en page @@ -29,7 +67,7 @@ Language: fr L’insertion du média a échoué : %s Choisir dans la bibliothèque des médias WordPress Retour - Commencer + Premiers pas Suivre des thèmes pour découvrir de nouveaux blogs Par Ce référent ne peut pas être marqué comme indésirable @@ -158,8 +196,7 @@ Language: fr Choisir une image ou une vidéo Choisir une image Bloc supprimé - Logo WordPress - Saisissez l’adresse de votre site + Saisissez l’adresse de votre site actuel Continuer avec WordPress.com Confirmation de l’inscription Si vous continuez avec Google sans disposer au préalable d’un compte WordPress.com, vous créez un compte et acceptez nos %1$sconditions d’utilisation%2$s. @@ -176,13 +213,11 @@ Language: fr Consultez vos e-mails sur cet appareil et appuyez sur le lien dans l’e-mail que vous avez reçu de WordPress.com. Nous vous enverrons par e-mail un lien qui vous connectera instantanément, sans mot de passe. Vérifier les e-mails - Commencer + Premiers pas Entrez votre adresse e-mail pour vous connecter ou créez un compte WordPress.com. Vous pouvez également saisir votre mot de passe Créer un compte Envoyer un lien par e-mail - Libérez la puissance de l’outil de création de sites Web le plus flexible. - 37\% du Web est intégré à WordPress. Réinitialiser votre mot de passe Un problème est survenu lors du traitement de la demande. Veuillez réessayer plus tard. Donnez à votre site un nom qui reflète sa personnalité et son thème. La première impression est importante ! @@ -487,7 +522,6 @@ Language: fr Cet article sera synchronisé immédiatement. Prêt à synchroniser ? Ce domaine n’est pas disponible - Introduction -%s Nous n’avons pas pu accéder à votre site. Vous devrez contacter votre hébergeur pour résoudre ce problème. Nous n’avons pas pu accéder à votre site à cause d’un problème avec le <b>certificat SSL</b>. Vous devrez contacter votre hébergeur pour résoudre ce problème. @@ -1313,7 +1347,6 @@ Language: fr Éditer photo Sélectionner le site Nouveau compte - Connexion Connecté en tant que Détail sur la personne Détails sur le fichier @@ -2247,7 +2280,6 @@ Language: fr Les logs de l\'application ont été copiés dans le presse papier Ce site est vide Nouveaux articles - Sites que vous aimerez Une erreur s\'est produite lors de la copie dans le presse papier Article en cours de téléversement %1$d années diff --git a/WordPress/src/main/res/values-gd/strings.xml b/WordPress/src/main/res/values-gd/strings.xml index caeda511ba0b..3af0901d0cf5 100644 --- a/WordPress/src/main/res/values-gd/strings.xml +++ b/WordPress/src/main/res/values-gd/strings.xml @@ -76,7 +76,6 @@ Language: gd_GB Chaidh lethbhreac de logaichean na h-aplacaid a chur air an stòr-bhòrd Puist ùra Thachair mearachd nuair a chaidh lethbhreac dha na faidhlichean a chur air an stòr-bhòrd - Bidh ùidh agad sna leanas is dòcha A’ faighinn nan ùrlaran… %1$d mìos(an) Bliadhna diff --git a/WordPress/src/main/res/values-gl/strings.xml b/WordPress/src/main/res/values-gl/strings.xml index 434a1e813a67..02ab9e5102c2 100644 --- a/WordPress/src/main/res/values-gl/strings.xml +++ b/WordPress/src/main/res/values-gl/strings.xml @@ -277,7 +277,6 @@ Language: gl_ES Esta entrada sincronizarase inmediatamente. Preparado para sincronizar? Este dominio non está dispoñible - Introdución -%s Non puidemos acceder ao teu sitio. Terás que contactar co teu aloxamento para solucionalo. Non puidemos acceder ao teu sitio debido a un problema co <b>certificado SSL</b>. Terás que contactar co teu aloxamento para solucionalo. @@ -1095,7 +1094,6 @@ Language: gl_ES Editar foto Elixe o sitio Nova conta - Iniciar sesión Conectado como Detalle da persoa Detalles do arquivo @@ -2020,7 +2018,6 @@ Language: gl_ES Os rexistros da aplicación foron copiados no portapapeis Este blogue está baleiro Artigos novos - Sitios que poden gustarche Houbo un erro ao copiar o texto no portapapeis Cargando %1$d anos diff --git a/WordPress/src/main/res/values-he/strings.xml b/WordPress/src/main/res/values-he/strings.xml index 2142e26f9033..2ee352d1e433 100644 --- a/WordPress/src/main/res/values-he/strings.xml +++ b/WordPress/src/main/res/values-he/strings.xml @@ -20,7 +20,6 @@ Language: he_IL כיצד ליצור פוסט של סטורי אנו מעניקים לך גישה מוקדמת אל \'פוסטים של סטורי\' ונשמח לראות אותך מתנסה בהם. אנו שמחים להציג את האפשרות \'פוסטים של סטורי\' - אירעה שגיאה בעת הבאת הפריסות נוצר עמוד ריק העמוד נוצר תצוגה מקדימה של הפריסה @@ -155,8 +154,6 @@ Language: he_IL לבחור תמונה או וידאו לבחור תמונה הבלוק הוסר - הלוגו של WordPress - יש להזין את כתובת האתר שלך להמשיך עם WordPress.com אישור הרשמה אם בחרת להמשיך עם Google או ועדיין אין לך חשבון ב-WordPress.com, חשבון ייווצר עבורך ויש הסכמה מצידך ⁦%1$s⁩לתנאי השירות⁦%2$s⁩ שלנו. @@ -178,8 +175,6 @@ Language: he_IL או להקליד את הסיסמה שלך ליצור חשבון לשלוח את הקישור באימייל - לעבוד עם בונה האתרים הכי הגמיש שיש. - 37\% מהאתרים ברשת בנויים על הפלטפורמה של WordPress. יש לאפס את הסיסמה שלך כדאי לתת לאתר שלך שם שמשקף את האישיות שלו והנושאים שמוצגים בו. הרושם הראשוני חשוב! להגדיר את שם האתר שלך @@ -483,7 +478,6 @@ Language: he_IL הפוסט הנוכחי יסונכרן באופן מיידי. הכול מוכן לסנכרון? הדומיין לא זמין - הקדמה -%s לא הייתה לנו אפשרות לגשת לאתר שלך. עליך ליצור קשר עם חברת האחסון שלך כדי לפתור את הבעיה. לא הייתה לנו אפשרות לגשת לאתר שלך עקב בעיה <b>בתעודת SSL</b>. עליך ליצור קשר עם חברת האחסון שלך כדי לפתור את הבעיה. @@ -1306,7 +1300,6 @@ Language: he_IL עריכת תמונה בחר אתר חשבון חדש - כניסה מחובר בתור פרטי המשתמש פרטי הקובץ @@ -2237,7 +2230,6 @@ Language: he_IL הלוגים של האפליקציה הועתקו אל ה-clipboard האתר הזה ריק פוסטים חדשים - אתרים מומלצים עבורך אירעה שגיאה בעת העתקת הטקסט אל ה-clipboard מעלה פוסט %1$d שנים diff --git a/WordPress/src/main/res/values-hr/strings.xml b/WordPress/src/main/res/values-hr/strings.xml index 00d3a8e81eab..2a0d7875ee99 100644 --- a/WordPress/src/main/res/values-hr/strings.xml +++ b/WordPress/src/main/res/values-hr/strings.xml @@ -309,7 +309,6 @@ Language: hr Došlo je do pogreške prilikom kopiranja teksta u međuspremnik Nove objave Zapisi aplikacije su kopirani međuspremnik - Web-stranice koje bi vam se mogle sviđati Objava se prenosi Ova web-stranica je prazna Prije sat vremena diff --git a/WordPress/src/main/res/values-id/strings.xml b/WordPress/src/main/res/values-id/strings.xml index 7d8d05971079..66cd3d531d01 100644 --- a/WordPress/src/main/res/values-id/strings.xml +++ b/WordPress/src/main/res/values-id/strings.xml @@ -1,11 +1,43 @@ + Kami berusaha keras untuk menambahkan banyak blok setiap rilis. + \'%s\' tidak sepenuhnya didukung + Tombol Bantuan + Edit menggunakan editor web + Pilih gambar + Buat Pos Cerita + Pos tersebut diterbitkan sebagai pos blog baru pada situs agar audiens Anda tidak ketinggalan hal-hal baru. + Pos Cerita tidak menghilang + Kombinasikan foto, video, dan teks untuk membuat pos cerita, yang menarik dan memancing pengunjung Anda untuk mengetuk, yang pasti disukai pengunjung Anda. + Sekarang cerita tersedia untuk semua orang + Contoh judul cerita + Cara membuat pos cerita + Anda memiliki akses awal e Pos Cerita dan kami ingin Anda mencobanya. + Memperkenalkan Pos Cerita + Halaman kosong dibuat + Halaman dibuat + Pratinjau Tata Letak + %1$s ditolak untuk mengakses foto Anda. Untuk memperbaiki ini, edit perizinan Anda dan aktifkan %2$s dan %3$s. + Penyisipan media gagal. + Penyisipan media gagal: %s + Pilih dari Pustaka Media WordPress + Kembali + Memulai + Ikuti topik untuk menemukan blog baru + Oleh + Perujuk ini tidak dapat ditandai sebagai spam + Hilangkan tanda sebagai Spam + Tandai sebagai Spam + Buka Situs Web + Mengunggah media gif + Mengunggah media stok + Mengunggah media Pilih item Cari atau ketik URL Tambahkan link telepon ini @@ -124,8 +156,6 @@ Language: id Pilih gambar atau video Pilih gambar Blok dihapus - Logo WordPress - Masukkan alamat situs Anda Lanjutkan dengan WordPress.com Konfirmasi pendaftaran Jika Anda melanjutkan dengan Google dan belum memiliki akun WordPress.com, Anda akan membuat akun dan menyetujui %1$sKetentuan Layanan%2$s kami. @@ -147,8 +177,6 @@ Language: id Atau ketik kata sandi Anda Buat akun Kirim tautan lewat email - Kenali kekuatan dari pembuat situs web yang paling andal. - 37\% dari situs web dikembangkan dari WordPress. Reset kata sandi Ada masalah saat menangani permintaan. Silakan coba lagi nanti. Berikan nama situs yang mencerminkan karakteristik dan topik situs Anda. Kesan pertama sangat berarti! @@ -399,7 +427,9 @@ Language: id Pilih Situs atau Tag untuk menyaring pos Hapus penyaring saat ini Login ke WordPress.com + Kelola Topik & Situs Login ke WordPress.com untuk melihat pos terbaru dari situs yang Anda ikuti + Masuk ke WordPress.com untuk melihat pos terbaru dari topik yang Anda ikuti Pratinjau Template Ganti Blok Saat Ini Tambahkan ke Akhir @@ -408,6 +438,8 @@ Language: id Tambahkan Blok Setelah Ikuti situs Lihat pos terbaru dari situs yang Anda ikuti + Tambahkan topik + Anda dapat mengikuti pos dengan subjek tertentu dengan menambahkan topik Mengikuti Penyaring Keterangan video. %s @@ -449,7 +481,6 @@ Language: id Pos ini akan segera disinkronisasi. Siap Mensinkronkan? Domain ini tidak tersedia - Perkenalan -%s Kami tidak dapat mengakses situs Anda. Anda harus menghubungi host untuk mengatasi masalah ini. Kami tidak dapat mengakses situs Anda karena ada masalah dengan <b>Sertifikat SSL</b>. Anda harus menghubungi host untuk mengatasi masalah ini. @@ -473,6 +504,7 @@ Language: id Temukan Suka Kami saat ini tidak dapat memuat data untuk situs Anda. Harap coba lagi nanti + Topik Pustaka Media WordPress Batalkan pengelompokan Terjemahan @@ -1052,6 +1084,8 @@ Language: id Anda belum memiliki situs Lainnya Ketuk %1$s Tema %2$s untuk menemukan tema baru + Tidak ada topi yang diikuti + Tambahkan topik di sini untuk menemukan pos tentang topik favorit Anda. Login ke akun WordPress.com yang Anda gunakan untuk menghubungkan Jetpack. Coba lagi Ada masalah @@ -1272,7 +1306,6 @@ Language: id Edit Foto Pilih situs Akun baru - Login Sudah login sebagai Detail orang Detail file @@ -2133,6 +2166,7 @@ Language: id 1 Suka %,d pengikut Suka + Edit topik dan situs Pos Pembaca Pengaturan pemberitahuan yang muncul di perangkat Anda. Pengaturan pemberitahuan yang dikirim melalui email yang terhubung dengan akun Anda. @@ -2204,7 +2238,6 @@ Language: id Log aplikasi sudah disalin ke clipboard Terjadi kesalahan saat menyalin teks ke clipboard Pos baru - Situs yang mungkin Anda sukai Mengunggah pos Situs ini kosong %1$d bulan @@ -2250,6 +2283,7 @@ Language: id Belum ada pos. Mengapa tidak membuatnya? Balas ke %s Log out… + Tidak ada pos dengan topik ini Tidak bisa menjalankan proses ini Tidak bisa memblokir situs ini Blokir situs ini @@ -2264,6 +2298,8 @@ Language: id Tidak dapat berhenti mengikuti situs ini Tidak ada situs yang disarankan Situs Pembaca + Topik yang diikuti + Masukkan URL atau topik yang ingin Anda follow Bantuan Sertifikat SSL tidak sah Jika biasanya Anda bisa terhubung dengan situs ini tanpa masalah, galat ini bisa berarti seseorang sedang mencoba meniru situs, dan Anda sebaiknya tidak melanjutkannya. Apakah Anda ingin mempercayai sertifikat ini? @@ -2344,6 +2380,8 @@ Language: id Tidak dapat menemukan file yang akan diunggah. Apakah file dihapus atau dipindahkan? Hapus pos? Hapus halaman? + Tidak dapat menambahkan topik ini + Tidak dapat menghapus topik ini Bagikan tautan Mengambil pos… Anda dan %,d lainnya menyukai ini @@ -2372,6 +2410,8 @@ Language: id Balas komentar… Belum ada komentar Reblog + Anda telah mengikuti topik ini + Topik tersebut tidak valid Tema Judul Subjudul diff --git a/WordPress/src/main/res/values-is/strings.xml b/WordPress/src/main/res/values-is/strings.xml index ba4c4102c2be..a3b972d37189 100644 --- a/WordPress/src/main/res/values-is/strings.xml +++ b/WordPress/src/main/res/values-is/strings.xml @@ -455,7 +455,6 @@ Language: is Gat ekki sótt færslur Atburðarskrá forritsins hefur verið afrituð í klemmuspjaldið Villa kom upp við að afrita texta í klemmuspjaldið - Vefir sem þér gæti líkað við Nýjar færslur %1$d mánuðir Á ári diff --git a/WordPress/src/main/res/values-it/strings.xml b/WordPress/src/main/res/values-it/strings.xml index ece921739d98..4a9dc70194f7 100644 --- a/WordPress/src/main/res/values-it/strings.xml +++ b/WordPress/src/main/res/values-it/strings.xml @@ -1,11 +1,42 @@ + Stiamo lavorando duramente per aggiungere più blocchi con ogni versione. + \"%s\" non è pienamente supportato + Pulsante di assistenza + Modifica utilizzando l\'editor web + Crea articolo della storia + Vengono pubblicati come un nuovo articolo del blog sul sito, quindi il pubblico non perderà nulla. + Gli articoli della storia non scompaiono + Combina foto, video e testo per creare articoli della storia coinvolgenti e che si possono toccare che i visitatori ameranno. + Le storie ora sono per tutti + Esempio titolo storia + Come creare un articolo della storia + Hai accesso anticipato agli articoli della storia e ci piacerebbe che tu facessi una prova. + Introduzione agli articoli della storia + Pagina bianca creata + Pagina creata + Anteprima layout + %1$s è stato negato l\'accesso alle tue foto. Per risolvere questo problema, modifica le tue autorizzazioni e attiva %2$s e %3$s. + Inserimento degli elementi multimediali non riuscito. + Inserimento degli elementi multimediali non riuscito: %s + Scegli dalla Libreria multimediale di WordPress + Indietro + Inizia ora + Segui gli argomenti per scoprire nuovi blog + Da + Questo referrer non può essere contrassegnato come spam + Annulla contrassegno come spam + Contrassegna come spam + Apri sito web + Caricamento GIF multimediale + Caricamento magazzino multimediale + Caricamento elemento multimediale Seleziona elemento Cerca o digita URL Aggiungi questo link del telefono @@ -125,8 +156,6 @@ Language: it Scegli immagine o video Scegli immagine Blocco rimosso - Logo WordPress - Inserisci l\'indirizzo del sito Continua con WordPress.com Conferma dell\'iscrizione Se continui con Google e non hai già un account WordPress.com, crei un account e accetti i nostri %1$sTermini di servizio%2$s. @@ -148,8 +177,6 @@ Language: it In alternativa, digita la tua password Crea account Invia link tramite e-mail - Libera tutto il potenziale del costruttore di siti più flessibile al mondo. - Il 37\% del web viene costruito con WordPress. Reimposta la password Si è verificato un errore durante la gestione della richiesta. Riprova più tardi. Dai al tuo sito un nome che rifletta la tua personalità e l\'argomento. La prima impressione conta. @@ -454,7 +481,6 @@ Language: it Questo articolo verrà sincronizzato immediatamente. Pronto per la sincronizzazione? Questo dominio non è disponibile - Introduzione -%s Non siamo stati in grado di accedere al tuo sito. Dovrai contattare il tuo fornitore di hosting per risolvere questo problema. Non siamo stati in grado di accedere al tuo sito perché abbiamo riscontrato un problema con il <b>certificato SSL</b>. Dovrai contattare il tuo fornitore di hosting per risolvere questo problema. @@ -1280,7 +1306,6 @@ Language: it Modifica foto Scegli sito Nuovo account - Accesso Autenticato come Dettaglio persona Dettagli file @@ -2214,7 +2239,6 @@ Language: it I log dell\'applicazione sono stati copiati negli appunti Questo sito è vuoto Nuovi articoli - Siti che potrebbero piacerti Si è verificato un errore durante la copia del testo negli appunti Caricamento dell\'articolo %1$d anni diff --git a/WordPress/src/main/res/values-ja/strings.xml b/WordPress/src/main/res/values-ja/strings.xml index 446d9521926b..162ff37f51a0 100644 --- a/WordPress/src/main/res/values-ja/strings.xml +++ b/WordPress/src/main/res/values-ja/strings.xml @@ -1,11 +1,43 @@ + リリースごとにブロックを追加できるよう努めています。 + 「%s」は完全にはサポートされていません + ヘルプボタン + Web エディターを使って編集 + 画像を選択 + ストーリー投稿を作成 + 新しいブログ投稿としてサイトに公開されるため、読者が見逃すことはありません。 + ストーリー投稿は消えません + 写真、動画、文章を組み合わせて、見た人が心をつかまれタップしたくなるような、好感を持たれるストーリー投稿を作ります。 + いまやストーリーはみなさんのためのものです + ストーリーのタイトルの例 + ストーリー投稿の作り方 + ストーリー投稿への早期アクセスへの権利を得ました。ぜひお試しください。 + ストーリー投稿の紹介 + 空白ページが作成されました + ページが作成されました + レイアウトのプレビュー + %1$s は写真へのアクセスを拒否されました。 これを解決するには、権限を編集して%2$sと%3$sをオンにしてください。 + メディアの挿入に失敗しました。 + メディアの挿入に失敗しました : %s + WordPress メディアライブラリから選択 + 戻る + 今すぐ始める + トピックをフォローして新しいブログを見つける + By + このリファラーはスパムとしてマークできません + スパムとしてのマークを解除 + スパムとしてマーク + サイトを開く + GIF メディアをアップロード中 + ストックメディアをアップロード中 + メディアをアップロード中 項目を選択 検索または URL を入力 この電話リンクを追加 @@ -125,8 +157,6 @@ Language: ja_JP 画像または動画を選択 画像を選択 削除したブロック - WordPress のロゴ - サイトアドレスを入力 WordPress.com で続ける 登録の確認 Google を引き続き使用し、WordPress.com アカウントをまだお持ちでない場合は、アカウントを作成して WordPress.com の%1$s利用規約%2$sに同意します。 @@ -148,8 +178,6 @@ Language: ja_JP または、パスワードを入力してください。 アカウントを作成 リンクをメールで送信 - 柔軟性の高いサイトビルダー機能を活用しましょう。 - Web の37\% が WordPress で構築されています。 パスワードをリセット リクエストの処理中に問題が発生しました。 後ほど、もう一度お試しください。 サイトにその特徴とトピックがわかるような名前を付けてください。 第一印象が大事です。 @@ -399,7 +427,9 @@ Language: ja_JP 選択済 投稿を絞り込むためのサイトまたはタグを選択 現在のフィルターを削除 + トピックとサイトを管理 WordPress.com にログイン + WordPress.com にログインし、フォロー中のトピックの最新投稿をチェック WordPress.com にログインし、フォロー中のサイトの最新投稿をチェック テンプレートのプレビュー 現在のブロックを置き換え @@ -407,7 +437,9 @@ Language: ja_JP 先頭に追加 前にブロックを追加 後にブロックを追加 + トピックを追加 サイトをフォロー + トピックを追加すると、特定の内容に関する投稿をフォローできます フォローしているサイトの最新の投稿を表示 フォロー中 フィルター @@ -450,7 +482,6 @@ Language: ja_JP この投稿はすぐに同期されます。 同期しても良いですか ? このドメインは利用できません - はじめに -%s サイトにアクセスできませんでした。ホストに問い合わせてこの問題を解決してください。 <b>SSL 証明書</b>の問題により、サイトにアクセスできませんでした。ホストに問い合わせてこの問題を解決してください。 @@ -465,6 +496,7 @@ Language: ja_JP いいね ディスカバー 保存しました + トピック サイト %sQi %sQa @@ -1053,6 +1085,8 @@ Language: ja_JP 後で もっと見る サイトがありません + フォローしているトピックがありません + ここでトピックを追加して、お気に入りのトピックに関する投稿を検索します Jetpack を接続するのに使用した WordPress.com アカウントにログインします。 再試行 設定 @@ -1273,7 +1307,6 @@ Language: ja_JP 写真の編集 サイトの選択 新しいアカウント - ログイン 現在のユーザーアカウント: ユーザーの詳細 ファイルの詳細 @@ -2135,6 +2168,7 @@ Language: ja_JP 1個の「いいね」 いいね %,d人のフォロワー + トピックとサイトを編集 Reader 投稿 端末に表示される通知の設定。 アカウントに結び付けられているメールに送信される通知の設定。 @@ -2206,7 +2240,6 @@ Language: ja_JP アプリケーションログをクリップボードにコピーしました このサイトは空です 新規投稿 - おすすめのサイト テキストをクリップボードにコピーする際にエラーが発生しました 投稿をアップロード中 %1$d年 @@ -2234,6 +2267,7 @@ Language: ja_JP %d件選択 よく聞かれる質問を表示 コメントはまだありません + このトピックには投稿がありません いいね 元の記事を表示 コメントは受け付けていません @@ -2264,7 +2298,9 @@ Language: ja_JP このサイトをすでにフォローしています このサイトを表示できません サイトをフォローしました + フォローする URL またはトピックを入力 フォロー中のサイト + フォローしているトピック Reader サイト 通常問題なくサイトに接続できる場合、このエラーは誰かがあなたのサイトになりすましていることを意味しているかもしれないので、そのまま進むべきではありません。それでもこの証明書を信頼してもよいですか ? 無効な SSL 証明書 @@ -2292,6 +2328,8 @@ Language: ja_JP このブログにアクセスする際にエラーが発生しました メディア項目を読み込めませんでした。 利用可能なネットワークがありません + このトピックを削除できません + このトピックを追加できません アプリケーションログ アプリのデータベースを作成する際にエラーが発生しました。アプリをもう一度インストールしてみてください。 このブログは非表示になっており読み込めません。設定画面から再有効化してもう一度お試しください。 @@ -2361,6 +2399,8 @@ Language: ja_JP %sを開けませんでした 画像を表示できません 共有に失敗しました + 有効なトピックではありません + このトピックをすでにフォローしています コメントを投稿できませんでした 「いいね」をつけました 1人がいいねをつけました diff --git a/WordPress/src/main/res/values-kmr/strings.xml b/WordPress/src/main/res/values-kmr/strings.xml index 547bc1c1aadc..f631014b1557 100644 --- a/WordPress/src/main/res/values-kmr/strings.xml +++ b/WordPress/src/main/res/values-kmr/strings.xml @@ -1,31 +1,63 @@ - Qalind + Ji bo em karibin bi her guhertoyê re blokên zêdetir tevlî bikin, em pir dixebitin. + \'%s\' bi temamî nayê piştgirîkirin + Bişkoka alîkariyê + Wêneyan hilbijêre + Şandiya Çîrokî Biafirîne + Bi bikaranîna webê sererast bike + Şandiyên çîrokî wenda nabin + Rûpel hat afirandin + Pêşdîtina rêkxistinê + Niha çîrok ji bo her kesî ne + Sernavê çîrokê - mînak + Şandiya çîrokî çawa tê afirandin + Rûpela vala hat afirandin + %1$s red kir ku bigihe wêneyên te. Bo çarekirina vê, destûrên xwe biguherîne û %2$s û %3$s veke. + Tevlîkirina medyayê bi ser neket. + Tevlîkirina medyayê bi ser neket: %s + Ji Medyageha WordPressê Hilbijêre + Paşve + Dest Pê Bike + Ji bo blogên nû keşf bikî, mijaran bişopîne + Ji hêla + Wekî Spam nîşan bike + Malperê Veke + Hilxistina medyaya gif\'ê + Hilxistina \"stock medya\"yê + Medya tê hilxistin + Hêmanê hilbijêre + Li URL\'yê bigere an jî binivîse + Girêdana vê telefonê tevlî bike + \"Înternet tune ye.\nPêşniyar nayên bikaranîn.\" + Vê girêdanê tevlî bike + Girêdana vê emailê tevlî bike Modern - Lîstikî Bihêz Klasîk - Serabera - Ji bo tomarkirina vîdyoyê divê tu destûra tomarkirina dengî bidî sepanê %s + Lîstikî %s hat hilbijartin - Bi E_name\'yê girêdanekê werbigire. - Hmm, em nikarin ti hesabekî WordPress.com\'ê ku bi vê navnîşana emailê ve girêdayî ye bibînin. + Serabera + Ji bo tomarkirina vîdyoyê divê tu destûra tomarkirina dengî bidî sepanê + Qalind Mîkrofon + Hmm, em nikarin ti hesabekî WordPress.com\'ê ku bi vê navnîşana emailê ve girêdayî ye bibînin. Ev şîrove nayê nîşandan - Hêmanên bo gerokê + Bi emailê girêdaneke têketinê bistîne + Hêmanan bibîne Vê şandiyê rapor bike Qozî - Tu bi xêr hatî Xwînerê. Li bin tiliyên xwe bi hezaran blogan keşf bike. Çewtiya servera navxweyî rû da - Ji bo çalakiya te destûr nayê dayîn %1$s hêmanên zêdetir + Tu bi xêr hatî Xwînerê. Bi milyonan blog li benda keşfkirina te ne. + Destûr ji bo çalakiya te nayê dayîn Planekê hilbijêre Nîşe: Di navbera pergala sitûnî, rûkar û ebatên ekranan de dibê ku cudabûn xwuya bikin Şandî an jî çîrokekê biafirîne @@ -34,26 +66,26 @@ Language: ku_TR \@string/contact_support Dibe ku tu biecibînî Veşêre + Pêvajo didome, dîsa biceribîne + Pêşeka Çîrokî nehat dîtin + Bîrgehê Bibîne + Çewtiya tomarkirina wêneyê Sernivîsa vîdyoyê. Vala Sernavê hildidemîne. - Blokê li peyê bizeliqîne + Blokê li dawiya wê bizeliqîne Sernavê rûpelê. %s Sernavê rûpelê. Vala Di jenandina vîdeoya te de çewtiyek rû da - Ev navgîna piştevaniya Camera2 API\'ê nake. - Vîdyo qeyd nebû - Çewtiya tomarkirina wêneyê - Pêvajo didome, dîsa biceribîne - Pêşeka Çîrokî nehat dîtin - Bîrgehê Bibîne + Ev cîhaz piştgiriya Camera2 API\'ê nake. + Vîdyo nehat tomarkirin + Pêşekek nehat tomarkirin + Bi rê ve bibe + Ji bo pêşekekê çalakî hewce ye Berî weşandinê divê em çîrokê li cîhaza te tomar bikin. Sazkariyên bîrgehê kontrol bike û ji bo ciyê vala çê bibe, hin dosyeyan rake. Bîrgeha cîhazê nêzî tijebûnê ye Jêbirin an jî tomarkirina pêşekan dîsa biceribîne, piştre dîsa weşandina çîroka xwe biceribîne. %1$d pêşek nehatin tomarkirin - Pêşekek nehat tomarkirin - Bi rê ve bibe - Ji bo %1$d pêşekan çalakî hewce ye - Ji bo pêşekekê çalakî hewce ye + Ji bo %1$d pêşekan çalakî hewce ne \"%1$s\" nehat hilxistin \"%1$s\" nehat hilxistin \"%1$s\" hat weşandin @@ -65,24 +97,24 @@ Language: ku_TR Bêsernav Biavêje Şandiya te ya çîrokî ew ê wekî reşnivîs neyê tomarkirin. - Şandiya çîrokî biavêje? Jê bibe Ev pêşek hîn nehatiye tomarkirin. Heke tu vê pêşekê jê bibî tu yê hemû guhertinan ji dest bidî. Ev pêşek ew ê ji çîroka te were rakirin. Pêşeka çîrokî jê bibe? Pêşekê jê bibe Rengê nivîsê biguherîne - Paldayîna nivîsê biguherîne çewtî derket hat hilbijartin nehat hilbijartin + Bila şandiya çîrokî were avêtin? + Spartina nivîsê biguherîne + Stickers Pêşek Dîsa biceribîne Hat tomarkirin Bigire Pêşve Qediya - Parve bike bi: PARVE BIKE Li wêneyan tomar bû Dîsa biceribîne @@ -92,65 +124,61 @@ Language: ku_TR Berovajî bike Deng Nivîs - Zêdetir - Stickers Flaş Kamerayê bizîvirîne - Bigire Pêşdîtin Rûpelekê biafirîne Rûpeleke vala biafirîne - Ji nav rûpelsaziyên berê hilbijêrin û destpê bikin. An ji pelekî vala dest pê bikin. - Rêkxistinekê Hilbijêre Sernavekî bide çîroka xwe Şandî an jî çîrokekê biafirîne Şandî, rûpel an jî çîrokekê biafirîne - Li %1$s Biafirîne bitikîne. %2$s Piştre<b>Şandiya Blogê</b> hilbijêre + Bigire + Ji nav rûpelsaziyên berê hilbijêrin û destpê bikin. An ji pelekî vala dest pê bikin. + Parve bike bi: + Zêdetir + Rêkxistinekê Hilbijêre + Li %1$s Biafirîne\'yê bitikîne. %2$s Piştre<b>Şandiya Blogê</b> hilbijêre Ji cîhazê hilbijêre Şandiya çîrokî + Bi ser kotaya Bîrgehê ket guhertina gotûbêja îkona malpera min pêdivî bi peyamek jetpack heye. Pengizîna rûpela girêdayî nayê dîtin Dosye nayê barkirin.\nBi ser kotaya Bîrgehê ket. - Bi ser kotaya Bîrgehê ket - Dosye tevlê bike + Dosye tevlî bike Vîdeoyê Dîscihke Vîdyo an wêneyê dîscihke Vîdyo Hilbijêre Vîdyo an jî wêne hilbijêre Wêne Hilbijêre Blok hat rakirin - Logoya WordPress\'ê - Navnîşana malpera xwe binivîse - Bi WordPress.com\'ê re dewam bike Pejirandina tomarbûnê + Bi WordPress.com\'ê re dewam bike Heke tu bi Googleê re dewam bikî û hesabê te yê WordPress\'ê tune be, dema tu ajimêrekê biafirînî tu pê re %1$sMercên Xizmetê%2$s yên me qebûl dikî. Bi domandinê re tu %1$sMercên Xizmetê%2$s yên me qebûl dikî. Em ê girêdaneke tomarbûnê bi emailê ji te re bişînin da ku tu hesabê xwe yê nû yê WordPress.com\'ê biafirînî. Em ê vê navnîşana emailê ji bo afirandina hesabê te yê nû yê WordPress.com\'ê bi kar bînin. Me girêdaneke tomarbûnê ji te re şand ji bo ku tu hesabê xwe yê WordPress.com\'ê biafirînî. Li ser vê cîhazê emaila xwe kontrol bike û li girêdana ji WordPress.com\'ê hatiye, bitikîne. - Ji bo %1$s agahiyên hesabê xwe bikevê. + Ji bo %1$sê agahiyên hesabê xwe bikevê. an jî Bi Googleê re dewam bike Navnîşana malpera xwe bibîne Qediya Ma tu emailê nabînî? Peldanka emailên nexwestî(spam) kontrol bike. Li ser vê cîhazê emaila xwe kontrol bike û li girêdana ji WordPress.com\'ê hatiye, bitikîne. - Em ê girêdaneke tu pê karibî rasterast têketinê bikî, ji te re bi emaîlê bişînin, hewceyî bi pêborînê tune ye. - Emaîlê kontrol bike + Em ê girêdaneke tu pê karibî rasterast têketinê bikî, ji te re bi emailê bişînin, hewceyî bi pêborînê tune ye. Dest Pê Bike - Ji bo têketinê an jî afirandina hesabekî WordPress.com\'ê navnîşana emaîla xwe bikevê. An jî pêborîna xwe binivîse Hesabekî biafirîne - Girêdanê bi emaîlê re bişîne - Hêza malpersaza herî kêrhatî derxe meydanê. - 37% ê webê li ser WordPressê hatiye avakirin. Pêborîna xwe nûsaz bike - Di hênana daxwazê de pirsgirêkek derket. Ji kerema xwe paşê dîsa biceribîne. - Nevekî bide malpera xwe bila li kesayet û mijarê wê were. Dîtina ewil girîng e! + Emailê kontrol bike + Ji bo têketinê an jî afirandina hesabekî WordPress.com\'ê navnîşana emaila xwe bikevê. + Girêdanê bi emailê re bişîne + Di hêrana daxwazê de pirsgirêkek derket. Ji kerema xwe paşê dîsa biceribîne. Sernavê malpera xwe saz bike Ji bo sazkirina sernavekî nû, li <b>%1$s</b>ê bitikîne - Jêbirina vê şandiyê ew ê guhertinên derhêlî jî rake. Bi rastî jî tu dixwazî dewam bikî? - Vebijarkên bloka %sê + Nevekî bide malpera xwe bila li kesayet û mijara wê were. Dîtina ewil girîng e! + Jêbirina vê şandiyê ew ê guhertinên derhêlî jî rake. Tu bi rastî jî dixwazî dewam bikî? + Vebijarkên bloka %s\'ê Blokê pir bike Blokê kopî bike Blok hat kopîkirin @@ -160,78 +188,78 @@ Language: ku_TR Blokê jê bike Blok hat kopîkirin Sernavê Malperê tenê ji aliyê rêveberekî dikare were guhertin. - Sernavê Malperê, li benda-sernavê ya geroka webê tê xuyan û di pirî rûkaran de jî li sermalperê tê xuyan. Mijar Sernavê malperê nehat hildemandin. Girêdana tora xwe kontrol bike û dîsa biceribîne. Guhertinên netomarkirî + Sernavê Malperê, li benda-sernavê ya geroka webê tê xuyan û di pirî rûkaran de jî li sermalperê tê xuyan. Girêdanê di gerokê de veke + %1$s · %2$s · %3$s Diçe beşa taybetkirina gradyanê + Gradyanê taybet bike Diçe bijêra rengê taybet Cûreya Gradyanê Ji bo vebijarkekê hilbijêrî, ducar bitikîne - Gradyanê taybet bike - %1$s · %2$s · %3$s Nivîskarê rûpelê - Vê GIF\'ê bi kar bîne Wêneyê biçûk ê medyayê nehat barkirin Awaya naverokê Hemû kes Ez + Vê GIF\'ê bi kar bîne Veşêre Nesazkirî - Etîket di derbarê çi bûna şandiyê de ji xwîneran re dibin alîkar. Demweşan - Etîketan tevlê bike Paşve Aniha tomar bike - Aniha bişîne Aniha demsaz bike Tê weşandin li - Etîket Demweşan Betal bike Vegerîne Reşnîvîsê + Etîket Şandiyên ketine jêbirdankê nayên sererastkirin. Ji bo tu karibî li ser wê bixebitî em rewşa şandiyê bikin \"reşnivîs\"? - Bila şandî veguheze Reşnivîsan? - Mijarên bala te dikişîne hilbijêre + Etîket di derbarê mijara şandiyan de ji xwîneran re dibin alîkar. + Aniha Bişîne + Etîketan tevlî bike Mijarên bala te dikişîne hilbijêre Qediya - Ji bo domandinê çend heban hilbijêre - Weşandî Çû jêbirdankê - Demsazkirî Demweşan + Weşandî + Demsazkirî Hişyariya nihêniyê ya CCPA\'yê bixwîne Qanûna Nihênîtiya Xerîdaran ya Californiayê (\"CCPA\") me mecbûr dike em şêniyên Californiayê agahdar bikin; derbarê wan kategoriyên agahiyên kesane de yên ku em kom dikin û parve dikin, herwiha ka em wan agahiyên kesane ji kû distînin û em wan çawa û çima bi kar tînin. + Bila şandî veguheze nav Reşnivîsan? + Hobiyên xwe hilbijêre + Ji bo domandinê hinekan hilbijêre Hişyariya nihêniyê ji bo bikarhênerên Kalîforniyayê - Rewş û Xuyanî - Aniha hildemîne + Rewş & Xuyanî + Aniha Hildemîne %1$s · %2$s - Rûpelsaziya destpêkê biceribînin Menuya çalakiyên blokê veke + Rûpelsaziya destpêkê biceribînin Behskirinê tevlîke Ji bo Pelê Jêrîn yê bi vebijêrkên berdest vebe, du caran bitepîne Ji bo Pelê Çalakiyan yê bi vebijêrkên berdest vebe, du caran bitepîne - Em aniha nikarin rûpelan vekin. Ji kerema xwe piştre dîsa biceribîne Bike Rûpela Şandiyan - Wekî Serrûpel saz bike \"Bikarhênerên wiha tune ne.\" - \"%s ne navekî derbasdar yê bikarhênerekî ye\" Rûpelê hilbijêre - Rûpela şandiyan + Em aniha nikarin rûpelan vekin. Ji kerema xwe piştre dîsa biceribîne + Wekî Serrûpel saz bike Serrûpela Sabît - Bloga klasîk Serrûpela hilbijartî û rûpela şandiyan nabe wekî hev bin. + Rûpela Şandiyan + Bloga Klasîk + \"%s ne bikarhênerekî derbasdar e\" + Bipejirîne Sazkariyên serrûpelê nehat hildemandin, girêdana înterneta xwe kontrol bike Heta rûpel neyên barkirin sazkariyên serrûpelê nayên tomarkirin Sazkariyên serrûpelê nayên tomarkirin - Bipejirîne - Barkirina rûpelan têk çû An serrûpeleke şandiyên te yên herî dawî nîşan dide (bloga klasîk) an jî rûpeleke sabit/statîk hilbijêre. Sazkariyên Serrûpelê - Serrûpel + Barkirina rûpelan bi ser neket Rûpela şandiyan nehat hildemandin Rûpela Şandiyan bi serkevtî hat hildemandin + Serrûpel Serrûpel nehat hildemandin Serrûpel bi serkevtî hat hildemandin Ji bo sazkirina rûpela Şandiyan, di Sazkariyên Malperê de \"Serrûpela Sabît\" biçalakîne @@ -241,47 +269,44 @@ Language: ku_TR Ji bo biçî sazkariyên rengan, ducar bitikîne Dema ku te malper şopandin tu yê naverokên wan li vira bibînî Zêdetirî wê bibîne - Di WordPress\'ê De Çi Nû Ne - %dê tevlê bike - biqusîne Pelge nehat barkirin, jkx dîsa biceribîne. - Pêşdîtina Wêneyê Biçûk Vê medyayê bi kar bîne Vê vîdyoyê bi kar bîne Medya hilbijêre Vîdyo Hilbijêre Malper nehat bijartin. Jkx, dîsa biceribîne. + Di WordPress\'ê De Çi Nû Ne + biqusîne + Pêşdîtina Wêneyê Biçûk + %d\'ê tevlî bike Bidomîne - Dîsblog bi ser neket - Malperan Birêve Bibe - Piştî te malpereke WordPress.com\'ê saz kir, tu dikarî naverokên kêfa te ji wan re tê li malpera xwe dîsblog bikî. - Ti malperên WordPress.com\'ê yên berdest nîn in Çi Nû Ne - Girêdan hat kopîkirin Navnîşana girêdanê kopî bike Parve bike bi Nehat parvekirin - Tevlê bike Bidomîne Kopî bike - Blokê ji pozisyona %1$sê, ber bi rastê ve bikişîne pozisyona %2$sê + Dîsblog bi ser neket + Malperan Birêve Bibe + Piştî te malpereke WordPress.com\'ê saz kir, tu dikarî naverokên kêfa te ji wan re tê li malpera xwe dîsblog bikî. + Ti malperên WordPress.com\'ê yên berdest nîn in + Girêdan hat kopîkirin + Tevlî bike Blokê veguhezîne rastê - Blokê ji pozisyona %1$sê, ber bi çepê ve bikişîne pozisyona %2$sê Blokê veguhezîne çepê Ji bo blokê veguhezînî rastê, ducar bitikîne Ji bo blokê veguhezînî çepê, ducar bitikîne - Blog + Blokê ji pozisyona %1$sê, ber bi rastê ve bikişîne pozisyona %2$sê + Blokê ji pozisyona %1$sê, ber bi çepê ve bikişîne pozisyona %2$sê Afirandina destgehê Sazkirina rûkarê Tevlêkirina taybetiyên malperê - Sitendina URL\'ya malperê - Malpera te ew ê di demeke kin de amade be Bijî!\nHindik ma biqede + Blog + Malpera te ew ê di demeke kin de amade be + Stendina URL\'ya malperê Hilxistinê betal bike Gava ev daxwaz dihat pêkanîn pirsgirêkek çêbû - Bihêzkirin: Tenor - Di Tenorê de lêgerînê bike - Ji Tenorê hilbijêre Şemî În Pêncşem @@ -289,268 +314,270 @@ Language: ku_TR Sêşem Duşem Yekşem + Bihêzkirin ji aliyê Tenor + Di Tenor\'ê de lê bigere + Ji Tenor\'ê hilbijêre Gihînî naveroka malpereke taybet nebû. Dibe ku hinek medya neyên bikaranîn Gihînî naveroka malpereke taybet dibe Wêne nehat qirpandin û tomarkirin, jkx dîsa biceribîne. - Barkirina wêneyê pêk nehat.\nJi bo dîsa biceribîne, bitepîne. Wêneyê pêş bibîne Formata rûpela nenas - Me nekarî vê çalakiya temam bikin û vê rûpelê bişinînin ji bo vekolînê. + Barkirina wêneyê pêk nehat.\nJi bo dîsa biceribîne, bitepîne. Me nekarî vê çalakiyê temam bikin û vê rûpelê demsaz bikin. Me nekarî vê çalakiyê temam bikin û vê rûpela taybet biweşînin. - Me nekarî vê çalakiyê temam bikin û vê rûpela biweşînin. + Me nekarî vê çalakiyê temam bikin û vê rûpelê bişinînin ji bo vekolînê. Me nekarî vê rûpelê bişînin ji bo vekolînê lê em ê piştre dîsa biceribînin. Me nekarî vê rûpelê demsaz bikin lê em ê piştre dîsa biceribînin. Me nekarî vê rûpela taybet biweşînin lê em ê piştre dîsa biceribînin. Me nekarî vê rûpelê biweşînin lê em ê piştre dîsa biceribînin. Me nekarî vê medyayê lê hilxin û vê rûpelê bişînin ji bo vekolînê. + Me nekarî vê çalakiyê temam bikin û vê rûpelê biweşînin. Me nekarî vê medyayê lê hilxin û vê rûpelê demsaz bikin. Me nekarî vê medyayê lê hilxin û vê rûpela taybet biweşînin. Me nekarî vê medyayê lê hilxin û vê rûpelê biweşînin. Dema ku cîhaza te dîsa serhêl bû em ê reşnivîsa te tomar bikin Dema ku cîhaza te dîsa serhêl bû em ê rûpela te ya taybet biweşînin. - Dema ku cîhaza te dîsa serhêl bû em ê rûpela te demsaz bikin. - Dema ku cîhaza te dîsa serhêl bû em ê rûpela te ji bo vekolînê bişînin. - Dema ku cîhaza te dîsa serhêl bû em ê rûpela te biweşînin. Rûpel ket dorê Rûpel tê hilxistin Cîhaz derhêl e. Rûpel bi cîgehî hat tomarkirin. Te li ser vê rûpelê guhertinên netomarkirî çêkir + Dema ku cîhaza te dîsa serhêl bû em ê rûpela te demsaz bikin. + Dema ku cîhaza te dîsa serhêl bû em ê rûpela te ji bo vekolînê bişînin. + Dema ku cîhaza te dîsa serhêl bû em ê rûpela te biweşînin. Rûpela te tê hilxistin - Rûpelê hilxistinên medyayê bi ser nexist û bi ew derhêlî hatin tomarkirin - Rûpel li cîhazê hat tomarkirin Rûpel bi serhêlî hat tomarkirin - Ji bo kurterêya QuickPress\'ê blogê hilbijêre - Sazandian Teserrufa Beteryayê Tarî Ronî Xuyang + Rûpel li cîhazê hat tomarkirin + Sazandian Teserrufa Beteryayê Te berî niha di vê rûpelê de hinek guhertin kir lê te ew guhertin tomar nekirin. Guhertoya were barkirin hilbijêre:\n\n + Rûpelê, hilxistinên medyayê bi ser nexist û bi ew derhêlî hatin tomarkirin + Ji bo kurterêya QuickPress\'ê, blogê hilbijêre Mirov Çi Dibêjin Em xizmetên cûrbicûr pêşkeş dikin ji bo tu bikarî encamên dixwazî bi dest bixî. Gelo baş nizanî ji te re çi divên an bihayên wan çend in? Em dikarin ji te re îzah bikin kîjan xizmet ji bo te baş in û behsa fiyetên xwe bikin. Li jêrê bi me re têkilî dayîne. - Em komeke biçûk ya pisporên jêhatî yên xwedî huner û tecrûbeyên cûrbicûr in. Em ji karê xwe hez dikin û wî bi dilxwazî dikin. Em ji dil li hêviyê ne ji bo bi te re bixebitin. + Em komeke biçûk a pisporên jêhatî yên xwedî huner û tecrûbeyên cûrbicûr in. Em ji karê xwe hez dikin û wî bi dilxwazî dikin. Em ji dil li hêviyê ne ji bo bi te re bixebitin. + Walt Disney Peyama Hişyariyê Tu dixwazî bi me re bixebitî? - Walt Disney Dê serdêr bixwazin pê bizanin yê li wî aliyê rûpelê kî ye. Ji bo li ser xwe, malpera xwe, karê xwe an jî her çi tiştê dixwazî binivîsînî vê qadê bi kar bîne. Ji bo ji ber gotinên xelkê yên li ser heman tiştan dipeyivin bigirî, helwestên li jêrê bi kar bîne. + Samuel the Dog + Sally Smith DYA - Ev naverokên nimûneyî ne, ji bo taybetiyên wî nîşan bidin li Kirasî hatine tevlîkirin. Wan jê bibe an jî li şûna wan peyv û medyayên xwe dayîne. - Ji bo mirov dest pê bike, divê dev ji axiftinê berde û dest bi kar bike. - Tîm Peyama Serkevtinê Stratejî Naveroka şandiyê nîşan bide Xizmet - Samuel the Dog - Sally Smith Navê Projeyê Pozisyon an jî Nasnav + Ev naverokên nimûneyî ne, ji bo taybetiyên wî nîşan bidin li Kirasî hatine tevlîkirin. Wan jê bibe an jî li şûna wan peyv û medyayên xwe dayîne. + Ji bo mirov dest pê bike, divê dev ji axiftinê berde û dest bi kar bike. + Tîm Portfolyo Tenê jêgirê nîşan bide Portfolyoya min çendîn projeyên min di seranserê kariyera xwe de ava kirine nîşan dide. Li jêrê agahiyên min yên têkiliyê bibîne û bi min re têkilî dayîne. Girêdana: - Ka em biaxivin 👋. Dudilî nebe, tu dikarî ji agahiyên têkiliyê yên li jêr xwe bigihînî me an jî bi bikaranîna formê peyamekê ji me re bişîne. + Ka em biaxivin 👋 Dudilî nebe, tu dikarî ji agahiyên têkiliyê yên li jêr xwe bigihînî me an jî bi bikaranîna formê, peyamekê ji me re bişînî. Ka were em bi hev re tiştekî ava bikin! Ka were em bi hev re tiştekî ava bikin! Juan Pérez J.K. Rowling - Harry, bijartinên me ji qabiliyetên me zêdetir, me bi rastî didin nîşan ka em kî ne. + Dr. Seuss + Dirêjahiya jêgirtinê (peyv) Îlham + Harry, bijartinên me ji qabiliyetên me zêdetir, me bi rastî didin nîşan ka em kî ne. Têkeve Têkiliyê - Dirêjahiya jêgirtinê (peyv) Ji min re email bişîne:<a href=\"mailto:mail@example.com\">mail@minak.com</a> - Dr. Seuss Negirî ji bo ku qediya, bikene ji bo pêk hat. - KESANE BIKE Bajar, 10100 - Bloka Pragrafê tevlê bike + BITAYBETÎNE Îzaheke kurt ji xizmetên tu pêşkêş dikî. - Biyografiyeke kurt ku raboriya kesane, serkevtinên girîng an jî rastiyek balkêş, bihewîne. <a href=\"mailto:mail@example.com\">mail@minak.com</a> Danasîna projeyê û xebatên tên pêşkêşkirin. + Biyografiyeke kurt ku raboriya kesane, serkevtinên girîng an jî rastiyeke balkêş, bihewîne. + Bloka Pragrafê tevlî bike (555)555–1234 - Rêya Kolana 10ê + Rêya Kolana 10 Şandiyekê biafirîne + Navnîşana Malperê Jêçûyî Demsazkirî Weşandî Girêdana Facebookê nikare ti Rûpelan bibîne. Bangewazî, bi Profîlên Facebookê ve nayê girêdan, tenê dikare bi Rûpelên weşandî ve bê girêdan. Nehatiye girêdan - Navnîşana Malperê - Ecibandin Şopîner Şîrove Nexwendî Neşîne jêbirdankê Jêbirdank + Ecibandin Çalakî Şandî û Rûpel Giştî - Karta nû tevlê bike - Karta amaranên nû tevlê bike + Karta nû tevlî bike + Karta amarên nû tevlî bike + Hilbijartî + Fîltreya heyî rake Ji bo şandiyên ji mijarên spesifîk bibînî bişkoka fîtreyê bi kar bîne Malper an Etîketê Hilbijêrin, Paceya Hilpekok - Hilbijartî Ji bo şandiyan fîltre bikî Malperekê an Etîketekê bibijêre - Fîltreya heyî rake - Etîket & Malperan bi rê ve bibe Têkeve WordPress.com\'ê - Têkeve WordPress.comê ji bo şandiyên ji etîketên tu wan dişopînî bibînî - Têkeve WordPress.comê ji bo şandiyên ji malperên tu wan dişopînî bibînî + Etîket û Malperan bi rê ve bibe + Ji bo tu şandiyên herî dawî yên ji mijarên tên şopandin bibînî têkeve WordPress.com\'ê + Ji bo tu şandiyên herî dawî yên ji malperên tên şopandin bibînî têkeve WordPress.com\'ê Pêşdîtina Kirês Bloka heyî biguherîne Tevlî dawiyê bike Tevlî serî bike Tevlî pêşiya blokê bike Tevlî paşiya blokê bike - Etîketekê tevlê bike Malperekê bişopîne - Tu dikarî etîketekê tevlî bikî ji bo şandiyên mijareke spesifîk bişopînî Şandiyên herî nû yên ji malperên tu wan dişopînî bibîne + Mijarekê tevlî bike + Tu dikarî bi lêzêdekirina mijarekê şandiyên eleqedarî babeteke diyar bişopînî Şopandinên Te Fîltre bike Sernivîsa vîdyoyê. %s Vîdyoyê sererast bike Ji bo rêkxistinekê bibijêrî du caran bitepîne - Kurtekodekê tevlê bike… + Kurtekodekê tevlî bike… Nivîskarê şandiyê - Şandiyekê biafirîne - Te hemû statîstîkên vê serdemê bihîstin.\n Heke dîsa bitepînî dê ji serî ve dest pê bike. - Di vê serdemê de qet statîstîk nîn in. - Ji bo %1$s Çalakiya Şandiyan - %1$s dîtin ji bo %2$sê di ev qas rojan de pêk hatine: %2$s %3$s . Ji bo agahiyên bêhtir bitepîne. - here hemû statîstîkên vê serdemê pir bilind bilind navîn nizm -   û %1$d %2$s + Şandiyekê biafirîne + %1$s dîtin ji bo %2$sê di ev qas rojan de pêk hatine: %2$s %3$s . Ji bo agahiyên bêhtir bitepîne. +   & %1$d %2$s + Te hemû amarên vê heyamê bihîstin.\n Heke dîsa bitepînî dê ji serî ve dest pê bike. + Di vê heyamê de ti amar nîn in. + Ji bo %1$s\'ê Çalakiya Şandiyan + here hemû amarên vê heyamê %1$s, %2$d %3$s - Wênêyê veguhezîne pêşiyê - Wêneyê veguhezîne paşiyê - Sernivîsa pêşangehê. %s %1$s. Nirxê heyî %2$s ye Şandî an jî rûpelekê biafirîne + Wênêyê veguhezîne pêşiyê + Wêneyê veguhezîne paşiyê + Sernivîsa galeriyê. %s Malpera ji destê mirovan - Niha na Em ê alî te bikin ji bo her çi tiştên dixwazî li vê derê ava bikî an parve bikî. - Bi xêr hatî WordPress\'ê + Ne niha Wênegeh Wêne nehat hilbijartin - , Hat hilbijartin + Tu bi xêr hatî WordPress\'ê Wêne hat hilbijartin Wêneyê Biçûk Şandiya blogê - Nû tevlê bike + ,Hat hilbijartin + Nû tevlî bike Biweşîne - Me gelek pêşxistinên mezin pêk anîn li ser edîtora blokî û bi ya me divê tu wê biceribînî! Me ew ji bo şandî û rûpelên nû çalak kir, lê heke dixwazî vegerî edîtora klasîk, here \'Malpera Min\'> \'Sazkariyên Malperê\'. + Me gelek pêşxistinên mezin li ser edîtora blokî pêk anîn û bi ya me divê tu wê biceribînî! Me ew ji bo şandî û rûpelên nû çalak kir, lê heke dixwazî vegerî edîtora klasîk, here \'Malpera Min\' \'Sazkariyên Malperê\'. Aniha senkronîze bike Ev şandî ew ê yekser were senkronîzekirin. Tu ji bo senkronîzekirinê amade yî? - Ev navper(domain) nayê bikaranîn - Pêşgotin -%s + Ev navper(domain) nayê bikaranîn Me nekarî xwe bigihînin malpera te. Ji bo çareserkirina vê pirsgirêkê hewce ye tu xwe bigihînî hewangeha(host) xwe. Ji ber pirsgirêkeke eleqedarî <b>Sertîfîkaya SSL</b>ê me nekarî xwe bigihînin malpera te. Ji bo çareserkirina vê pirsgirêkê hewce ye tu xwe bigihînî hewangeha(host) xwe. Me nekarî xwe bigihînin malpera te ji ber ku hewceyî bi <b>Rastandina HTTP</b>ê heye. Ji bo vê çareser bikî divê tu bi hewangerê xwe re têkilî dayînî. + Rûpela malperê + Şopandî Me nekarî xwe bigihînin </b>dosyeya XMLPRC<b>ê ya li ser malpera te. Divê tu bi hewangerê xwe re têkilî dayînî ji bo vê çareser bikî. Hindikek ma! Tenê divê em navnîşana emaila te ya bi Jetpack\'ê ve girêdayî piştrast bikin <b>%1$s</b> Bi agahiyên xwe yên malperê têkeve. - Bi agahiyên xwe yên malperê %1$s têkeve Emaila pejirandinê bişîne - Rûpela malperê - Şopandî - Ecibandî - Keşf bike - Hat tomarkirin - Etîket - Malper + Bi agahiyên xwe yên malpera %1$sê têkeve %sQi %sQa %sT %sB %sM %sk + Ecibandî + Keşf bike + Malper Em aniha nikarin şandiyan vekin. Ji kerema xwe piştre dîsa biceribîne Em aniha nikarin daneyan ji bo malpera te bar bikin. Piştre dîsa biceribîne + Hat tomarkirin + Mijar Medyageha WordPress\'ê - Komê Jihev Veqetîne Wergerîne Sernav: + Dest bi nivîsandinê bike… + Komê Jihev Veqetîne + Vîdyo an jî Wêne Bikişîne Ji bo klavyeyê veşêrî, bitikîne Ji bo alîkariyê nişan bidî, bitikîne - Vîdyo bikişîne - Vîdyo an jî Wêne Bikişîne - Wêneyekî bikişîne - Dest bi nivîsandinê bike… + Vîdyo Bikişîne + Wêneyekî Bikişîne Mezinahî - Pêveka sernavê kêlekbendê Beşê nîşan bide + Blokê jê bike Bloka %s. Di vê blokê de naveroka nederbasdar heye Bloka %s. Vala - Blokê jê bike + Pêveka sernavê kêlekbendê Bilokê Nûsaz Bike - Têbîniyan rake Pirsgirêk di vekirina vîdyoyê de derket + Bloka dawiya rûpelê. %s + Sazkariyan Veke + Têbîniyan rake Pirsgirêk di nîşandana blokê de derket Sernavê şandiyê. %s Sernavê şandiyê. Vala - URL\'ê bizeliqîne - Bloka dawiya rûpelê. %s - Sazkariyan Veke - Ti sepan nikare vê daxwazê birêve bibe. Ji kerema xwe gerokeke webê saz bike. - Here Jorê + URL\'yê bizeliqîne + Ti sepan nikare vê daxwazê birêve bibe. Ji kerema xwe gerokeke Webê saz bike. Panela min a berî-weşanê Agahiyên rewşa şandiya min Panela min a piştî-weşanê + Here Jorê Panela sazkariyên dosyeya min Blokê ji rêzika %1$sem veguhezîne rêzika %2$sem Blokê veguhezîne jorê Blokê ji rêzika %1$sem bi jêr ve veguhezîne rêzika %2$sem - Blokê bikişîne jêrê Sernivîsa wêneyê. %s Klavyeyê veşêre - Naveroka panelê li vir e! Îkona alîkariyê Tevlîkirina medyayê bi ser neket.\nJi bo vebijarkan, bitepîne. + Blokê bikişîne jêrê + Naveroka panelê li vir e! Ji bo guhertina dawî vegerînî, ducar bitikîne + Ji bo bijartinê, ducar bitikîne Ji bo sazkariyê biguherînî, ducar bitikîne Ji bo wêneyekî hilbijêrî, ducar bitikîne Ji bo vîdyoyekê hilbijêrî, ducar bitikîne - Ji bo bijartinê, ducar bitikîne Ji bo guhertina dawî dubare bikî, ducar bitikîne Ji bo blokê veguhezînî jorê, ducar bitikîne Ji bo blokê veguhezînî jêrê, ducar bitikîne Ji bo vî nirxî sererast bikî, ducar bitikîne - Ji bo bi rêya kaşokê nirxî biguherînî ducar bitepîne Ji bo blokekê tevlê bikî, ducar bitikîne - Nirxê heyî %s ye + Ji bo bi rêya kaşokê nirxî biguherînî, ducar bitepîne Naverok… Ji cîhazê hilbijêre + Nirxê heyî %s ye Kêlekbenda Şerhan - Çewtiyeke nenas qewimî. Jkx dîsa hewl bide. Nivîsa alternatîv - VÎDYO TEVLÊ BIKE - URL tevlê bike - WÊNE AN VÎDYO TEVLÊ BIKE - WÊNE TEVLÊ BIKE - BLOKÊ TEVLÎ VIR BIKE Tebîniyeke zêde devlîke - Raveyekê tevlê bike + Çewtiyeke nenas qewimî. Jkx dîsa hewl bide. + BLOKÊ TEVLÎ VIR BIKE + VÎDYO TEVLÎ BIKE + URL tevlî bike + WÊNE AN VÎDYO TEVLÎ BIKE + WÊNE TEVLÎ BIKE + Raveyekê tevlî bike Ji bo şandiyekê li lîsteya xwe qeyd bikî bitikîne bişkoka Tevlî Şandiyên Qeydkirî Bike. - %1$d hêman li lîsteyê hatin barkirin. + \"%1$d hêman li lîsteyê hatin barkirin.\" Danezan Girtî Vekirî Heke tu ji bo vê malperê danezanan bigirî, ew ê danezanên li ser \"hilpeka danezanan\" tên xuyan neçalak bike. Piştî ku te ji bo vê malperê danezan çalak kirin, tu dikarî cûreya danezanan saz bikî. - Ji bo danezanên vê malperê di hilpeka danezanan de bibînî, danezanên vê malperê çalak bike. Ji hilpeka danezanan ji bo vê malperê nîşandana danezanan çalak bike Ji hilpeka danezanan ji bo vê malperê nîşandana danezanan neçalak bike Danezanên ji bo vê malperê Danezanên ji bo vê malperê + Ji bo danezanên vê malperê di hilpeka danezanan de bibînî, danezanên vê malperê veke. Tu aniha ji bo rûpelên nû edîtora blokê bi kar tînî, bijî! Heke tu bixwazî vegere edîtora klasîk, here \'Malpera Min\' > \'Sazkariyên Malperê\'. - Tu aniha ji bo şandiyên nû edîtora blokê bi kar tînî, bijî! Heke tu bixwazî vegere edîtora klasîk, here \'Malpera Min\' > \'Sazkariyên Malperê\'. - Vîdyo an wêne tevlê bike + Tu ji bo şandiyên nû aniha edîtora blokê bi kar tînî, bijî! Heke tu bixwazî vegere edîtora klasîk, here \'Malpera Min\' > \'Sazkariyên Malperê\'. + Vîdyo an jî wêne tevlî bike Me nekarî vê şandiyê bişînin ji bo vekolînê lê em ê piştre dîsa biceribînin. Me nekarî vê şandiyê demsaz bikin lê em ê piştre dîsa biceribînin. Me nekarî vê şandiya taybet biweşînin lê em ê piştre dîsa biceribînin. @@ -564,355 +591,350 @@ Language: ku_TR Me nekarî vê medyayê lê hilxin û vê şandiya taybet biweşînin. Me nekarî vê medyayê lê hilxin û vê şandiyê biweşînin. Me nekarî vê medyayê lê hilxin. - Me nekarî vê çalakiyê temam bikin lê em ê piştre dîsa biceribînin. - Me nekarî ev çalakî temam bikin. Reşnivîseke vala nayê pêşdîtin Rûpeleke vala nayê pêşdîtin Şandiyeke vala nayê pêşdîtin Pêşdîtin nayê bikaranîn Çewtî: di tomarkirina şandiyê ya berî pêşdîtinê de + Me nekarî vê çalakiyê temam bikin lê em ê piştre dîsa biceribînin. + Me nekarî vê çalakiyê temam bikin. Pêşdîtin tê çêkirin… - Tê tomarkirin… Te li ser vê şandiyê guhertinên netomarkirî çêkir + Tomar dibe… Guhertoya ji vê sepanê Guhertoya ji cîhazeke din - Bi vê sepanê\nDi %san de hatiye qeydkirin\n\nBi amûreke din\nDi %san de hatiye qeydkirin\n Te berî niha di vê şandiyê de hinek guhertin kir lê te ew guhertin tomar nekirin. Guhertoya were barkirin hilbijêre:\n\n Tu dixwazî kîjan guhertoyê sererast bikî? - Bêveger jê bibe - Em ê guhertinên herî dawîn ên reşnivîsa te tomar nekin. + Bi Mayînde Jê Bîbe + Ji vê sepanê\nDi %san de hat tomarkirin\n\nJi cîhazeke din\nDi %san de hat tomarkirin\n + Em guhertinên herî dawîn ên reşnivîsa te tomar nakin. Em ê van guhertinan demsaz nekin. Em ê van guhertinan ji bo vekolînê neşînin. - Em ê van guhertinan neweşînin. Dema ku cîhaza te dîsa serhêl bû em ê reşnivîsa te tomar bikin Dema ku cîhaza te dîsa serhêl bû em ê şandiya te ya taybet biweşînin. + Em ê van guhertinan neweşînin. Dema ku cîhaza te dîsa serhêl bû em ê şandiya te demsaz bikin. Dema ku cîhaza te dîsa serhêl bû em ê şandiya te ji bo vekolînê bişînin. Dema ku cîhaza te dîsa serhêl bû em ê şandiya te biweşînin. - Ev çalakî nikare bê betalkirin. Dibe ku navê bikarhêner hatibe hildemandin. Navê te yê bikarhêneriyê yê nû %1$s e Navê bikarhêner tê tomarkirin… - Navê bikarhêner biguherîne - Tu navê xwe yê bikarhêneriyê dikî %1$s%2$s%3$s. Guhertina navê bikarhêneriyê dê bandorê li ser profîla te ya Gravatarê û navnîşanên te yên profîla Intense Debateyê jî bike.\nJi bo dewamkirinê, navê xwe yê bikarhêneriyê yê nû bipejirîne. + Ev çalakî nayê betalkirin. Dibe ku navê bikarhêner ji berê ve hatibe hildemandin. Baldar be! - Tu dikî navê xwe yê bikarhêneriyê yê ku niha %1$s%2$s%3$s ye, biguherînî. Tu yê careke din nekaribî navê xwe yê bikarhêneriyê bikî yê berê. - Sazkariyên performansa Jetpack\'ê bibîne û biguherîne Performans û Lez - Zêdetir - Li şûna lêgerîna WordPress\'ê ya cihgirtî tecrûbeyeke lêgerînê ya pêşketîtir saz bike + Navê bikarhêner biguherîne + Sazkariyên performansa Jetpack\'ê bibîne û biguherîne + Tu navê xwe yê bikarhêneriyê dikî %1$s%2$s%3$s. Guhertina navê bikarhêneriyê dê bandorê li ser profîla te ya Gravatarê û navnîşanên te yên profîla Intense Debateyê jî bike.\nJi bo dewamkirinê, navê xwe yê bikarhêneriyê yê nû bipejirîne. + Tu dikî navê xwe yê bikarhêneriyê yê ku niha %1$s%2$s%3$s ye, biguherînî. Tu yê nekaribî navê xwe yê bikarhêneriyê vegerînî yê berê. Lêgerîna Pêşketî - Lêgerîna Jetpack\'ê - Hewandina Vîdyoyên Bêreklam Medya - Bihêle bila Jetpack wêne û dosyeyên te yên statîk (wek CSS û JavaScriptê) optîmîze bike ji bo rûpelên te zûtir werin barkirin. - Dosyeyên statîk ên leztir - Wêneyên bilez Girtî + Wêneyên bilez + Zêdetir + Li şûna lêgerîna WordPress\'ê ya cihgirtî tecrûbeyeke lêgerînê ya pêşketîtir saz bike + Lêgerîna Jetpack\'ê + Hewandina Vîdyoyên Bê-reklam + Ji bo rûpelên te zûtir werin barkirin bihêle bila Jetpack wêne û dosyeyên te yên statîk (wek CSS û JavaScriptê) optîmîze bike. + Dosyeyên statîk ên bilez Vekirî + Performans Lezkera Malperê Tenê bi barkirina wêneyên li ser ekranê dixuyin re leza malperê xwe zêde bike. - Performans Daxistin - Dosye + Navçedema malperê (UTC) + Navçedema malperê (UTC + %s) + Navçedema malperê (UTC - %s) Daxistinên pelgeyan Rêjejimarên daxistina pelgeyan berî 28ê Pûşpera 2019an nehatiye tomarkirin. - Navçedema malperê (UTC - %s) - Navçedema malperê (UTC + %s) - Navçedema malperê (UTC) + Dosye Sermase Jixweber - Gotûbêjê Bigire - Cûreya pêşdîtinê hilbijêre Parve bike - Paşve here Pêşve here - Ji bo li ser \"%s\" ê biweşînî di sepana te %s ya WordPressê de\n\"%s\" hat demsazkirin - Şandiya Demsazkirî ya WordPressê: \"%s\" + Paşve here \"%s\" dê di 10 xulekan de were weşandin + Şandiya Demsazkirî ya WordPressê: \"%s\" + Cûreya pêşdîtinê hilbijêre + Ji bo li ser \"%s\" ê biweşînî di sepana te %s ya WordPressê de\n\"%s\" hat demsazkirin + Kodika Gotûbêjê Bigire + Dema ku hat weşandin \"%s\" dê di demjimêrekê de were weşandin \"%s\" hat weşandin - Şandiya demsazkirî: Bîranîna 10 xulekî - Şandiya demsazkirî: Bîranîna 1 demjimêrî Şandiya demsazkirî Heke dema weşanandinê di roboriyê de be, danezan nayê afirandin. - Dema ku hat weşandin + Şandiya demsazkirî: Bibîrxistina10 xulekî + Şandiya demsazkirî: Bibîrxistina 1 demjimêrî + Danezan + Girtî Berî 10 xulekan Berî demjimêrekê - Girtî - Salnameyê tevlê bike - Danezan Dem û Dîrok - Alîkarî hewce ye ji bo dîtina e-peyama ku tu pê dihate girêdan? + Tevlî salnameyê bike Malpera di vê adrêsê de ne malpereke WordPressê ye. Ji bo ku em karibin pê re têkiliyê deynin divê ser malperê WordPress sazkirî be. Xêra xwe temamiya adrêsa malperê têkeve, mîna \"example.com\". + Alîkarî hewce ye ji bo dîtina e-peyama ku tu pê dihate girêdan? Bi WordPressê têkeve, ji bo girêdana %1$s Dîtin Şandî %1$s: %2$s, %3$s: %4$s - Hêman hate tengkirin - Hêman hate firehkirin - Veşêre Fireh bike - Grafîk hate hildemandin. %1$s %2$s bo dewra %3$s, ji dewra berê hate guhertin - %4$s - Daneya karta bijartî tê barkirin - Sererastker + Veşêre + Hêman hat tengkirin + Hêman hat firehkirin + Grafîk hat hildemandin. + Daneyên karta bijartî tê barkirin + Edîtor Fireh bike Teng bike - Navnîşana epeyama xwe piştrast bike - rêname bo epeyama te hat şandin - Navnîşana epeyama xwe piştrast bike - rêname bo %s hate şandin - Betal + Navnîşana emaila xwe piştrast bike - rêwerz ji emaila te re hat şandin + Navnîşana emaila xwe piştrast bike - rêwerz ji %s\'ê re hat şandin BAŞ e http(s):// Girêdanê Rake Girêdanê tevlî bike Hilxistinê dîsa biceribîne - Medya tê hilxistin.\nJi kerema xwe bo vebijêrkan, bitepîne. - Girêdanê di paceyek/hilpekek nû de veke + Betal bike + Medya tê hilxistin.\nJi kerema xwe ji bo vebijêrkan, bitepîne. + Girêdanê di pencereyeke/hilpekeke nû de veke Ji bo amarên xwe bibînî, têkeve hesabê WordPress.com\'ê. + Îro Li gor lêgerîna te ti şandî nehatin dîtin Li şandiyan bigere - Mirov ew ê di înternetê de te li vir bibînin. - Navê navpereke(domain) premiûm bibijêre Di hemû planên WordPress.com\'ê de navperên(domain) taybet hene. Aniha navpera xwe ya premiûm bêpere tomar bike. Bi nêrînekê - Îro Hemû-dem + Mirov di înternetê de ew ê te li vir bibînin. + Navê navpereke(domain) premiûm hilbijêre Dîtinên vê hefteyê - Ji bo tevlîkirina alavokekê, ji kerema xwe têkeve sepana WordPressê. - Înternet tune ye Dane nehatin barkirin - Cûre Reng - Malpera xwe bibijêre - Tarî - Ronî Reng - Malpera xwe bibijêre Malper - Hemû-dem Dîtinên vê hefteyê Alavok tevlî bike + Ji bo tevlîkirina alavokekê, ji kerema xwe têkeve sepana WordPressê. + Tarî + Ronî + Înternet tune ye + Cûre + Hemû-dem + Malpera xwe hilbijêre + Malpera xwe hilbijêre Nûyandina hûrgiliyên pêvekan ji her tim bêhtir dewam dike. Ji kerema xwe paşê dîsa kontrol bike. Eger te navperek nû tomar kiribe, ji kerema xwe li bende be heta em sazkirina wê biqedînin û paşê dîsa biceribîne.\n\nEger na, wisa dixuyê ku şaşiyek pêk hatiye û dibe ku taybetiya pêvekê ji bo vê malperê ne guncan be. - Herêm (Tune Ye) Bi tomarkirina vê navperê tu %1$sşert û merc%2$s ên me dipejirînî. - Li girêdana xwe ya torê venêre û dîsa biceribîne. Ev rûpel niha nayê barkirin. + Li girêdana xwe ya torê venêre û dîsa biceribîne. Sazkarî nehatin wergirtin: Hin API ji bo vê tevîhevkirina IDya sepana OAuthê û ajimêrê ne guncan in. Bi sazkirina Jetpackê tu %1$smerc û rêbazên%2$s me dipejirînî. - Girêdan tune ye. Serastkirin hat neçalakkirin. - Ji bo sepanê dîsa girê bidî malpera xwe yî xwe-hewandî, pêborîna nû ya malperê binivîse vir. + Herêm (Tune ye) Pêborîn hat hildemandin + Ji bo sepanê dîsa girê bidî malpera xwe yî xwe-hewandî, pêborîna nû ya malperê binivîse vir. Pêborînê hildemîne + Înternet tune ye. Sererastkirin hat neçalakkirin. Navê navperê(domain) tê tomarkirin… - Herêmê Hilbijêre Welat Hilbijêre + Herêmê Hilbijêre + Bajar + Navnîşan 2 bidomîne navê navpera we <b>%s</b> tê sazkirin.\nMalpera we ji coşê dide çirifîtka! - Pîrozbahî - Navperê Tomar Bike - Koda posteyê Herêm - Bajar - Navnîşan 2 Navnîşan Welat Koda Welêt Telefon - Organîzasyon (li gor daxwazê) + Koda Posteyê + Bijî! + Domain\'ê Tomar Bike Ji bo rehetiya te, me agahiyên te yên pêwendiya WordPress.comê\n ji berê de tije kir. Ji kerema xwe bala xwe bidê, bê ka ji dil agahiyên tu dixwazî ji bo vê navperê bi kar bînî ew in an na. - Agahiyên têkiliya navperê + Organîzasyon (li gor daxwazê) Bi eşkereyî tomar bike Bi Parastina Nepenîtiyê, veşartî tomar bike Divê xwediyên navperan di agahîdankek giştî ya hemû navperan de agahiyên xwe yî pêwendiyê parve bikin. Bi Parastina Nepenîtiyê, em agahiyên xwe di şûna yên te de parve dikin û hemû danûstandinan radigihînin te. + Agahiyên têkiliyê yên navperê(domain) Parastina Nihêniyê - Ji kerema xwe %s yeka derbasdar têxîne - Navper Bibijêre + Ji kerema xwe %s a derbasdar bikevê - Veşêre Aniha biceribîne - Rêjejimarên(statîstîkên) ku dixwazî bibînî bibijêre û bala xwe bide agahiyên bi kêrî te tên. Ji bo rêjejimaran(statîstîkan) xwemalî bikî, bitepîne %1$s di binê \"Kûrbînî\"yê de. - Rêjejimarên xwe bi rê ve bibe - Guherto tê anîn… - Tevlîkirina medyayê bi serneket.\nJi bo ceribandineke din, bitepîne. + Veşêre + Domain\'ê Hilbijêre + Amarên ku tu dixwazî bibînî hilbijêrî û bala xwe bide daneyên bi kêrî te tên. Ji bo amarên xwe kesane bikî, li hêmana %1$s\'ê ya di binê \"Kûrbînî\"yê de ye bitikîne. + Amarên xwe bi rê ve bibe + Guherto tên stendin… Reşnivîsa te tê hilxistin + Tevlîkirina medyayê bi ser neket.\nJi bo dîsa biceribînî, bitikîne. Reşnivîs tê hilxistin Reşnivîs - Di vegerandina şandiyê de çewtiyek pêk hat - Vegeriya ser: %s - Tenê rêjejimarên herî pêwendîdar bibîne. Kûrbînên xwe tevlî jêr bike û serast bike. + Di vegerandina şandiyê de çewtiyek derket Civakî - Rêjejimarên Salane ya Malperê - Tevahiya Şopîneran - Pêşniyarên navperê nehate barkirin - Ji bo fikrên zêdetir peyvkilîdekê binivîse Ti pêşniyar nehatin dîtin - Navperê Tomar Bike - Ev Jetpack niha hate sazkirin, tene divê em sazkariyên te temam bikin. Ev, dê tenê xulekekê bigire. - Ji nav kûrbînan derxe - Daxe jêr - Bibe jor - Sazkariyên hêmana rêjejimaran - Şandî vedigere reşnivîsê - Şandî tê vegerandin + Vegeriya ser: %s + Tevahiya Şopîneran + Tenê amarên herî eleqedardar bibîne. Kûrbînên xwe tevlî jêrê bike û sererast bike. + Amarên malperê yên salane + Pêşniyarên navperê(domain) nehatin barkirin + Ji bo fikrên zêdetir, peyvkilîdekê binivîse + Domain\'ê Tomar Bike + Va ye Jetpack hate sazkirin, îja divê em sazkariyên te temam bikin. Ev ê tenê xulekekê bigire. Şandî hat vegerandin + Şandî tê vegerandin Şandî tê jêbirin - Jêbirina vê şandiyê dê guhertinên neqeydkirî jî rake. Bi rastî jî tu dixwazî dewam bikî? - Guhertinên xwecihî - Vegerîne Reşnîvîsê - Derbasî xuyanga lîsteyê bibe - Derbasî xuyanga kardan bibe - Şandiyên te yên hatine jêbirin tune - Şandiyên te yên reşnivîsî tune - Şandiyên te yên demsazkirî tune + Şandî vedigere reşnivîsê + Jêbirina vê şandiyê dê guhertinên netomarkirî jî rake. Bi rastî jî tu dixwazî dewam bikî? + Ji nav kûrbînan rake + Veguhezîne jêrê + Veguhezîne jorê + Sazkariyên hêmana amaran + Guhertinên cîgehî Te hîn ti şandî neweşandiye - Ji kerema xwe bi navê bikarhêner û pêborînê têkevê. - Ji kerema xwe li şûna navnîşana epeyama xwe, bi navê bikarhêner ya WordPress.comê têkevê. - Navîna peyvan/şandiyê + Veguhezîne Reşnivîsê + Derbasî xuyanga lîsteyî bibe + Derbasî xuyanga kartan bibe + Şandiyên te yên hatine jêbirin tune ne + Ti şandiyên te yên reşnivîsî tune ne + Ti şandiyên te yên demsazkirî tune ne + Ji kerema xwe bi navê bikarhêner û pêborînê têkeve. + Ji kerema xwe li şûna navnîşana emaila xwe bi navê bikarîner ê WordPress.com\'ê têkeve. Tevahiya peyvan - Navîna ecibandinan/şandiyê Tevahiya ecibandinan - Navîna şîroveyan/şandiyê - Tevahiya şîroveyan - Şandî Sal Îsal - Malpera li ser vê navnîşanê ne malpereke WordPressê ye. Ji bo girêdana bi pê re divê malper WordPressê bi kar bîne. + Tevahiya şîroveyan + Navîna peyvan/şandiyê + Navîna ecibandinan/şandiyê + Navîna şîroveyan/şandiyê + Şandî + Malpera li ser vê navnîşanê ne malpereke WordPressê ye. Ji bo em lê bên girêdan divê malper WordPressê bi kar bîne. Çavdêriya krediyên navper a berdest bi serneket. Krediyên navperê têne çavdêrîkirin - Navperê tomar bike Bo sazkirina pêvekan divê tu bibe xwediyê navpereke taybet ya ku têkîldarî malpera te yî. - Pêvek bisazîne - Tê piştre karibî xuyanga malpera xwe kesane bikî - Dema weşanê: %s + Domain\'ê tomar bike + Pêvekê saz bike Demsaz bike bo: %s - Di vê de hat weşand: %s Hat demsazkirin bo: %s Hefteyên Dawî - Pêşandinên Navîn yê Rojane - Pêşandin Heyam Meh û Sal - Bêhtir bar bike + Pêşandinên Navîn yê Rojane + Tu yê piştre karibî xuyanga malpera xwe kesane bikî + Demweşan: %s + Demweşan: %s + Dîtin + Zêdetir bar bike Îro - Demjimêra Herî Baş Roja Herî Baş + Demjimêra Herî Baş Rêjêjimarên têne pêşandayîn: Na spas Piştre - Niha deng bide - Dîsa hatina te çi xweş e! Heke kêfa te ji sepanê re hatibe, ji dengdayîna te ya li Google Play Storeê em ê kêfxweş bin. - Kêfa te ji WordPressê re hat? - Şandî vegeriya rewşa reşnivîsê + Aniha deng bidê + Çavê me li rêya betilî! Heke kêfa te ji sepanê re hatibe, ji dengdayîna te ya li Google Play Storeê em ê kêfxweş bibin. Çalakiya Şandinê + Kêfa te ji WordPressê re hat? Malper hîn nehatiye barkirin - Şandiyên bêhtir Şandiyên kêmtir - Gengaz e tu pêşveçûnê winda bike. Bi rastî jî tu dixwazî derkeve? - Dîsa jî Planan Nîşan Bide - Ji bo dîtina Planan girêdana torekê pêwîst e, ji ber wê dibe ku hûrgilî kevn bin. - Ji bo dîtina Planan girêdana torê pêwist e. - Niha em nikarin Planên te bar bikin. Piştre dîsa biceribîne. - Plan nayên barkirin - Girêdan nîn e - Derbasî Serrastgeha Blokê Bibe - Derbasî Serrastgeha Klasîk Bibe - Di dema barkirina daneyên te de pirsgirêkek pêk hat, rûpela xwe nû bike û dîsa biceribîne. - Dane nehat barkirin - Bi serrastgeha bloke şandî û rûpelên nû serrast bike - Serrastgeha Blokê bi kar bîne - Serrastgeha Blokê Hat Çalakkirin - derkeve - Ji %1$d/%2$d hat qedandin + Şandî veguherî rewşa reşnivîsê + Şandiyên zêdetir + Dibe ku tu pêşveçûnê wenda bikî. Tu bi rastî jî dixwazî derkevî? + Dîsa jî Pakêtan Nîşan Bide + Ji bo Pakêtan bibînî girêdana înternetê pêwîst e, ji ber wê dibe ku hûrgilî ne hildem bin. + Ji bo Pakêtan bibînî girêdana înternetê pêwîst e. + Niha em nikarin Pakêtên te bar bikin. Piştre dîsa biceribîne. + Pakêt nayên barkirin + Înternet tune ye + Derbasî edîtora blokê bibe + Derbasî edîtora klasîk bibe + Di dema barkirina daneyên te de pirsgirêkek derket, rûpela xwe nû bike û dîsa biceribîne. + Dane nehatin barkirin + Bi edîtora bloke şandî û rûpelên nû sererast bike + Edîtora blokê bi kar bîne + Edîtora Blokê Hat Çalakkirin + derketin + Ji bo plana derbasdar û ên din ên berdest bibînî pê li %1$s Plan %2$s ê bike Girseya Xwe Mezin Bike - Malpera Xwe Kesane Bike Gavên Pêşve (%d) Qediya Kêfa xwe bi berhema qediyayî bîne! - Nîşanekeke malperê hilbar bike - Mêvanên te dê nîşaneka te di gerokên xwe de bibînin. Ji bo xuyangeke birqonek û profesyonel nîşanekeke taybet tevlî bike. - Ji bo plana derbasdar û ên din ên berdest bibînî pê li %1$s Plan %2$s ê bike Ji bo rûpeleke nû biafirînî pêlî %1$s Rûpel Tevlî Bike %2$s yê bike. - Ji bo domandinê %1$s Rûpel %2$sê bitepîne. + %1$d / %2$d qediya + Malpera Xwe Taybet Bike + Îkona malperekê hilxîne + Mêvanên te ew ê îkona te di gerokên xwe de bibînin. Ji bo xuyangeke profesyonel û balkêş îkoneke taybet tevlî bike. Ji bo performansa malpera xwe bibîne pêlî %1$s Rêjejimar %2$sê bike. Ji bo yeka din bar bike, pêlî %1$s Nîşaneka Te ya Malperê %2$s bike Yekem şandiya xwe bike reşnivîs û biweşîne. Em ê ji bo binyadên avakirin û mezinkirina malpera te ji te re rêberiyê bikin. Me hinek guhertin di lîsteya te ya çavdêriyê de kir. - Ji bo alikariya mezinkirina girseya te, me zêdetir peywir tevlî kir. + Ji bo domandinê %1$s Rûpel %2$sê bitepîne. Planan venasîne Di planên mûçekarî de agahî derbarê alavên bazargerî û SEOyê bi dest bixe. + Ji bo alikariya mezinkirina girseya te, me zêdetir peywir tevlî kir. Parvekirina şandiyê biçalakîne - Ji bo ajimêrên xwe yên medyayên civakî, şandiyên nû bi otomatîkî parve bike. Fontên nivîsan eyar bike, wêneyan tevlî bike û zêdetir. Rûpeleke nû biafirîne - Ji bo naveroka sereke, rûpelekê tevlî bike — rûpela \"Derbar\" destpêkeke mezin e. - Rêjejimarên malpera xwe kontrol bike + Ji bo ajimêrên xwe yên medyayên civakî, şandiyên nû bi otomatîkî parve bike. + Ji bo naverokên girîng rûpelekê tevlî bike. Çêkirina rûpela \"Derbar\"ê ew ê destpêkeke mezin be. Di derbarê performansa malpera xwe de agahiyên rojane bi dest bixe. - Li rûkaran bigere Bi dehan vebijêrkên pergalê vebibîne û ahengiya bêkêmasî bibîne. Rakirina Gavên Pêş dê hemû gerên vê malperê veşêre. Ev çalakî nayê vegerandin. Gavên Pêş Rake + Li rûkaran bigere Vêya rake - Temamkirina lîsteya te pîroz be. Her bijî, karekî baş e. - Hemû peywir hat temamkirin! + Amarên malpera xwe kontrol bike + Te lîsteya xwe temam kir. Her bijî te karekî baş kir. Peywirê derbas bike - teng bike - fireh bike Bîranînok - Heyama pêşî hilbijêre + fireh bike + Hemû peywir hatin temamkirin! + teng bike Heyama paşî hilbijêre - %1$d%% ji dîtinan Dema Herî Populer %1$s (%2$s%%) +%1$s (%2$s%%) - Pêşdîtina malperê tê nişandan + %1$d%% ji dîtinan + Heyama pêş hilbijêre Paqij bike - Xuya ye girêdana te hêdî ye. Heke tu malpera xwe ya nû di lîsteyê de nabînî, venûkirinê biceribîne. - Efsûnbaza Afirandina Malperê Betal Bike Em malpera te ya nû diafirînin - Pirsgirêkek hebû Malperê Biafirîne Malperê Biafirîne - Li Navpêran Bigere Vê derê ew cih e ku dê mirov te li înternetê têde bibînîn. - Ji bo malpera xwe navekî navperê hilbijêre - Tu navnîşana derbasdar û lêgerîna te li hev nayên + Efsûnbaza Afirandina Malperê Betal Bike + Pirsgirêkek hebû + Pêşdîtina malperê tê nîşandan + Xuya ye înterneta te hêdî ye. Heke tu malpera xwe ya nû di lîsteyê de nabînî, nûkirinê biceribîne. + Li Domainan Bigere Şaşiyeke di ragihandina rajekarê de pêk hat, ji kerema xwe dîsa biceribîne Pirsgirêkek hebû - Pirsgirêkek hebû + Tu navnîşana derbasdar û lêgerîna te li hev nayên + Ji bo malpera xwe navekî navperê hilbijêre Ev ê ji bo pêşniyardayînê alîkariya me bike. Lê, tê qet sînordar nebe — wiha dê hemû malper pêşkevin! + Pirsgirêkek hebû Tu dixwazî malpereke çawa çê bike ji mere bibêje - malpera te hat afirandin! - %1$d ji %2$d - Malperê Biafirîne - Pêşniyar hatin hildemandin Malpera xwe-hewandî ya nû hatiye tevlîkirin, nehat hilbijartin. Nakokiya guhertoyê - Ji bo alîkariya pêşxistinê ya performansa sepanê, desture bide raporê hilweşînên otomatîk. - Raporên hilweşînê + Malperê Biafirîne + Pêşniyar hatin hildemandin + Malpera te hat afirandin! + %1$d / %2$d Vegerîne Guhertoya webê hat avêtin - Guhertoya cîgehî hat avêtin + Raporên hilweşînê + Ji bo alîkariya pêşxistinê ya performansa sepanê, desture bide raporê hilweşînên otomatîk. Şandî tê hildemandin - Webê Biavêje - Xwecihiyê Biavêje - Xwecihî\nDi %s an de hat tomarkirin\n\nWeb\nDi %s an de hat tomarkirin\n + Guhertoya cîgehî hat avêtin + Web\'ê Biavêje Du guhertoyên nakok yê vê şandiyê hene. Guhertoya tu dixwazî ya avêtinê hilbijêre.\n\n\n - Nakokiya hevdemiyê çareser bike + Cîgehiyê Biavêje + Cîgehî\nDi %s\'an de hat tomarkirin\n\nWeb\nDi %s\'an de hat tomarkirin\n + Nakokiya senkronîzeyê çareser bike Ji bo vê heyamê dane tune Ji medyayê cih rake Rêjejimar vêga nayên vekirin. Paştre dîsa biceribîne. - Ji ber çewtiya torê hinek medya nehatin barkirin. - Lêgerîna te û ti medya li hev nehat - Ji bo tu GIF\'an tevlî Medyageha xwe bikî, lê bigere! Dîtin Nivîskar Nivîskar Dîtin - Têgeha Lêgerînê - Têgehên Lêgerînê Dîtin Sernav Vîdyo @@ -920,12 +942,12 @@ Language: ku_TR Welat Welat Tikandin + Ji weşana %2$s vir ve %1$s. Heta nuha şandeya te çawa performans kiriye, li vir e: Girêdan Tikandin Dîtin Şander Şander - Şandî û Rûpel LinkedIn Google+ @@ -933,13 +955,18 @@ Language: ku_TR Twitter Facebook Zêdetir bibîne - Şandî parve bike - Şandî biafirîne - Ji weşana %2$s vir ve %1$s. Heta nuha şandeya te çawa performans kiriye, li vir e: + Şandî û Rûpel + Lêgerîna te û ti medya li hev nehat + Ji ber çewtiya torê hinek medya nehatin barkirin. + Ji bo tu GIF\'an tevlî Medyageha xwe bikî, lê bigere! + Têgiha Lêgerînê + Têgihên Lêgerînê + Şandiyê parve bike + Şandiya biafirîne Ji weşana %2$s vir ve %1$s. Topa xwe bigindire û bi riya parvekirinê dîtinên şandeya xwe zêde bike: - Te hîn ti şandî neweşandiye. Dema te dest bi weşanê kir dê kurteya şandiya te ya dawî li vir xuya bike: Etîket û Kategorî - Her-dem + Te hîn ti şandî neweşandiye. Dema te dest bi weşanê kir dê kurteya şandiya te ya dawî li vir xuya bike: + Hemû-dem %1$s - %2$s Şopîner Xizmet @@ -951,541 +978,540 @@ Language: ku_TR Şîrove Sernav Nivîskar - Şandî û Rûpel Nivîskar Ji vir ve Şopîner Tevahî %1$s Şopîner: %2$s - E-peyam WordPress.com Kûrbînan bi rê ve bibe - Hîn kûrbîn nehatine tevlîkirin + Şandî û Rûpel + Email Hîn dane tune ye + Hîn kûrbîn nehatine tevlîkirin Menuya Debugê Pêborîn tê guhertin… - Divê pêborîna te bi qasî dirêjiya şeş karekteran be. Ji bo bihêztirbûnê tîpên mezin û biçûk, hejmar û nîşanên wekî ! \" ? $ % ^ & ) bi kar bîne. - Pêborîn bi serkeftî hat guherandin Pêborînê biguherîne + Divê pêborîna te bi qasî dirêjiya şeş karekteran be. Ji bo bihêztirbûnê tîpên mezin û biçûk, hejmar û nîşanên wekî ! \" ? $ % ^ ) bi kar bîne. + Pêborîn bi serkeftî hat guhertin Nav - (sernav tune) - Pêşdîtina HTML Pêşdîtina dîtbarî - Serrastkirî + (sernav tune) + Pêşdîtina HTML\'î Paşve Pêşve + Revizyon %1$s hat bikaranîn Ji kerema xwe têkeve WordPress.com-ekê an jî malpereke WordPressê ya xwe-hewandî û girêdayî Jetpackê. - Serrastkirin tê barkirin - Serrastkirin hat barkirin - Bar bike - Şandî li ser %1$s di %2$s de hat afirandin Rûpel li ser %1$s di %2$s de hat afirandin + Bar bike Hîn raborî tune ye - Dema te guhertinek li ser şandiya xwe kir, tê bikaribî raboriyê li vir bibînî - Dema te guhertinek li ser rûpela xwe kir, tê bikaribî raboriyê li vir bibînî + Şandî li ser %1$s di %2$s de hat afirandin + Revizyon tê barkirin + Revizyon hat barkirin + Dema ku te guhertin li ser şandiya xwe kir, tu yê karibî raboriyê li vir bibînî + Dema ku te guhertinek li ser rûpela xwe kir, tu yê karibî raboriyê li vir bibînî Avatara bikarhêner Mezinahiya Tije Mezin Navîn - Wêneya biçûk Raborî - Rûpela hilbijartî ne li berdeste + Wêneyê biçûk Vekolîna Hilawîstî - Ti rûpelên te yên hatine jêbirin nîn e + Rûpela hilbijartî ne berdest e + Li rûpelan bigere + Bi Mayînde Jê Bîbe Ti rûpelên te yên demsazkirî nîn e Ti rûpelên te yên reşnivîs nîn e Te hîn ti rûpel neweşandiye. - Li rûpelan bigere Lêgerînên te bi ti rûpelan re hevber nebû - Bi Mayînde Jê Bîbe - Veguhezîne Jêbirdankê Vegerîne Reşnivîsê - Dêûbavan diyar bike + Rûpelên we yên jêbirî tune + Veguhêze jêbirdnakê Bibîne - Jêçûyî - Demsazkirî Reşnivîs - Weşandî Me gelek caran koda piştrastkirinê a SMSê şand — navberekî bide û di nav xulekekî de dîsa bixwaze. - Ti ajimêrên WordPress.comê bi vê ajimêra Googleê re hevberî hev nehatin. + Dêûbavan diyar bike + Demsazkirî + Weşandî + Bo jêbirdankê hat veguhêstin + Rûpela mak hat guhertin Lêgerînên te bi ti malperan re hevber nebû Lêgerînên te bi ti malperan re hevber nebû - Rûpela mak hat guhertin - Rûpel bi awayekî mayînde hate jêbirin + Ti ajimêrên WordPress.comê bi vê ajimêra Googleê re hevberî hev nehatin. Rûpel hat demsazkirin Rûpel hat weşandin + Di guhertina rûpela mak de pirsgirêkek heye + Makê Saz Bike + Rûpel bi awayekî mayînde hate jêbirin Rûpel hat jêbirin Rûpel bo reşnivîsê hate vegerandin Asta bilind Bi rastî jî tu dixwazî rûpela %s jê bibe? - Di guhertina rûpela mak de pirsgirêkek heye Di guhartina rewşa rûpelê de pirsgirêkek çê bû Di jêbirina rûpelê de pirsgirêkek çê bû - Makê Saz Bike vira bitepîne - Malpera xwe biafirîne Malpera xwe bînin rewşa xebitînê + Malpera xwe biafirîne + Malpera xwe parve bike Malpera xwe bibîne Ji bo tu pêşdîtina malpera xwe bibînî %1$s Malperê Bibîne %2$s yê bitepîne - Malpera xwe parve bike Ji bo domandinê %1$s Parvekirin %2$s ê bitepîne Ji bo tevlîkirina ajimêrên medyaya civakî %1$s Pêwendî %2$s yê bitepîne Ajimêrên xwe yên medyaya civakî girê bide – malpera te dê şandiyên nû wekî otomatîk parve bike. + Bipejirîne Şandiyekê biweşîne Ji bo şandiyeke nû biafirînî %1$s Şandî Biafirîne %2$s yê bitepîne - Ji bo destpêkirinê hinek alîkarî dixwazî? - Bipejirîne - Qet - Na Spas + Ji bo dest pê bikî alîkariyeke biçûk dixwazî? Malperên din bişopîne Ji bo malperên heman berjewendîdar bibînî li %1$s Lêbigere %2$s yê bitepîne - Ji bo domandinê %1$s Xwîner %2$s ê bitepîne + Na Spas Li malperan bigere ên dinûqûtin dilê te û wan bişopîne bo rojanekirinên şandeyên wan. - Malpera xwe besane bike + Qet + Ji bo domandinê %1$s Xwîner %2$s ê bitepîne Ji bo domandinê %1$s Rûkar %2$s ê bitepîne - Ji bo dest bi kesanekirina malpera xwe bike %1$s Kesane Bike %2$s yê bitepîne - Rûkarekê hilbijêre + Ji bo dest bi kesanekirina malpera xwe bike %1$s Kesane Bike %2$s yê bitepîne + Malpera xwe taybet bike + Here Ji bo rûkarên nû vebibîne %1$s Rûkar %2$s ê bitepîne Ji bo ahengeke bêkêmasî, çavekî li hemû rûkarên me bigerîne. - Here - Betal Ne niha - Zêdetir Ti malperên te nîn e Etîketa şopkirî tune ye Etîketan tevlî vir bike ji bo dîtina şandeyan ên ku favoriyên te ne + Betal bike + Rûkarekî hilbijêre + Zêdetir Ji bo girêdana Jetpackê di ajimêra xwe ya WordPress.comê keve. Dîsa Biceribîne Bidomîne - Jetpack di vêgavê de nehat sazkirin. Pirsgirêkek hebû - Jetpack sazkirî ye - Jetpack li malperate tê sazkirin. Qedandina vê kirariyê dê çend xulekan bigire Jetpack tê sazkirin - Dê agahiyên nasnameya malpera te neyê veşartin wê tenê ji bo sazkirina Jetpackê were bi kar anîn. - Jetpackê Saz Bike Jetpack - Jetpack PPTP + Jetpack di vêgavê de nehat sazkirin. + Jetpack li malperate tê sazkirin. Qedandina vê kirariyê dê çend xulekan bigire + Dê agahiyên nasnameya malpera te neyê veşartin wê tenê ji bo sazkirina Jetpackê were bi kar anîn. + Jetpack hat sazkirin + Jetpack\'ê Saz Bike + Jetpack PPP Ji bo bikaranîna rêjejimarên malpera te ya WordPressê, divê tu pêvekên Jetpackê saz bikî. - Lêgerînen te bi ti rûkaran re hevber nebû - Tu dixwazî çi bibînî? Tu etîket û lêgerîna te li hev nayên - Tu etîketên te tune ye. + Tu dixwazî çi bibînî? Etîketên xwe yên tu pir bi kar tînî tevlî vir bike, bi saya vê dikarin nivîsên te yên etîketkirî bi hêsanî hilbijêrin - Etîketek biafirîne + Lêgerînen te bi ti rûkaran re hevber nebû Lêgerîna te û ti medya li hev nayên - Tu yê ji WordPressê derkeve? - Guhertinên di şandiyan de hatiye kirin li malpera te nehatiye hilxistin. Heke tu aniha derkevî dê ev guhertin ji amûrê biçe. Dîsa jî tê derkevî? - Hîna kesî temaşe nekiriye - Hîna şopînerên epeyamê tune ne - Hîna şopîner tune - Hîna bikarhêner tune - Şandiyên te ecibandî dê li vir xuya bibe + Ti etîketên te tune ye + Etîketekê biafirîne + Tu yê ji WordPressê derkevî? + Guhertinên di şandiyan de hatine kirin li malpera te nehatine hilxistin. Heke tu aniha derkevî dê ev guhertin ji cîhazê bên jêbirin. Dîsa jî tu yê derkevî? + Hîn ti kesî temaşe nekiriye + Hîn şopînerên emailê tune ne + Hîn şopîner tune ye + Hîn bikarhêner tune ye + Şandiyên ku te ecibandine ew ê li vir xuya bibin Hîn ti tişt nehatiye ecibandin - Here şopandinên xwe Malperan keşf bike - Malperên tu dişopîne tune - Hîn ecibandin tune - Hîna şopîner tune + Here şopandinan + Malperên tu dişopînî tune ye Ji ber ku tu di plana azad de yî, tu yê di çalakiyên xwe de bûyerên sînorkirî bibînî. + Hîn ecibandin tune ye + Hîn şopîner tune ye Dema ku te guhertin li malpera xwe kir, dê bikaribî raboriya çalakiyên xwe li vir bibînî - Hîn çalakî tune - Şandiyekê biafirîne + Hîn çalakî tune ye Rûpelekê biafirîne - Medya hilbar bike - Ti medya tune - pêşengeha wêneyan - Nîşaneka malperê - wêneya rûkarê + Şandiyekê biafirîne + Medyayê lê hilxîne + Ti medyayên tune ye taybetiyên wêneyê + galeriya wêneyan + îkona malperê + wêneyê rûkarê Biavêje wêneyê profîlê - Demkî - Emaîl - Ji kerema xwe navnîşan xwe ya epeyamê têxîne - Ji bo domandinê ji kerema xwe navnîşana xwe ya epeyamê û nav têxîne - Ji \'Alîkarî & Piştgirî\'yê peyameke nû WordPress - Saz nebû - Têkiliya epeyamê - Tomarên sepanê - Bilêtên min Tu bawer î malpera xwe vegerînî li şûna %1$s berê %2$s? Dê ev, hemû naverok û vebijarkên ku hatine çêkirin an guhertin ji wê demê ve rake. Malperê Vegerîne Vegerandin didome - Li şûna %1$s %2$s ê tê vegerandin - Malpera te niha tê vegerandin + Demkî + Ji bo domandinê ji kerema xwe navnîşana xwe ya epeyamê û nav têxîne + Saz nebû + Ji \'Alîkarî & Piştgirîyê ve peyameke nû + Email + Ji kerema xwe navnîşana emaila xwe bikevê + Emaila têkiliyê + Tomargeha sepanê + Bilêtên Min + Vedigere rewşa %1$s %2$s\'ê Malpera te bi serkeftî hat vegerandin - Malpera te bi serkeftî hat vegerandin\nLi şûna %1$s %2$s ê hat vegerandin - Malpera te bi serkeftî hat vegerandin\nLi şûna %1$s %2$s ê tê vegerandin - Bişkoka çalakiyê ya Tomarên Çalakê + Malpera te niha tê vegerandin + Malpera te bi serkeftî hat vegerandin\nVegeriya rewşa %1$s %2$s\'ê + Malpera te bi serkeftî hat vegerandin\nVedigere rewşa %1$s %2$s\'ê + Bişkoka Tomargeha Çalakiyan Yên bi otomatikî tê rêvebirin - Vê şandiyê qeyd bike û kengî tu bixwazî wê bixwînî lê vegere. Ew ê tenê li ser vê cîhazê berdest be, şandiyên qeydkirî bi cîhazên te yên din re senkronîze nabe. - Şandiyan ji bo Piştre Tomar Bike + Vê şandiyê tomar bike û kengî tu bixwazî wê bixwînî lê vegere. Ew ê tenê li ser vê cîhazê berdest be, şandiyên qeydkirî bi cîhazên te yên din re senkronîze nabin. Lêgerîn nayê kirin Ti encam nehatin dîtin - Çavkaniya şandiyê bixwîne + Şandiyan ji bo Piştre Tomar Bike Malper + Çavkaniya şandiyê bixwîne Girêdana sêhrî hat şandin - Bi epeyamê têkevê - Piştrastkirina kodê - Agahiyên nasnameyê ya têketinê + Bi emailê têkeve Girêdana sêhrî hat şandin Têketina girêdana sêhrî Têketina navnîşanê ya malperê Têketina navnîşanê ya epeyamê Vebijêrkek din: + Pejirandina kodê + Agahiyên têketinê + Tomar bike Li %s ê bitepîne ji bo tomarkirina şandeyeke li lîsteya te. - Şandeyên tomarkirî tune ye — hîn! - Şandeya tomarkirî - Hemûyan Bibîne Tomarkirî - Tomar bike + Hemûyan Bibîne Ji nav şandiyên tomarkirî rake Tevlî şandiyên tomarkirî bike Şandiyên tomarkirî Hat rakirin - Nîşaneka malperê biguherîne - Betal + Heta niha ti şandî nehatiye tomarkirin! + Şandî hat tomarkirin Rake Biguherîne + Tu dixwazî nîşanekeke malperê tevlî bikî? + ev malper Destûra te bo serrastkirina nîşaneka malperê tune ye. Destûra te bo tevlîkirina nîşaneka malperê tune ye. Tu çawa dixwazî nîşanekê serrast bike? - Tu dixwazî nîşanekeke malperê tevlî bikî? - Nîşaneka Malperê - ev malper Biçalakîne - Bila ji bo \"%1$s%2$s%3$s\"ê danezan çalak bibin? - Danezanên malperê veke - Danezanên malperê bigire - Nîşaneka Jetpackê + Betal bike + Îkona malperê biguherîne + Îkona Malperê + Ev agahî alîkariya me dikin ji bo pêşverbirina berhemên me, ji bo kêrhatîtirkirina bazariya me, ji bo taybetkirina ceribeyên te yên WordPress.comê û ji bo bi dûrûdirêj qalkirina me di rêbaza me ya veşariyê de. + Dema têketina te ya ajimêra WordPress.comê, di derheqê bikaranîna xizmetan de bi alavê me ya analîzê re agahî parve bikin. + Rêbaza Xurekê Vegerîne - Bûyer - Nîşaneka çalakiyê - Tomara çalakiyê Çalakî + Danezanên malperê veke + Danezanên malperê bigire + Agahî berhev bike + Bila ji bo \"%1$s%2$s%3$s\"ê danezan çalak bibin? Polîtîkaya nihêniyê bixwîne - Hin aliyên sêyemîn jî tê de, em alavên şopandinê ya din bi kar tînin. Di derheqê wan û kontrolkirina wan de bixwîne. Polîtîkaya Aliyên Sêyem - Ev agahî alîkariya me dikin ji bo pêşverbirina berhemên me, ji bo kêrhatîtirkirina bazariya me, ji bo taybetkirina ceribeyên te yên WordPress.comê û ji bo bi dûrûdirêj qalkirina me di rêbaza me ya veşariyê de. Polîtîkaya Nihêniyê - Dema têketina te ya ajimêra WordPress.comê, di derheqê bikaranîna xizmetan de bi alavê me ya analîzê re agahî parve bikin. - Rêbaza Xurekê Sazkariyên nihêniyê - Agahî berhev bike + Îkona Jetpackê + Çalakî + Îkona çalakiyê + Tomargeha Çalakiyan + Hin aliyên sêyem jî tê de, em alavên şopandinê yên din bi kar tînin. Di derheqê wan û kontrolkirina wan de bixwîne. Şandî hat şandin - Ajimêra te tune ye? %1$sTomar Bibe%2$s - Taybetiya pêvekê pêwîstî bi rewşa baş a malperê dike. Taybetiya pêvekê pêwîst dike ku endamtiya bingehîn a navperê bi vê bikarhênerê ve girêdayî be. + Taybetiya pêvekê pêwîstî bi rewşa baş a malperê dike. + Hesabê te tune ye? %1$sTomar Bibe%2$s Taybetiya pêvekê pêwîstî bi berjewendiya rêvebirê dike. Pêvek li malperên VIPê nayê sazkirin. - Ji ber sînordarkirinên qada dîskê pêvek nayê sazkirin. Taybetiya pêvekê pêwîstî bi navnîşana epeyameke piştrastkirî dike. Taybetiya pêvekê pêwist dike ku malper gelemperî be. - Taybetiya pêvekê pêwistî bi planeke business dike. Taybetiya pêvekê pêwistî bi navpereke taybet dike. + Ji ber sînordarkirinên qada dîskê pêvek nayê sazkirin. + Taybetiya pêvekê pêwistî bi planeke business dike. Em sazkirina dawî dikin — hema hema qediya… Pêvek tê sazkirin… Saz Bike Dibe ku sazkirina pêveka yekem xulekekê bigire. Di vê demê de tê nikaribî guhertinan li ser malpera xwe bikî. + Rojane Pêvekê saz bike Danezan - Şîroveyên nû bi emailê ji min re bîşîne Heftane Demildest - Rojane Şandiyên nû Ji bo şandiyên nû yên vê malperê danezanan bistîne + Şîroveyên nû bi emailê ji min re bîşîne Şandiyên nû bi emalê ji min re bişîne Hemû Malperên Ez Dişopînim - Malperên Hatine Şopandin Kesê bi cîhazê danezanan dixwîne Mirovên li grafîk û planan dinêrin %s li ser %s Bi rastî jî tu dixwazî vê şandiyê bi awayekî mayînde jê bibî? + Malperên Şopandî Girîng Giştî Vî wêneyî bi kar bîne - %1$d / %2$d - Wêne ji aliyê %s ve tên peydakirin - Ji bo wêneyên belaş tevlî Medyagehê bikî, li wêneyan bigere - Di wênegeha serhêl de bigere - Ji wênegeha serhêl hilbijêre Reşnivîsa vala nayê tomarkirin - Ji %1$s bêsînor - %d pêşdîtin - %d Tevlê Bike + %1$d / %2$d + Ji aliyê %s ve tê peydakirin + Ji bo wêneyên belaş tevlî Medyageha xwe bikî, li wêneyan bigere + Li wênegeha belaş bigere + Ji nav Wênegeha Belaş hilbijêre + %1$s / bêsînor + Pêşdîtin %d + %d Tevlî Bike Etîketekê Biafirîne - here jorê - Danezan - Girêdana derveyî veke - zêdetir nîşan bide + deng + jê bibe + dîsa biceribîne + %s rake + nîşana pejirandinê wêne jê bibe - Vîdyoyê bilîzîne - Vîdyoya berpêş bilîzîne logoya pêvekê sernavê pêvekê - ji medyayên WordPress\'ê hilbijêre kamerayê veke - ji cîhazê hilbijêre agahiya rolê + Danezan + Vîdyoyê bilîzîne + Vîdyoya berpêş bilîzîne bilîzîne pêşdîtina wêneyê pêşdîtin - deng vîdyoyê bilîzîne - jê bibe - dîsa biceribîne + here jorê + Girêdana derveyî veke + zêdetir nîşan bide + ji cîhazê hilbijêre pêşdîtina medyayê, navê dosyeyê %s - %s rake wêneyê profîla %s - nîşana pejirandinê Bi Googleê tomar dibe… + ji nav medyayên WordPress\'ê hilbijêre Bi Jetpack\'ê ve nehat girêdan: %s Jixwe tu bi Jetpack\'ê ve girêdayî ye - VEGERÎNE - Derbasî moda Dîtbarî bû Moda Dîtbarî - Derbasî moda HTML bû - Moda HTML Pêşdîtin - Wekî reşnivîs tomar bike %s TB + Derbasî moda Dîtbarî bû + VEGERÎNE + Wekî reşnivîs tomar bike + Derbasî moda HTML\'ê bû + Moda HTML\'ê + Ecibandina şîroveyê hat rakirin + Şîrove hat ecibandin + Pejira şîroveyê hat rakirin + Şîrove hat pejirandin %s GB %s MB %s kB %s B - %1$s / %2$s - Heke pêwistiya te bi valahiya zêdetir hebe, bilindkirina plana WordPress\'ê bifikire. - Qada Hatiye Bikaranîn Medya - Şîrove wekî nespam hat nîşankirin Şîrove wekî spam hat nîşankirin Şîrove hat jêbirin Şîrove hat vegerandin - Şîrove hate şandin jêbirdankê - Ecibandina şîroveyê hat rakirin - Şîrove hat ecibandin - Pejira şîroveyê hat rakirin - Şîrove hat pejirandin + Qada Hatiye Bikaranîn Hûrgiliya danezanê %s + %1$s / %2$s + Heke pêwistiya te bi valahiya zêdetir hebe, bilindkirina plana WordPress\'ê bifikire. Wêneyê Sererast Bike Malper hilbijêre Hesabê nû - Têkeve - Bi vî hesabî têketî ye + Şîrove wekî ne spam hat nîşankirin + Şîrove hat jêbirin Hûrgiliya kesê - Hûrgiliya dosyeyê Bişkokên parvekirinê + Xwîner + Alîkarî û Piştgirî + Lîsans Danezan - Xwîner Ez Malpera Min + Bi vî hesabî têketî ye + Hûrgiliya dosyeyê Sazkariyên danezanan - Alîkarî û Piştgirî - Lîsans Notên guhertoyê Avatara te hat hilxistin û di demeke nêzîk de ew ê berdest bibe. - Xuya ye te destûrên pêdivî yên ji bo vê taybetiyê girtiye.<br/><br/>Ji bo guhertina wê destûrên xwe serrast bike û ji çalakbûna <strong>%s</strong>ê piştrast bibe. + © %1$d %2$s Destûr + Guherto %s + Xuya ye te destûrên pêdivî yên ji bo vê taybetiyê girtiye.<br/><br/>Ji bo guhertina wê destûrên xwe serrast bike û ji çalakbûna <strong>%s</strong>ê piştrast bibe. Berpêş Ji ber ku modula Jetpacka te ya Parvekirinê neçalak e, tu nikare bigihêje sazkariyên pervekirinê. Modula parvekirinê neçalak e - Guherto %s - © %1$d %2$s Weşanger: %s - Rêya dengê hilbijartî ne derbasdar e. Ji kerema xwe yeka din hilbijêre. + Rêya dengê hilbijartî nederbasdar e. Ji kerema xwe yeka din hilbijêre. QP %s - %1$d rûpel / şandî maye Rûpelek maye - %1$d rûpel maye - %1$d şandî maye - %1$d rûpel / şandî û pelgeyek maye - %1$d şandî û pelgeyek maye - %1$d rûpel û pelgeyek maye - Şandî û pelgeyek maye - Rûpel û pelgeyek maye - %1$d rûpel / şande û %2$d ji %3$d ê pelgeh man - %1$d şande û %2$d ji %3$d ê pelgeh man - %1$d rûpel û %2$d ji %3$d ê pelgeh man - 1 şande û %1$d ji %2$d ê pelgeh man - 1 rûpel û %1$d ji %2$d ê pelgeh man - %1$d şandî / rûpel nehatin hilxistin + %1$d rûpel / şandî mane + %1$d rûpel mane + %1$d şandî mane + %1$d rûpel / şandî û dosyayek mane + %1$d şandî û dosyayek mane + %1$d rûpel û dosyayek mane + Şandiyek û dosyeyek mane + Rûpelek û dosyeyek mane + %1$d rûpel / şandî û %2$d / %3$d dosye man + %1$d şandî û %2$d / %3$d dosye man + %1$d rûpel û %2$d / %3$d dosye man + Şandiyek û %1$d / %2$d dosye mane + Rûpelek û %1$d / %2$d dosye mane %1$d rûpel nehatin hilxistin + %1$d şandî / rûpel nehatin hilxistin Rûpelek nehat hilxistin - %1$d şandî nehatin hilxistin Şandiyek nehat hilxistin - %1$d şande / rûpel bi %2$d pel ve nehat barkirin - %1$d rûpel bi %2$d pel ve nehatin barkirin - 1 rûpel bi %1$d pel ve nehatin barkirin - %1$d şande bi %2$d pel ve nehatin barkirin - 1 şande bi %1$d pelgeh ve nehatin barkirin - %1$d şande / rûpel bi 1 pelgeh ve nehatin barkirin - %1$d rûpel bi 1 pelgeh ve nehatin barkirin - 1 rûpel bi 1 pelgeh ve nehatin barkirin - %1$d şande bi 1 pelgeh ve nehatin barkirin - 1 şande bi 1 pelgeh ve nehatin barkirin + %1$d şandî nehatin hilxistin + %1$d şandî / rûpel û %2$d dosye nehatin hilxistin + %1$d rûpel û %2$d dosye nehatin hilxistin + Rûpelek û %1$d dosye nehatin hilxistin + %1$d şandî û %2$d dosye nehatin hilxistin + Şandiyek û %1$d dosye nehatin hilxistin (Bêsernav) \@%s %1$f, %2$f + %1$d şandî / rûpel û dosyeyek nehatin hilxistin + %1$d rûpel û dosyeyek nehatin hilxistin + Rûpelek û dosyeyek nehatin hilxistin + %1$d şandî û dosyeyek nehatin hilxistin + Şandiyek û dosyeyek nehatin hilxistin Malperê biafirîne - Ji bo domandinê bitepîne. Malper hat afirandin! - Bersivdana Googleê gelek dereng ma. Heta ku girêdana te ya torê bihêztir bibe, dibe ku tu li bendê bisekine. - Navê bikarhênerê biguherîne - Ji bo zêdetir pêşniyazan binivîsîne Navê te ya heyî %1$s%2$s%3$s e/ye. Ji xeynî çend awarteyan, kesên din dê tenê navê te ya dîtinê %4$s%5$s%6$s bibînin. Ji %1$s%2$s%3$s ê navê bikarhêner nehat pêşniyazkirin. Ji bo ku pêşniyazan bistîne, ji kerema xwe zêdetir tîp an jî hejmar binivîse. - Dema ku pêşniyarên navên bikarhêneran dihat stendin çewtiyek pêk hat. - Bila guhertina navê bikarhêner betal bibe? - Bavêje Tomar bike Avatar tevlî bike + Dema ku pêşniyarên navên bikarhêneran dihat stendin çewtiyek pêk hat. + Bila guhertina navê bikarhêner betal bibe? Bi Google\'ê Tomar Bibe - Bi Epeyamê Tomar Bibe - Epeyam ji xwe li ser WordPress.comê heye.\nBi têketinê kar tê kirin. - Ajimêr tê hildemandin… - Bi tomarbûnê re tu %1$sMercên Xizmetê%2$s yên me dipejirînî. - Epeyam tê şandin + Ji bo domandinê, bitepîne. + Googleê pir dereng bersiv da. Dibe ku heta girêdana te ya înternetê bihêztir bibe tu bisekinî. + Navê bikarhêner biguherîne + Ji bo pêşniyarên zêdetir, binivîse + Biavêje + Bi Emailê Tomar Bibe + Jixwe email li ser WordPress.com\'ê heye.\nDerbasî têketinê dibe. + Hesab tê hildemandin… + Bi tomarbûnê re tu %1$sMercên Xizmetê%2$s qebûl dikî. + Email tê şandin Dîsa Bîceribîne Bigire + Dîsa biceribîne Di şandina epeyamê de pirsgirêk çebû. Tu dikarî niha dîsa biceribîne an jî bigire û piştre biceribîne. Navê bikarhêner - Tu her timî dikarî wekî berî niha bi girêdanekê têketinê bike lê heke tu bixwazî em dikarin pêborînekê jî ji te re saz bikin. Pêborîn (li gor daxwazê) Navê Xuya - Dîsa biceribîne Vegerîne Di hildemandina ajimêra te de hinek pirsgirêk çêbûn. Ji bo domandinê tu dikarî guhertinên xwe vegerîne an jî dîsa biceribîne. Di hilxistina avatara te de hinek pirsgirêk derketin. - Ji bo afirandina ajimêra xwe ya nû a WordPress.comê ji kerema xwe navnîşana epeyamê binivîse. - Di kontrolkirina navnîşana epeyamê de pirsgirêk çêbû.. - Li WordPress.comê Tomar Bibe - Bi navê xwe yê bikarhêner têkevê. - Bi navnîşana malperê xwe têkevê. - Pêwîstî hildemandinê heye + Tu her timî dikarî wekî berî niha bi girêdanekê têketinê bike lê heke tu bixwazî em dikarin pêborînekê jî ji te re saz bikin. + Ji bo tu hesabê xwe yê nû yê WordPress.com\'ê biafirînî, ji kerema xwe navnîşana emaila xwe bikevê. + Di kontrolkirina navnîşana emailê de hinek pirsgirêk derketin. + Li WordPress.com\'ê Tomar Bibe + Bi navê xwe yê bikarhêner têkeve. + Bi navnîşana malperê xwe têkeve. Li Pêvekan Bigere - - Populer - Lihevhatin tune ye - Hemûyan Bibîne - Bi rê ve bibe Lêgerîna pêvekan nayê kirin - Dema sazkirina %s ê şaşiyek pêk hat %s bi serkeftî hat sazkirin Saz Bike Biecibîne - Malpereke nû tevlê bike + + Populer Ji bo karsazî, kovar an jî bologeke şexsî malpereke nû biafirîne; an jî bi sazkirineke WordPressê ya heyî re girê bidin. + Hemûyan Bibîne + Lihevhatin tune ye + Divê bê hildemandin + Birêve bibe + Di sazkirina %s\'ê de çewtî derket + Malpera nû tevlî bike Ji bo agahiyên kêrhatî ji malpera te ya WordPressê bê amûra te, divê tu pêveka Jetpackê saz bike. Tu dixwazî Jetpackê saz bikî? Wêneyên dereng barkirinê Jetpack\'ê saz bike Nivîsê biguherîne Guhertoya WordPress\'a Te - Guhertoya WordPress\'ê ya Pêwîst - Hildema Dawîn + Guhertoya WordPress\'ê ya Hewce + Hildema Dawî Guherto 5 stêrk 4 stêrk 3 stêrk 2 stêrk stêrkek - Ti tişt nehat peydakirin %s daxistin %s puan Nirxandinan Bixwîne Pirsên Pir Tên Pirsîn - Çi Nû Ne + Ti tişt nehat peydakirin Sazkirin Daxuyanî + Çi Tiştên Nû Hene Sazkarî - Sazkirî Guhertaya %s hat sazkirin + Hat sazkirin Guherto %s ji aliye %s - Wêneyê biguherîne Pêvek nehatin barkirin + Wêneyê biguherîne Rûpelên Malperê - Etîketên malpera xwe birêve bibe Tê tomarkirin Tê jêbirin - Etîketa \'%s\' bi mayînde bê jêbirin? - Bi vê navê ji xwe etîketeke heye - Etîketeke Nû Tevlî Bike + Etîketên malpera xwe birêve bibe + Bila etîketa \'%s\' bi temamî bê jêbirin? + Jixwe bi vî navî etîketek heye Daxuyanî Etîket Malpera te ya WordPress.com piştgiriyê dide bikaranîna Rûpelên Mobîlê yên Lezandî ku peyaneke bi serokatiya Googleê ye û di amûrên mobîlan de mudeta barkirinê bibandorî dilezîne + Etîketa Nû Tevlî Bike Rûpelên Mobîlê yên Lezandî (RML) - Herêma demê nayê barkirin - Di derbarê dirûva(format) dem û dîrokê de zêdetir bizane - Dirûva taybet + Navçedem nehatin barkirin + Di derbarê formata dem û dîrokê de zêdetir bizane Taybet - Serê rûpelê, şandî + Formata taybet + Serê rûpelê şandî Bajarekî bibijêre li qada xwe ya demê - Qada demê - Dirûva Demê - Dirûva Dîrokê + Navçedem + Formata Demê + Formata Dîrokê Destpêka hefteyê - Nîşank Trafîk + Etîket Jê bibe Girêdana derveyî - Nîşaneka Pêvekê + Îkona pêvekê Serrûpela Pêvekan - WordPress.org Rûpela pêvekan + Rûpela pêvekan a WordPress.org\'ê Bi rastî jî tu dixwazî ji %2$s\'ê %1$s rake?\n\n\nEv, dê pêvekê neçalak bike û hemû dane û pelgeyên têkildar jê bibe. Pêvekê Rake - %s tê rakirin… - %s tê neçalakkirin… - Di pevsazandina pêvekê de çewtiyek pêk hat: %s - Di rakirina %s de çewtî pêk hat - %s biserkeftî hat rakirin - Di hildemandina %1$s de çêwtî pêk hat: %2$s - Çewtiya hildemandina %sê. - %s biserkeftî hat hildemandin + Tê rakirin %s… + Tê neçalakkirin %s… + Di pevsazandina pêvekê de çewtiyek derket: %s + Di rakirina %s\'ê de çewtî derket + %s bi serkeftî hat rakirin + Di hildemandina %1$s\'ê de çêwtî derket: %2$s + Di hildemandina %s\'ê de çewtî derket. + %s bi serkeftî hat hildemandin Guhertoya %s berdest e - Hildemandinên otomatîk + Hildemandinên otomatîkî Çalak Neçalak Çalak Pêvek Pêvek - Di hilpekîn/paceya nû de girêdanê veke + Di hilpeka/paceya nû de girêdanê veke Giredan: - Çewtiyek çêbû. + Çewtiyek derket. Ji kerema xwe bo domandinê kodeke rastandinê peyda bike. Ji kerema xwe bo domandinê pêborîna xwe du cara kontrol bike. Têketin rawestiya - Ji kerema xwe benda têketinê be. + Ji kerema xwe li benda têketinê be. Pêvojoya têketinê didome… - Bo domandinê bitepîne. + Ji bo domandinê, bitepîne. Têketin pêk hat! Têketina Googleê nehat destpêkirin. - Ji kerema xwe pêborînekê têxîne - Jê dêvla wê kodeke din ji min re bişîne + Ji kerema xwe pêborînekê bikevê + Ji dêvla wê kodeke din ji min re bişîne Em ji telefona te yê ku bi %s re diqede re peyameke nivîsî şandin. Xêra xwe koda rastandinê yê di SMSê de têkeve. Mezinahî - Pelgeyek maye Ji %2$d pelgeyan %1$d maye + Dosyeyek maye Şandiyek maye Tê hilxistin… - Şandî Binivîse + Nivîsekê Binivîse %d pelge biserkeftî hatin hilxistin - , %d biserkeftî hat hilxistin - Pelgeyek hat hilxistin - Pelgeyek nehat hilxistin - %d pelge hat hilxistin - %d pelge nehatin hilxistin + , %d bi serkeftî hat hilxistin + Dosyeyek hat hilxistin + Dosyeyek nehat hilxistin + %d dosye hatin hilxistin + %d dosye nehatin hilxistin Ji şandiyê rake Bila ev wêne ji şandiyê were rakirin? - Kesane bike - Hûrgiliyên Pelgeyê + Taybet bike + Hûrgiliyên Dosyeyê \nBelkî tu ajimêreke cuda biceribîne? Di girêdana bi ajimêra Googleê re hinek pirsgirêk pêk hatin. Bigire - Bi Googleê têkevê. + Bi Google\'ê têkeve. Ji bo bi vê ajimêra Googleê bidomînî, ji keram xwe peyvborîna li WordPress.comê peyda bikin. Ev ê careke bê pirsîn. Çewtiyeke girêdanê pêk hat. Ji kerema xwe girêdana xwe kontrol bike û dîsa biceribîne. Wêneyê bijarte rake @@ -1501,105 +1527,105 @@ Language: ku_TR Rastandina du-gavî ferz bike Bi bikaranîna epeyamê ajimêran hevber bike Destûrê bide bo têketina WordPress.comê - Têketina WordPress.comê + Têketina WordPress.com\'ê Navnîşanên IPê yên lîsteya spî Hewldanên têketinê ên niyetxirab asteng bike Parastina ji erişa Brute forceê Danezanên demkî bişîne - Danezanan bi epeyamê bişîne - Wextê xizmetê ya malpera xwe temaşe bike + Danezanan bi emailê bişîne Ewlehî - Sazkariyên Jetpackê + Dema kar a malpera xwe kontrol bike Tevlî vê dibe - Malper hilbijêre Tevlî medyagehê bike + Sazkariyên Jetpack\'ê + Malperê hilbijêre Tevlî şandiya nû bike Ji bo parvekirina wêne an jî vîdyoyan destûrên hewce. IP an navbera IPyê ya nederbasdar Tê jêbirin Bila ev vîdyo jê biçe? - Bila ev wêne jê biçe - Hûrgiliyên pelgeyê + Bila ev wêne jê biçe? Hûrgiliyên deng + Hûrgiliyên dosyeyê Hûrgiliyên vîdyoyê Hûrgiliyên wêneyê Pêşdîtin Dîroka Hilxistinê - Dirêjî Pîvanên Vîdyoyê + Dirêjî Pîvanên Wêneyê - Cureya Pelgeyê - Navê Pelgeyê URL + Cûreya Dosyeyê + Navê Dosyeyê Nivîsa alternatîv - Malperekê lê girêde Şewqê veke - bigire - Amûrê biricifîne - Deng bibijêre + Malperekê lê girêde + Cîhazê biricifîne + Deng hilbijêre Dîmen û deng - Epeyama ji WordPress.comê - Derbarê reşnivîsen li bendê min agahdar bike + Emaila ji WordPress.com\'ê + Di derbarê reşnivîsen hilwestî min agahdar bike Şîroveyên di malperên din de - Yên din Hemû Malperên Min + Ya din Malperên Te Girtina Sazkariyên Danezanan wê hemû agahdariyên ji vê sepanê neçalak bike, bêyî dîqetkirina cureya danezanê. Piştî ku te Sazkariyên Danezanan vekir, tu yê karibî agahdariyên ku tu dixwazî werbigirî bisazînî. - Ji bo di vê amûrê de danezanan bistîne, Sazkariya Danezanan biçalakîne. + Ji bo di vê cîhazê de danezanan bistînî, Sazkariya Danezanan biçalakîne. Danezanan biçalakîne Danezanan neçalak bike Girtî Vekirî Mezinahiya Vîdeoyê ya Maksîmum Mezinahiya Wêneyê ya Maksîmum - Gava medya li vê şandiyê dihate hilxistin çewtiyeke derket holê: %s. - Gava ev şandî dihate hilxistin çewtiyeke derket holê: %s. + Dema ku medya li vê şandiyê dihat hilxistin çewtiyek derket: %s. + Dema ku ev şandî dihat hilxistin çewtiyek derket: %s. Medya tê tevlîkirin Ji bo zêdetir, seredana %s\'ê bike - Rûpel di rajekarê de nehat dîtin + Rûpel di serverê de nehat dîtin Rûpela vala nayê weşandin - Hilxistin ji bo \"%s\" bi serneket - Medya hate rakirin. Bila ji vê şandiyê biçe? - Di vekirina geroka webe de çewtî pêk hat. Ji kerema xwe sepaneke din bibijêre: - Girêdan venabe - Şandî di rajekarê de nehat dîtin - Êdî ev şandî ne li berdest e - Hilxistina medyayê hate betalkirin + Hilxistina ji bo \"%s\"ê bi ser neket + Medya hat rakirin. Bila ji vê şandiyê bê rakirin? + Di vekirina geroka webê ya jixweberî de çewtî derket. Jkx, sepaneke din hilbijêre: + Girêdan nayê vekirin + Şandî di serverê de nehat dîtin + Êdî ev şandî ne berdest e + Hilxistina medyayê hat betalkirin Hilxistina medyayê di vê rûpelê de rastî çewtiyekê hat: %s. Di hilxistina vê rûpelê de çewtiyek pêk hat: %s. Şandiya te tê hilxistin - Medya tê hilxistin + Medya tê hilxistin… Rûpel hat demsazkirin Şandî hat demsazkirin Dîsa biceribîne - Şandiya rêzkirî + Şandî ket dorê \"%s\" tê hilxistin Girêdana bi rajakerê re qut bû - Malperên min Malpera min - Sepana te ya epeyam-wergir bikare were tesbîtkirin + Malperên min Ji kerema xwe kodeke rastandinê têxîne + Sepana te ya epeyam-wergir bikare were tesbîtkirin Ji kerema xwe navekî bikarhêner têxîne Ji bo bigihêje şandiyê di WordPress.comê de têketinê bike. Dema malper dihat tevlîkirin çewtî çêbû. Koda çewtiyê: %s Navnîşana malperê tê kontrolkirin - Alîkariya zêdetir pêwîst e? Malpera te li benda jor a ekranê wê bê xuyan gava ku te di Chrome-ê de malpera xwe seredan kir. + Alîkariya zêdetir hewce ye? Navnîşana Malpera min çi ye? - Ji bo dîtina navnîşana malpera te alîkarî hewce ye? + Ji bo tu navnîşana malpera xwe bibînî alîkarî lazim e? Navnîşana malperê Navnîşana Malpera WordPressê ya ku tu dixwaze pê gire bide têxîne. Jixwe di WordPress.comê de têketin pêk hatiye Bidomîne Malpereke din girêde - Pêborîna xwe ya WordPress.comê têxîne. + Pêborîna xwe ya WordPress.com\'ê bikevê. Xwestina emaila têketinê Wisa dixuye ku ev pêborîn xelet e. Ji kerema xwe agahiyên xwe du caran kontrol bike û careke din biceribîne. Xwestina kodeke rastandinê bi riya SMSê. Jê dêvla wê kodekê ji min re bişîne Hema bêje temam bû. Xêra xwe koda rastandinê têkeve ji sepana xwe ya rastkar. - Epeyamê veke Pêşve Ji bo ku tu hemû malperên xwe yên WordPressê bi rê ve bibî di WordPress.com\'ê de bi navnîşaneke emailê têketinê bike. + Emailê veke Optîmîzasyona wêneyan, wêneyan biçûk dike ji bo hilxistina zûtir\nTu herdem dikarî vê biguherînî ji sazkariyên malperê. Bila optimizasyona wêneyê vebe? Girtî bihêle @@ -1607,14 +1633,14 @@ Language: ku_TR Wêneyê Profîlê Cewaba nehêvîkirî ji rajekarê Hilxistin nayê rawestandin ji ber ku ji zû ve qediya ye - Sernav - Dubare bike Vegerîne + Dubare bike + Sernav Ji bo nîşandanê medya pir biçûk e Hişyarî: Hemû hêmanên ku hatine hiştin nayên piştgirîkirin! Destûr nayê dayîn ku wêne li Serikê bêne danîn - Çewtiyeke derket holê gava danîna nivîsê Di moda HTMLê de destûra hiştina wêneyan nayê dayîn + Çewtiyeke derket holê gava danîna nivîsê Çîroka xwe li vir parve bike… Taybet Reşnivîs @@ -1625,22 +1651,22 @@ Language: ku_TR Bijêra Lokasyonê nikare veke, Xizmetên Google Playê ne berdest e Cîgeh Aniha - Tenê kesên xwediyê vê pêborînê karin vê şandiyê bibînin + Tenê kesên xwediyê vê pêborînê dikarin vê şandiyê bibînin Kurte, kurteyên naveroka te yên ku bi destan hatiye amadekirin e. Kurtenav versiyona URL-hez ya serikê şandiyê ye. - Dirûva Şandiyê - Etîket Kurtenav Kurte + Etîket + Formata Şandiyê Nesazkirî - Vebijêrkên Zêdetir - Kategorî & Etîket + Vebijarkên Zêdetir + Kategorî û Etîket Hemû - Asta jor - Beşa sereke (li gor daxwazê): + Asta bilind + Kategoriya sereke (li gor daxwazê): Ti dosyeya te ya dengî tune ye - Ti dosyeyên te tune ye Hîç vîdyoyên te tune ye + Ti dosyeyên te tune ye Hîç wêneyên te tune ye Serverê pir dereng bersiv da Ji bo li hilxistina vê malperê pelge pir mezin e @@ -1649,9 +1675,9 @@ Language: ku_TR Wêne pir mezin e bo hilxistinê. Biceribîne ku ji sazkariyên sepana xwe vebijêrka \"Wêneyan Optîmîze Bike\" biguherînî Deng Vîdyo - Pelge Wêne Hemû + Dosye %1$s red kir ku bigihe wêneyên te. Bo çarekirina vê, destûrên xwe biguherîne û %2$s veke. Şîroveyan bibîne Qalîteya vîdyoyan. Nirxên bilindtir tê wateya vîdyoyên qalîteya çêtir. @@ -1660,54 +1686,54 @@ Language: ku_TR Vîdyoyan Optîmîze Bike Reşnivîs hate hilxistin Çewtî ragihîne - Qalîteya Vîdyoyê + Kalîteya Vîdyoyê Kamera - Bîrbar + Bîrgeh Destûran serast bike Destûr bide Hewcedariya %s bi destûra te heye bo gihîna wêneyên te Wê ev tevlî nava twîtan bibe gava ku însan bi bişkoka Twîtterê parve kirin Nivîsa bişkokên parvekirinê biguherîne. Ev nivîs wê neyê xuyan heta ku te bi kêmanî yek bişkokek parvekirinê lê zêde kir. - Ajimêr tê girêdan + Hesab tê girêdan Girêdana %s nikare çêbe ji ber ku ti ajimêrek nehatiye bijartin. - Hat girêdan Twitter Ecibandin Destûr bide ku hemû şîrove ji hêla te û xwendevanên te ve were ecibandin - Bişkok + Hat girêdan Bişkokên \"Bêhtir\" serast bike Bişkokeke \"Zêdetir\" daketokekê dihewîne ku ew bişkokên parvekirinê nîşan dide + Bişkok Bibijêre ku ka bila kîjan bişkok bêne xuyan li bin şandiyên te - Navê bikarhêner yê Twitterê Ecibandinên şîroveyê - Şêwaza Bişkokê - Lêgerîn bo: + Navê bikarhêner ê Twitterê Bişkokên parvekirinê Bişkoka Ecibandinê Nîşan Bide + Etîket + Stîla Bişkokê Bişkoka Dîsblogê Nîşan Bide - Dîsblog û Ecibandin + Dîsblog bike û Biecibîne Bişkokên Fermî Tenê Nivîs - Tenê Nîşanek - Nîşanek û Nivîs - Ajimêra ku tu dixwazî destûrdar bikî bibijêre. Bi bîr bixe ku şandiyên te wê li ajimêrên bijartî bi otomatî werin parvekirin. - Girêdan bi %s re pêk tê + Tenê Îkon + Îkon û Nivîs + Ajimêra ku tu dixwazî destûrdar bikî hilbijêre. Bi bîr bixe ku şandiyên te wê li ajimêrên hilbijartî bi otomatîk werin parvekirin. Bila girêdan ji %s qut bibe? - Ajimereke din gire bide + Bi %s ve tê girêdan + Hesabekî din girêde Dîsa girêde Girêdanê qut bike - Girêdan Ji bo ku tu şandiyên blogê yên li ser %s otomatik parve bikî, gire bide. - Ji bo %s belav bike - Ajimêrên Girêdayî + Girêde + Li ser %sê belav bike Servîsên medyayên civakî yên kêfa te ji wan re tê gire bide û bila bi awayekî otomatik şandiyên nû bi hevalên te re were parvekirin. + Hesabên girêdayî Girêdan Parvekirin Danezan. Danezanên xwe bi rê ve bibe. Xwîner. Ji malperên din naverokan bişopîne. - Malpera Min. Malpera xwe bibine, bi rê ve bibe û li rêjejimaran meyzîne. + Malpera Min. Malpera xwe bibîne, bi rê ve bibe û amaran kontrol bike. Parvekirin - Ne aniha + Ne niha Çewtiya hilxistinê. Ji sazkariyên sepana xwe guhertina vebijêrka \"Wêneyan Optîmîze Bike\" biceribîne Medya di vê amûrê de tê tomarkirin Medya nayê tomarkirin @@ -1718,13 +1744,13 @@ Language: ku_TR Ji bo pirtir şîroveyan hilbijêre, bitepîne û li ser bihêle Ji amûrê vîdyo bibijêre Ji amûrê wêne bibijêre - Medyayên WordPressê - Wekî pêşangeh tevlî bike Yekane tevlî bike + Medyayên WP\'ê + Wekî galerî tevlî bike Wêneyan bi piranî tevlî bike %d stûn Stûnek - Epeyamê dîsa bişîne + Emailê dîsa bişîne Em epeyamekê şandin %s, gava ku te cara pêşîn têket. Xêra xwe peyamê vebike û bitikîne bişkoka şîn, ji bo çalakkirina weşandinê. Em epeyamekê şandin te, gava ku te cara pêşîn têket. Xêra xwe peyamê vebike û bitikîne bişkoka şîn, ji bo çalakkirina weşandinê. Ji kerema xwe navnîşana epeyama xwe bipejirîne @@ -1741,41 +1767,41 @@ Language: ku_TR Bi malpera %s re dest pê bike Di rakirina malperê de çewtî çêbû, piştre dîsa biceribîne Şandî bi ser neket ku medyayê hilbixe û wek herêmî hate qeydkirin - Bila ji sepanê ev malpera rabe? + Bila ev malper ji sepanê bê rakirin? Wêne hilbijêre - Cîhaz derhêl e. Şandî cîgehî hat tomarkirin. - Peyam bi serhêl hat tomarkirin. - Qalîteya wêneyan. Nirxên bilindtir tê wateya wêneyên qalîteya çêtir. + Cîhaz derhêl e. Şandî bi cîgehî hat tomarkirin. + Şandî bi serhêlî hat tomarkirin + Kalîteya wêneyan. Nirxên bilindtir tê wateya wêneyên kalîteya wan çêtir. Verehandin û dewisandina wêneyan, biçalakîne Herî bilind Pir bilind Bilind Navîn Nizm - Hilxistî - Hilxistin bi Serneket + Hat hilxistin Hat jêbirin Tê jêbirin Tê hilxistin Di dorê de - Qalîteya Wêneyê - Ji ber sedema çewtiyeke nediyar hilxistina hemû medayayan betal bû. Ji kerema xwe hilxistinê dîsa biceribîne - Dirûva şandiya nenas + Hilxistin bi ser neket + Kalîteya Wêneyê + Ji ber çewtiyeke nenas hilxistina hemû medayayan hatin betalkirin. Ji kerema xwe hilxistinê dîsa biceribîne + Formata şandiya nenas Bişîne - Malpereke dubarekirî hate tesbîtkirin - Ev malper jixwe heye di sepanê de, tu nikarî lê zêde bikî. + Malpereke dubareyî hat tesbîtkirin. + Jixwe ev malper di sepanê de heye, tu nikarî wê tevlê bikî. Te jixwe têketiye ajimêreke WordPress.comê, tu nikarî malpereke WordPress.comê yê ku girêdayî ajimêke din e lê zêde bikî. Medya nayê barkirin Ji bo nûkirina medyagehê înternet hewce ye - Ji bo dîtin an jî serastkirina medyayê destûra te nîn e + Ji bo tu medyayê bibinî an jî wê sererast bikî îzina te tune ye Medya nehat dîtin Ser medyaya amûrê destûra xwendinaê hate redkirin Wêneyan optîmîze bike - Çewtiya medyayê qewimî - Çewtiya hilxistinê ya medyayê qewimî - Dîsa biceribîne + Çewtiya medyayê derket + Çewtiya hilxistina medyayê derket Bipejirîne Nikare were girêdan. Em çewtiya 403 wergirtin gava ku diceribandin\n xwe bigihînin nuqteya dawîn ya XMLRPC. Hewcehiya sepanê pê heye da ku bi malpera te re têkiliyê deyîne. Bo çareserkirina vê pirsgirêkê\n têkiliyê deyîne bi hewandera xwe re. + Dîsa biceribîne Nikare were girêdan. Hewandera te daxwazên POSTê asteng dike, \n û hewcedariya sepanê pê heye ji bo ku bi malpera te re têkiliyê deyîne. Ji bo çareserkirina vê pirsgirêkê bi pêşkêşkera hewangeha xwe re têkiliyê deyîne. Li malperên şopandî bigere Wê daliqandî nehêle! \'%1$s\' li benda weşandinê te. @@ -1796,12 +1822,12 @@ Language: ku_TR Şîrove nayê bersivandin. Ji kerema xwe piştre dîsa biceribîne. Şîrove nayê pejirandin. Ji kerema xwe piştre dîsa biceribîne. Şîrove nayê ecibandin. Ji kerema xwe piştre dîsa biceribîne. - Ji bo wan nîşan bide, bitikîne + Ji bo nîşandanê, bitikîne Danezanên nû Tê bersivandin… Tê pejirandin… Tê ecibandin… - Tê xebitandin… + Tê hêran… Çalakî qediya! Şîrove hat ecibandin Derkeve @@ -1809,32 +1835,32 @@ Language: ku_TR Zêdetir li ser WordPress.com\'ê Zêdetir li ser %s\'ê Sazkariyên cîhazê veke - %s: Emaila nederbasdar %s: Vexwendinên bikarhêner astengkirî + %s: Emaila nederbasdar %s: Jixwe tê şopandin - %s: Jixwe endamek e %s: Bikarhêner nehat dîtin + %s: Jixwe endamek e Şîrove hat pejirandin! Biecibîne Aniha Bîner Şopîner - Girêdan tune ye, profîla te nehat tomarkirin + Înternet tune ye, profîla te nehat tomarkirin Rast Çep Hîç %1$d hat hilbijartin Bikarhênerên malperê nehatin stendin - Şopînerê Emailê Şopîner Bikarhêner tên stendin… + Şopînera Emailê Bîner Şopînerên Emailê Şopîner - Kom - Heya 10 heb navnîşanên emailê û/an jî bikarhênerên WordPress.com\'ê, vexwîne. Ji bo kesên hewcedariya wan bi navê bikarhêner heyî re di derbarê afirandinê de ew ê rêwerz were şandin. - Heke tu vî/vê bînerî/ê rakî êdî ew dê nikaribî seredana vê malperê bike.\n\nDîsa jî tu dixwazî vî/vê binerî/ê rake? - Heke ev şopîner were rakirin dê di derbarê vê malperê de danezanan nestîne, heta ku dîsa bişopînî.\n\nDîsa jî tu dixwazî ev şopîner were rakirin? + Tîm + Heta 10 heb navnîşanên emailê û/an jî bikarhênerên WordPress.com\'ê, vexwîne. Ji bo kesên ku hewcedariya wan bi navê bikarhêneriyê hene re di derbarê afirandinê de ew ê rêwerz were şandin. + Heke tu vî bînerî rakî êdî ew ê nikaribe seredana vê malperê bike.\n\nTu dîsa jî dixwazî vî binerî rakî? + Heke ev şopîner were rakirin ew ê danezanan di derbarê vê malperê de nestîne, heta ku dîsa bişopînî.\n\nTu dîsa jî dixwazî vî şopînerî rakî? Ji %1$s\'an vir de Bîner nayê rakirin Şopîner nayê rakirin @@ -1845,7 +1871,7 @@ Language: ku_TR Giredan Binnivîs Guhertin hatin tomarkirin - Bila guhertinên netomarkirî were avêtin? + Bila guhertinên netomarkirî bên avêtin? Bila hilxistin raweste? Ji bo dîsa biceribîne, bitepîne Vexwendin bi serkeftî hat şandin @@ -1853,62 +1879,62 @@ Language: ku_TR Vexwendin hat şandin lê hin(ek) çewtî rû dan! Dema vexwendin dihat şandin çewtiyek çêbû! Nikare bişîne: Epeyam an jî navên bikarhêneran nederbasdar e - Nikare bişîne: Navek an jî epeyamek nederbasdar e - Ji kerema xwe herî kêm navekî bikarhêner tevlî bike + Nikare bişîne: Navek an jî email nederbasdar e + Ji kerema xwe herî kêm navê bikarhênerekî tevlî bike (Girêdayî daxwazê) Tu dikarî peyameke xisûsî têkevî heya 500 karakteran, ku ev wê tevlî vexwendina bo bikarhêner(an)ê were kirin. %d karakter mane Karakterek maye 0 karakter maye Peyama Taybet Vexwîne - Epeyam an jî navên bikarhêneran + Email an jî navê bikarhêneran Mirovan Vexwîne Derveyî Girêdanê bişîne Raboriya lêgerînê paqij bike Raboriya lêgerînê paqij bike? Di zimanê te de ji bo \"%s\"ê ti encam nehatin dîtin - Li ser WordPress bigere + Li ser WordPress\'ê bigere Girêdanên li ser ekrana pêşdîtinê neçalak in Bişîne %1$s bi serkeftî hat rakirin - Heke tu bikarhêner %1$s rake, ew dê nikaribe bigihêje vê malperê lê naverokên ji hêla %1$s wî/ê hatiye afirandin dê li malperê bimîne.\n\nDîsa jî tu dixwazî vî/ê bikarhênerî/ê rake? + Heke tu %1$s rakî, ew ê êdî nikaribe bigihîje vê malperê, lê naverokên ji hêla %1$s ve hatine afirandin ew ê li ser malperê bimînin.\n\nTu dîsa jî dixwazî vî bikarhênerî rakî? %1$s rake Rol Kes - Malperên di vê lîsteyê de vê gavê ti şandî nekirine + Malperên di vê lîsteyê de vê gavê ti şandî neweşandine Bikarhêner nayê rakirin Rola bikarhêner nayê hildemandin Bînerên malperê nehat stendin - Di hildemandina Gravatara te de çewtî çêbû - Di ji nû ve hildemandina Gravatara te de çewtî çêbû - Di bicîhkirina wêneyên qusandî de çewtî çêbû - Di qusandina wêneyê de çewtî çêbû - Epeyam tê kontrolkirin - Niha ne li berdest e. Ji kerema xwe pêborîna xwe têxîne + Di hildemandina Gravatara te de çewtî derket + Di nûkirina Gravatara te de çewtî derket + Di bicîkirina wêneyên qusandî de çewtî derket + Di qusandina wêneyê de çewtî derket + Email tê kontrolkirin + Niha ne guncav e. Ji kerema xwe pêborîna xwe bikevê Têketin pêk tê - Li şûnê pêborîna xwe têxîne + Ji şûna wê pêborîna xwe bikevê Dema te şîrove kir wê her kes bibîne - Wêne hilbijêre an jî bikşîne + Wêne hilbijêre an jî bigire Plan Plan - Rûpel, şandî û sazkariyên te dê ji navnîşana %s re bi epayamê were şandin. - Naverokên xwe derxîne derve - Epeyama derxistinê hat şandin! + Rûpel, şandî û sazkariyên te ew ê bi emailê ji ser %s\'ê ji te re bên şandin. + Naverokên xwe derxîne + Emaila derxistinê hat şandin! Naverok tê derxistin… - Kirînên te tê kontrolkirin Kirînan nîşan bide Li ser malpera te bilindkirinên premium yên çalak hene. Ji kerema xwe berî tu malpera xwe jê bibî bilinkirinên xwe betal bike. + Kirînên te tên kontrolkirin Bilindkirinên Premium Hinek tişt çewt çû. Daxwazên kirînan pêk nehat. Malper tê jêbirin… - Malperê jê bibe? - Malpera xwe wekî pelgeya XML derxe - Navpera Sereke + Malperê jê bibe + Malpera xwe wekî dosyeya XML\'ê derxîne + Navpera (Domain) Sereke Dema malpera te dihat jêbirin çewtiyek çêbû. Ji kerema xwe bo zêdetir alîkariyê bi timên alîkariyê re têkilî deyne. Di jêbirina malperê de çewtî - Naverokê derxe - Ji kerema xwe bo pejirandinê di qasa jêrîn de %1$s binivîsîne. Malpera te dê piştî vê heta hetayê winda bibe. + Ji kerema xwe bo pejirandinê di qasa jêrîn de %1$s binivîsîne. Malpera te dê piştî vê bi hemîşeyî winda bibe. + Naverokê derxîne Jêbirina Malperê Bipejirîne Têkîlîya Alîkariyê Tu malpereke nû bixwazî, lê nexwazî ku ti rûpel û şandiyeke te ya niha tê de nîn bin, tîma me ya alîkariyê dikare ji bo te şandî, rûpel, medya û şiroveyên te jê bibe.\n\nEv wê malpera te û URLya te aktîv bigire, lêbelê ji bo te destpêkek teze bide ji bo çêkirina naverokan. Tenê bi me re têkiliyê deyîne ji bo paqijkirina hemû naverokên xwe yên vê gavê. @@ -1916,30 +1942,30 @@ Language: ku_TR Ji serî ve dest bi malpera xwe bike Ji serî ve dest pê bike Sazkariyên Sepanê - Hilxistinên bi serneketî rake + Hilxistinên serneketî rake Pêşketî - Şîroveyên hatine jêbirin tune ye - Şîroveyên hilawîstî tune ye + Di jêbirdankê de şîrove tune ye + Şîroveyên hilawestî tune ye Şîroveyên pejirandî tune ye - Girêdan pêk nehat. Rêbazên XML-RPC yên pêwist li ser serverê kêm in. - Navîn bike + Girêdan pêk nehat. Fonksiyonên XML-RPC yên pêwîst di serverê de kêm in. Vîdyo Rewş - Standard - Jêgirtin + Navend Girêdan Wêne - Pêşangeh Gotûbêj + Standard + Jêgirtin + Galerî Deng Kêlekbend - Agahdariya li ser qurs û çalakiyên WordPress.comê (serhêl û şexsî). - Derfetên beşdarbûnê yên ji bo lêkolîn û vekolîna WordPress.comê. + Agahdariya li ser kurs û çalakiyên WordPress.comê (serhêl û şexsî). + Derfetên beşdarbûnê yên ji bo lêkolîn û vekolîna WordPress.comê. Bendên ji bo fêdedîtina ji WordPress.comê. Civak Lêkolîn Pêşniyar - Bersivên li ser şîroveyên min + Bersivên şîroveyên min Qalkirinên li ser navê bikarhêner Serkevtinên malperê Şopandinên malperê @@ -1949,8 +1975,8 @@ Language: ku_TR %d hêman Hêmanek Hemû bikarhêner - Şîroveyên bikarhênerên naskirirî - Şîrove nîn e + Şîroveyên bikarhênerên navnas + Şîrove tune ye Serê rûpelê %d şîrove Serê rûpelê şîroveyek Zêdeyî %d girêdanan be pejirandin pêwîst e @@ -1963,9 +1989,9 @@ Language: ku_TR Rojek Navnîşana webê Malpera sereke - Ji bo navnîşana nû ya te were pejirandin, li girêdana rastandinê ya ji epeyama %1$s re hatiye şandin bitikîîne - Vê gavê tu medya hldixîne. Ji kerema xwe heta qedandinê bisekine. - Şîrove vê gavê nayê nûkirin - şîroveyên kevn tên nîşandan + Ji bo navnîşana te ya nû were pejirandin, li girêdana rastandinê ya ji emaila %1$s re hatiye şandin, bitikîne + Tu niha medyayê hildixîne. Ji kerema xwe heta biqede bisekine. + Şîrove vê gavê nayên nûkirin - şîroveyên kevn tên nîşandan Wêneya bijarde saz bike Wêneya Bijarde Navnîşana emailê @@ -1974,39 +2000,39 @@ Language: ku_TR Jê bibe Vegerîne Şîrove hat jêbirin - Şîroveyên nexwestî nîn e + Şîroveyên spam tune ye Hemû Rûpel nayê barkirin Girtî Zimanê Navrûyê Derbarê sepanê - Sazkariyên ajimêra te nehat tomarkirin - Sazkariyên ajimêra te nehat stendin + Sazkariyên hesabê te nehat tomarkirin + Sazkariyên hesabê te nehat stendin Profîla te nehat stendin - Koda ziman nehat naskirin + Koda zimên nehat naskirin Destûr bide ku şîrove di nav hev de wek deziyan bêne rêzkirin Hejmara meztirîn yê deziyan Neçalak - Lêgerîn Rake - Mezinahiya Resen + Lê bigere + Mezinahiya Orijînal Malpera te tenê ji bo te û bikarhênerên ku te pejirandî ye xuya ye Malpera te ji her kesî re tê xuyan lê ji motorên lêgerînê daxwaz dike ku ew neyê navnîşkirin Malpera te ji her kesî re tê xuyan û dibe ku hatibe navnîşkirin ji aliyê motorên lêgerînan ve Çend gotin di derbarê te de… - Derbarê min de Ger nehatibe sazandin, navê berdest wê bibe navê jixweber + Derbarê min de Navê xuyabûnê ya giştî Paşnav Nav Profîla Min Risma pêşdîtina şandiyê ya eleqedar - Nebû ku agahiyên malperê qeyd bike - Nikare agahiyên malparê werbigire. + Agahiyên malperê nehat tomarkirin + Agahiyên malperê nehat standin Bi otomatîkî bigire - Şiroveyên ser gotaran bi otomatîkî bigire + Şîroveyên li ser gotaran bixweber bigire. Zincîreyên şiroveyan li nava rûpelên pirhejmar belav bike - Şiroveyên bo her rûpelek + Serê rûpelê şîrove Şirovekirinê bigire Gava şiroveyek di naverok, nav, URL, e-name, an jî IPya xwe de yek ji van peyvan bihewîne, wê ew wek nexwestî (spam) were nîşankirin. Tu dikarî peyvên qismî têkevî, wek \"press\", wê ew bibe \"Wordpress\". Gava şiroveyek di naverok, nav, URL, e-name, an jî IPya xwe de yek ji van peyvan bihewîne, wê ew şirove bigirin nav dûvika teftîşê. Tu dikarî peyvên qismî têkevî, wek \"press\", wê ew bibe \"Wordpress\". @@ -2015,7 +2041,7 @@ Language: ku_TR Tu dikarî van sazkariyan ji bo şandiyên taybet bêhukm bikî di \"Bilindkirin\"ê de Nîvenda Bilindkirinê: Videopress ji bo Zewacan - di \"Sepan\"ê de + di \"Sepanan\" de Sepana WordPressê ya bo Androîdan Ji Aliyê Xuyaniyê ve Gelekî Bi Pêş Ket di \"Mobîl\"ê de Hildemandina Mezin ya iPhone/iPadê Vêga Hazir e @@ -2040,101 +2066,101 @@ Language: ku_TR Hemû sazkariyên berdest yên Gotûbêjê nîşan bide Nîşan bide an biveşêre şandiyên eleqedar di xwînerê de Mezinahiya wêneyên di şandiyê de dike bi qasî vê mezinahiyê - Sazkariyên dirûva şandiyên nû - Sazkariyên kategoriya şandiyên nû - Pêborîna xwe bigherîne - Ajimêra bikarhêner ya derbasdar + Formata şandiya nû saz dike + Kategoriya şandiya nû saz dike + Pêborîna xwe biguherîne + Hesabê bikarhêner ê niha Zimanê sereke yê ev blog pê hatiye nivîsîn - Kontrol dike ku kî dikare malpera te bibîne + Kesên dikarin malpera te bibînin kontrol dike Guherandina navnîşana te vê gavê nayê piştgirîkirin Danasîneke kurt an gotineke cazib yê ku bloga te dide naskirin Bi hin peyvan, îzah bike ku ev malper derbarê çi de ye Şîroveyên ji bikarhênerên nas Şîroveyên ji hemû bikarhêneran - asta %d + asta %d Taybet Veşartî - Giştî Malperê jê bibe + Gelerî Reşlîste Bisekinîne bo Kontrolê Girêdanên di şîroveyan de - Otomatîk bipejirîne + Bi otomatîkî bipejirîne Rûpelkirin Zincîrkirin Rêz bike Divê bikarhêneran têketin kiribin - Divê epeyam û nav vehewîne + Divê nav û emailê vehewîne \"Pingback\"an werbigire \"Pingback\"an bişîne - Destûr bide şîroveyan - Dirûva Jixweberî + Destûrê bide şîroveyan + Formata Jixweberî Kategoriya Jixweberî Navnîşan Rêza etîketê Sernavê Malperê - Ji bo şandiyên nû jixweberî + Jixweberiyên ji bo şandiyên nû Nivîsîn Hesab Giştî Yekema herî nû - Yekema herî kevn + Nihênî Piştre bigire Şîrove Şandiyên Têkildar - Nihênî - Nîqaş + Yekema herî kevn + Gotûbêj Destûra te nîn e ji bo hilxistina medyayê ya li malperê Nenas Oet Ev şandî êdî ne berdest e Bo xuyakirina vê şandiyê destûra te tune - Ev nivîs cardin nayê anîn + Ev şandî nayê anîn Malperên tu dişopîne di vê dawiyê de ti şandî nekirine - Nivîsên nû tune + Şandiyên nû tune ye URL li rûnûsgehê hat kopîkirin Rûkara Hilbijartî - Rûkar nayê hilbarkirin - Hinek tişt çewt çû. Rûkar nayê çalakkirin. - ji aliyê %1$s - Ji bo bijartina %1$s spas + Rûkar nayê barkirin + Hinek çewtî derketin. Rûkar nayê çalakkirin + ji hêla %1$s ve + Spas ji bo bijartina %1$s\'ê + Hûrgilî MALPERÊ BI RÊ VE BIBE - QEDIYA Piştgirî - Hûrgilî Xuyang - Biceribîne & Kesane Bike - Çalak bike - Çalak - Piştgirî + QEDIYA + Biceribîne & Taybet Bike + Biçalakîne Hûrgilî - Kesanekirin + Piştgirî Rûkara Derbasdar - Rûpel hate hildemandin - Şandî hate hildemandin - Rûpel hate weşandin - Şandî hate weşandin + Taybet bike + Biçalakîne + Rûpel hat hildemandin + Şandî hat hildemandin + Rûpel hat weşandin + Şandî hat weşandin Bibore, rûkar nehatin dîtin. - Şandiyên bêhtir bar bike + Şandiyên zêdetir bar bike \'%s\' û tu malper nehat li hev Li malperan bigere - Biçe Xwînerê - Ji bîr neke: şandiyên hatine xwendin şîrove bike - Beşdarî gotûbêjekê bibe: şandiyên blogan şîrove bike + Here Xwînerê + Ji bîr neke: şandiyên ku te xwendine şîrove bike + Beşdarî gotûbêjan bibe: şandiyên blogan şîrove bike Gotûbêjan gûr bike: şandiyeke nû binivîse - Çalak be! Li şandiyên di blogên tu dişopîne de şîrove bike - Hîn ti şîrove tune ne + Çalak be! Li şandiyên di blogên tu dişopînî de şîrove bikî. + Hîn ti şîrove tune ye Hemû hildem in! %s Şîrove Şîroveyek - Şandiyê bibersivîne - Serdana %s bike - Eslen li ser %s hate weşandin - Eslen ji aliyê %s ve hate weşandin - Eslen ji aliyê %1$s ve li ser %2$s hate weşandin + Şandiyê bibersivîne… + Serdana %s\'ê bike + Bi awayekî orijînal li %s\'ê hat weşandin + Bi orijînalî ji aliyê %s ve hat weşandin + Bi orijînalî ji aliyê %1$s ve li %2$s\'ê hate weşandin %s Ecibandin - Ecibandinek Biecibîne + Ecibandinek %,d Şopîner Malper û etîketan sererast bike Şandiya Xwînerê @@ -2143,291 +2169,290 @@ Language: ku_TR Sazkariyên danezanan ên di hilpeka Danezanan de dixuyin. Danezanên sepanê hatin neçalakkirin. Bitepîne vir ji bo çalakkirina wan di Sazkariyan de Cûreyên Danezanan - Sazkariyên danezana nehat barkirin + Sazkariyên danezanan nehat barkirin Ecibandinên şîroveyê - Danezanên Sepanê - Emaîl Hilpeka danezanan + Danezanên sepanê + Email Em ê hertim epeyamên girîng bişînin te yên derbarê ajimêra te de, lê belê tu yê karibî hin zêdekên alîkar jî werbigirî. - Kurteya Şandiya dawî - Girêdan tune - Şande hate şandin jêbirdankê - Bavêje jêbirdankê - Amar - Pêşdîtin + Kurteya Şandiya Dawî + Înternet tune ye Bibîne - Biweşîne + Pêşdîtin + Amar Sererast bike - Bo gihîna vê malperê destura te tune + Şande hate şandin bo jêbirdankê + Jêbirdank + Biweşîne + Ji bo gihana vê malperê destûra te tune Ev malper nehat dîtin Vegerîne Daxwaza te demborî bû. Ji bo ku cardin biceribînî têkeve WordPress.comê. Piştguh bike Dîtinên Çêtirîn ya Heya Vêga - Rêjejimarên Îro - Şandî, dîtin û serlêderên bo hemû deman + Amarên Îro + Şandî, dîtin û mêvanên hemû deman Kûrbînî - Ji WordPress.comê derkeve - Têketinê di WordPress.com de bike + Ji WordPress.com\'ê derkeve Têketin/Derketin + Têkeve WordPress.com\'ê + \"%s\" nehate veşartin çimkî ew malpera berdest e Alîkarî & Piştgirî Sazkariyên Hesêb - \"%s\" nehate veşartin çimkî ew malpera berdest e - Malpera WordPress.com Biafirîne + Malpera WordPress.com\'ê biafirîne Malpereke xwe-hewander tevlî bike - Malpera nû tevlê bike - Maperan nîşan bide/veşêre - Malper hilbijêre + Malpera nû tevlî bike + Maperan veşêre/nîşan bide + Malperê Biguherîne Malperê Bîbîne Revebir Bibîne - Malperê Biguherîne + Malperê hilbijêre + Biweşîne Sazkarî Şandiyên Blogê - Biweşîne - Xuyanî û Şêwaz + Xuyang û Şêwaz Pevsazî - Bo nîşandanê bitepîne - Temamiya bijartinan rake - Teva bijêre + Ji bo wan nîşan bide, bitikîne Veşêre Nîşan bide - Bo domandinê dîsa têkevê - Koda rastandinê ya nederbasdar - Koda rastandinê + Hemû bijartinan rake + Hemûyan hilbijêre Ziman - Nikare şandiyan cardin bîne - Weşanger: - Danezan nehat vekirin - Têgehên Lêgerînê yên Nenas - Têgehên Lêgerînê + Koda rastandinê + Ji bo dewamkirinê, dîsa têkeve. + Koda rastandinê nederbasdar e Nivîskar + Danezan nehat vekirin + Weşanger: Şîrove tên stendin… - Rûpel tên stendin + Şandî nayên vegerandin + Têgihên Lêgerînê yên Nenas + Têgihên Lêgerînê + Şandî tên stendin… Şandî tên stendin… Medya tê stendin… - Tomarên sepanê li rûnûsgehê hatin kopîkirin + Dema nivîs li rûnûsgehê dihat kopîkirin çewtî pêk hat Ev malper vala ye Şandiyên nû - Malperên dibe ku tu biecibîne - Dema nivîs li rûnûsgehê dihat kopîkirin çewtî pêk hat - Şandî tê hilbarkirin - %1$d sal - Salek - %1$d meh - Mehek + Tomarên sepanê li rûnûsgehê hatin kopîkirin + Şandî tê hilxistin + Mêvan + Sal + Ecibandin + Welat + Şopîner + Vîdyo + berî xulekekê %1$d roj Rojek %1$d demjimêr berî demjimêrekê %1$d xulek - berî xulekekê - çirke berê - Şopîner - Bangewazî - Vîdyo - Şandî û Rûpel - Welat - Ecibandin - Mêvan + Mehek + %1$d sal + Salek + %1$d meh Dîtin - Sal + saniye berê + Belavok + Şandî & Rûpel Rûkar tên stendin… Hûrgilî %d hilbijartî - Çav li rûpela PPP bigerîne - Hîn ti şîrove tune ne - Ti şandiyeke tine bi vê nîşankê - Biecibîne - Gotara resen bixwîne + Şîrove bike Şîrove hatin girtin + Biecibîne %1$d / %2$d - Nikare biweşîne şandiyeke vala - Ji bo dîtin an jî serastkirina şandiyan destûra te nîn e - Ji bo dîtin an jî serastkirina rûpelan destûra te nîn e - Destûra te nîn e ji bo dîtin an sererastkirina şîroveyan - Bêhtir Ji mehekê kevintir Ji hefteyekê kevintir Ji 2 rojan kevintir - Ecibandî - Şîrove bike - Şîrove hate jêbirin. - Vê %s bibersivîne - Hêj ti şandiyek tine ye. Çima yek çênakî? - Derdikeve… - Nikare vê çalakiyê bi cîh bîne - Nikare vê malperê asteng bike - Şandiyên ji vê malperê wê êdî neyên nîşandan + Rûpela me ya PPP bibîne + Gotara orijînal bixwîne + Hat ecibandin + Tê derketin… + Hîn ti şîrove tune ye + Ti şandiyên eleqedarî vê mijarê tune ye + Şandiya vala nayê weşandin + Ji bo dîtin an jî sererastkirina şandiyan destûra te nîn e + Ji bo dîtin an jî sererastkirina rûpelan destûra te nîn e + Ji bo dîtin an jî sererastkirina şîroveyan destûra te nîn e + Zêdetir + Şîrove betal bû + Vêya %s bibersivîne + Hîn ti şandî tune ye. Ka em şandiyekê çêkin? Vê malperê asteng bike + Şandiyên ji vê malperê wê êdî neyên nîşandan + Nikare vê çalakiyê bi cîh bîne + Ev malper nayê astengkirin Demsaz bike - Bihildemîne + Hildemîne Malperên pêşniyarkirî nîn in Nikare dev ji şopa vê malperê berde - Nkare vê malperê bişopîne + Malperên şopandî Jixwe tu vê malperê dişopînî Ev malper nayê nîşandan Malper hat şopandin - Ji bo şopandinê URL an etîketekê binivîse - Malperên şopandî - Etîketên şopandî Malpera Xwînerê + Ev malper nayê şopandin + Ji bo şopandinê URL an jî mijarekê bikevê + Mijarên şopandî + Alîkarî Eger tu bi pirranî bêyî pirsgirêkek karibî werî girêdan li vê malperê, ev dibe ku were maneya yek dixwaze malpera te teqlîd bike, û tu nikaribî dewam bikî. Dîsa jî dixwazî bi sertîfîkayê bawer bibî? Sertîfîkaya SSL\'ê ya nederbasdar Te pêborîna xwe wenda kir? - Alîkarî + Çewtiya daxistina wêneyê + Toreke berdest nîn e Navê bikarhêner an pêborîn xelet e - Navnîşaneke emailê ya derbasdar têkeve Navnîşana emaila te nederbasdar e - Çewtiya daxistina wêneyê Şîrove nehat barkirin - Di sererastkirina şandiyê de çewtiyek qewimî - Di sererastkirinê de çewtiyek qewimî Çewtiyek derket Vê gavê şîrove nehatin nûkirin - Rûpel nikarin werin nûkirin vê gavê - Şandî nikarin werin nûkirin vê gavê - Çewtiyeke rû da gava jêbirina şandiyê - Ti danezanek tine - Hewcedariya hilxistina medyayê heye ya karteke SDyê ya bicîhkirî - Qada navê pêwîst e bo beşê - Beş bi serkeftî hate tevlîkirin - Tevlîkirina beşê bi ser neket - Ne nexwestî - Bi ser neket ku rûkaran bîne - Çewtiyeke rû da wextê gihîna vê blogê - Hêmana medyayê nikare were anîn - Toreke berdest nîn e - Nikare vê nîşankê rabike - Nikare vê nîşankê tevlî bike - Qeyda sepanê - Çewtiyeke rû da wextê çêkirina danegeha sepanê. Jinûvesazkirina sepanê biceribîne. - Ev blog veşartî ye û nikare were barkirin. Ji sazkariyan wê çalak bike û cardin biceribîne. - Medya nikare were nûkirin vê gavê + Di jêbirina şandiyê de çewtiyek derket + Danezan tune + Ji bo medya were hilxistin SD karta girêdayî pêwîst e + Navê kategoriyê pêwîst e + Kategorî bi serkevtî hat tevlîkirin + Tevlîkirina kategoriyê bi ser neket + Ne spam e + Stendina rûkaran bi ser neket + Navnîşana emaileke derbasdar têkeve + Di sererastkirina şandiyê de çewtiyek derket + Di sererastkirinê de çewtiyek derket + Rûpel niha nayên nûkirin + Şandî niha nayên nûkirin + Di gihîna vê blogê de çewtiyek derket + Hêmana medyayê nehat vegerandin + Sazkariyên rûpelê + Çewtiya girêdanê + Sazkariyên şandiyê + Şandiyê jê bibe? + Pejirandî + Rûpelê jê bibe? + Nexwestî + Bipejirîne + Tê pejirandin + Red bike + Tê redkirin + Şîrove pêwîst e + Malperê rake + Di gerokê de bibîne + Navê kategoriyê Bloga WordPressê - Sazkariyên Wêneya - Guherînên xwecihî - Rênimaya nihêniyê Medyaya nû Şandiya nû - Danezan tune…hîna. - Destûrdarkirin pêwîste - Kontrol bike ku URLya têketî derbasdar be Lîsansên çavkaniya vekirî - Ji bo hilxistina medyayê nikare pelgeya demkî çêbike. Jê bawer be ku ser amûra te têra xwe cihê vala hebe. - Navê kategoriyê - Kategoriya nû tevlê bike - Di gerokê de bibîne - Malperê rake - Şîrove nehat guherandin - Şîrove pêwîst e - Bila sererastkirina vê şîroveyê were betalkirin? - Guhertin tên tomarkirin + Jêbirdank Jêbirdank - Bila biçe jêbirdankê? - Diçe jêbirdankê + Kontrol bike ku URLya têketî derbasdar be + Guhertin tên tomarkirin Wekî spam tê nîşankirin - Tê redkirin - Tê pejirandin - Jêbirdank Spam - Red bike - Bipejirîne Şîroveyê sererast bike - Çû jêbirdankê - Nexwestî Hilawestî - Pejirandî - Rûpelê jê bibe? - Şandiyê jê bibe? - Sazkariyên şandiyê - Ji bo hilxistinê dosye nehat dîtin. Ew hatiye jêbirin an veguheztin? - Reşnivîsa Cîgehî - Sazkariyên rûpelê Nivîsa girêdanê (li gor daxwazê) Hinek medya nehatin jêbirin. Piştre dîsa biceribîne. Destûra te ji bo dîtina medyagehê tune ye Wêneyê biçûk Zêdetir bizane - Di barkirina şandiyê de çewtiyek rû da. Şandiyê nû bike û dîsa biceribîne. - Di gihîştina vê pêvekê de çewtiyek rû da - Serrastkirinê betal bike - Çewtiya girêdanê - Kategoriyan Hilbijêre + Tomargeha sepanê + Sazkariyên wêneyê + Guhertinên cîgehî + Polîtîkaya nihêniyê + Danezan tune ye…heta niha. + Ji bo hilxistinê dosye nehat dîtin. Ew hatiye jêbirin an jî veguheztin? + Reşnivîsa cîgehî + Sererastkirinê betal bike + Kategoriyan hilbijêre + Ev mijar nayê rakirin + Ev mijar nayê tevlîkirin + Dema danegeha sepanê dihat afirandin çewtiyek derket. Sepanê ji nû ve saz bike. + Ev blog veşartî ye û nayê barkirin. Di sazkariyan de wê dîsa çalak bike û dîsa biceribîne. + Medya niha nayên nûkirin + Destûrdarkirin pêwîst e + Ji bo hilxistina medyayê dosyeya demkî nayê afirandin. Jê bawer be bila di cîhaza te de têra xwe ciyê vala hebe. + Kategoriya nû tevlî bike + Şîrove nehat guhertin + Sererastkirina vê şîroveyê betal bike? + Bişîne jêbirdankê? + Dişîne jêbirdankê + Çû jêbirdankê + Di barkirina şandiyê de çewtiyek derket. Şandiyê nû bike û dîsa biceribîne. + Di gihîna vê pêvekê de çewtiyek derket Girêdanê parve bike - Şandî tên anîn… - Te û %,d kesên din ev eciband - %,d kes ev eciband + Şandî tên stendin… Bibersivîne - Tu nikarî bi WordPressê parve bikî bêyî blogeke berçav + %,d kes ev eciband + Te û %,d kesên din ev eciband Şîrove wekî nexwestî hat nîşankirin - Ev şandî cardin nayê anîn - Te û kesekî din ev eciband + Tu nikarî bi WordPressê parve bikî bêyî blogeke berçav Vîdyo hilbijêre Wêne hilbijêre + Te û kesekî din ev eciband + Ev şandî cardin nayê anîn + Parve bike + Bişopîne + Şîroveya te nehat şandin + (Bêsernav) Bibe Endam - %s nehate vekirin + Tê şopandin Wêne nehat nîşandan Nehat parvekirin - Ew etîket ne derbasdar e - Jixwe tu vê etîketê dişopînî - Şîroveya te nehat şandin Te vêya eciband Kesekî vêya eciband %s hat rakirin - %s hat tevlîkirin Şîroveyê bibersivîne… - Tê şopandin - Bişopîne - Parve bike Dîsblog bike - (Bêsernav) - Hîn ti şîrove tune ne Ev lîste vala ye + Ew etîket ne derbasdar e + Jixwe tu vê etîketê dişopînî + %s hat tevlêkirin + Hîn ti şîrove tune ye + %s nehat vekirin + Parve bike + Biçalakîne + Rûkar + Doh + Îro Meh Hefte Roj - Doh - Îro - Şander - Etîket û Kategorî - Tikandin - Amar - Parve bike - Biçalakîne - Çewtiya hildemandinê - Daxuyanî + Çargoşe + Çember + Pêşekerî Sernûçe + Daxuyanî + Tikandin + Şander Sernav - Pêşekerî - Çember Mozaîk - Çargoşe - Rûkar - Biavêje + Amar + Etîket û Kategorî + Çewtiya hildemandinê Bi rê ve bibe - û %d zêdetir. + Biavêje %d danezanên nû Şopîner Bersiv hat weşandin + û %d zêdetir. Têkeve Tê barkirin… Pêborîna HTTP\'ê Navê bikarhêner ê HTTP\'ê - Di hilxistina medyayê de çewtiyek rû da + Di hilxistina medyayê de çewtiyek derket Navê bikarhêner an jî pêborîn çewt e. - Têkeve Navê bikarhêner Pêborîn + Têkeve Xwîner Rûpel - şandî Anonîm - Tora berdest nîn e - qediya + Şandî + Înternet tune ye BAŞ e - Navnîşan - Mercên Xizmetê + qediya + URL Guherto + Mercên Xizmetê WordPress ji bo Android\'ê Bicîkirin Nû bike @@ -2440,23 +2465,23 @@ Language: ku_TR Veşartî Sernav Etîketan bi bêhnokê(,) ji hev veqetîne - Herî Zêde Bikarhatî + Kategorî Şîrove tên jêbirin SD Kart Pêwîst e - Parçeyê vîdeoyê rake - Bipejirîne + Medya Jê bibe + Bipejirîne Hîç - Aniha Biweşîne - Bibersivîne - li ser - Pêşdîtin - Çewtiya nûkirina kategoriyê Çewtî Na Erê + Bibersivîne + Pêşdîtin + li ser Sazkariyên Danezanan - Tevlê bike + Aniha Biweşîne + Çewtiya nûkirina kategoriyê Tomar bike - Betal + Betal bike + Tevlî bike diff --git a/WordPress/src/main/res/values-ko/strings.xml b/WordPress/src/main/res/values-ko/strings.xml index 2af7f7ebc1bc..4142001ff4ff 100644 --- a/WordPress/src/main/res/values-ko/strings.xml +++ b/WordPress/src/main/res/values-ko/strings.xml @@ -1,13 +1,52 @@ + <b>요한 브란트</b>가 글에 답했습니다 + 오늘 사이트에서 <b>50개의 좋아요</b>를 받았습니다 + <b>메디슨 루이즈</b>가 글을 좋아합니다 + 스크롤 할 수 있는 블록 메뉴가 열렸습니다. 블록을 선택하세요. + 스크롤 할 수 있는 블록 메뉴를 닫았습니다. + 건너뛰기 + 선택하기 + 좋아하는 홈페이지 레이아웃을 고르세요. 사용자 정의하거나 나중에 바꿀 수 있습니다. + 디자인 선택하기 + 온라인으로 돌아가면 재시도를 누르거나 아래의 단추를 이용하여 빈 페이지를 만드세요. + 레이아웃은 오프라인 중에 사용할 수 없습니다 + 재시도를 누르거나 아래의 버튼을 이용하여 빈 페이지를 만드세요. + 오류가 있어 레이아웃을 사용할 수 없습니다 + 카테고리 추가하기 + 새 카테고리 추가하기 + 카테고리 + 설정하지 않았습니다 + 카테고리 + 런던의 박물관 + 세계 최고의 팬 + 상위 10개 카페 + 정치 + 음악 + 원예 + 축구 + 요리 + 예술 + 주간 락앤롤 + 웹 뉴스 + 파멜라 응우옌 + 사진가 카메론 카르스텐의 작업에서 깊은 영감을 받았습니다. 다음 번에 이 기법을 시도할 것입니다 + 영감받기 + 좋아하는 사이트를 팔로우하고 새 읽을 거리를 발견하세요. + 깊이 있는 분석으로 독자가 늘어나는 것을 확인하세요. + 댓글과 알림을 실시간으로 보세요. + 강력한 편집기로 이동 중에도 글을 쓸 수 있습니다. + 세계적으로 가장 잘 알려진 웹사이트 제작기에 오신 것을 환영합니다. + 미디어 로딩을 실패했습니다 + 팔로우한 사이트 매 릴리즈마다 더 많은 블록을 추가하려고 열심히 일하고 있습니다. - \'%s\'을(를) 완전히 지원하지 않습니다 + ‘%s’을(를) 완전히 지원하지 않습니다 도움 버튼 웹 편집기를 이용하여 편집하기 이미지 고르기 @@ -20,7 +59,6 @@ Language: ko_KR 이야기 글을 만드는 방법 이야기 글을 일찍 접근할 수 있게 되었으니 한 번 사용해 보시기 바랍니다. 이야기 글 소개 - 레이아웃을 가져오는 동안 오류가 생겼습니다 빈 페이지를 만들었습니다 페이지를 만들었습니다 레이아웃 미리보기 @@ -158,8 +196,7 @@ Language: ko_KR 이미지나 비디오 고르기 이미지 고르기 블록을 제거했습니다 - 워드프레스 로고 - 새로 만들 사이트 주소 입력하기 + 이미 있는 사이트 주소 입력하기 워드프레스닷컴으로 계속하기 등록 확인 워드프레스닷컴 계정이 없는 상태에서 Google로 계속하기를 선택하는 경우, 계정을 만들고 %1$s서비스 약관%2$s에 동의하는 것으로 간주됩니다. @@ -181,8 +218,6 @@ Language: ko_KR 또는 비밀번호 입력하기 계정 만들기 이메일로 링크 보내기 - 가장 유연한 웹사이트 제작 도구의 능력을 직접 경험해보세요. - 웹의 37\%가 워드프레스로 만들어졌습니다. 비밀번호 재설정하기 요청을 처리하는 중에 문제가 있었습니다. 나중에 다시 시도하시기 바랍니다. 개성과 주제를 반영할 사이트 이름을 주세요. 첫 인상이 중요합니다! @@ -487,7 +522,6 @@ Language: ko_KR 이 글은 즉시 동기화됩니다. 동기화할 준비가 되셨습니까? 이 도메인을 사용할 수 없습니다. - 소개 -%s 사이트에 액세스할 수 없습니다. 이 문제를 해결하려면 호스트에게 연락해야 합니다. <b>SSL 인증서</b> 문제로 인해 사이트에 액세스할 수 없습니다. 이 문제를 해결하려면 호스트에게 연락해야 합니다. @@ -1313,7 +1347,6 @@ Language: ko_KR 사진 편집 사이트 선택 새 계정 - 로그인 다음 계정으로 로그인됨 사용자 세부정보 파일 세부정보 @@ -2247,7 +2280,6 @@ Language: ko_KR 애플리케이션 로그가 클립보드에 복사되었습니다. 이 블로그는 비었습니다. 새 게시물 - 회원님이 좋아할 만한 블로그 텍스트를 클립보드에 복사하는 동안 오류가 발생했습니다. 업로드 %1$d년 diff --git a/WordPress/src/main/res/values-ms/strings.xml b/WordPress/src/main/res/values-ms/strings.xml index 679fe829a13e..bee8ed6f1f13 100644 --- a/WordPress/src/main/res/values-ms/strings.xml +++ b/WordPress/src/main/res/values-ms/strings.xml @@ -726,7 +726,6 @@ Language: ms Ralat berlaku semasa menyalin teks ke papan keratan Log aplikasi telah disalin ke papan keratan Kiriman baharu - Laman yang anda mungkin suka Memuat naik kiriman %1$d hari %1$d jam diff --git a/WordPress/src/main/res/values-nb/strings.xml b/WordPress/src/main/res/values-nb/strings.xml index b49eadfa9f04..ae376c29cf8e 100644 --- a/WordPress/src/main/res/values-nb/strings.xml +++ b/WordPress/src/main/res/values-nb/strings.xml @@ -1,11 +1,38 @@ + Hopp over + Velg + Legg til kategori + Legg til ny kategori + Kategorier + Ikke angitt + Kategorier + Museer i London + Politikk + Musikk + Hagestell + Fotball + Matlaging + Kunst + Nettsteder å følge + Velg bilder + Side opprettet + Tilbake + Kom i gang + Følg emner for å oppdage nye nettsteder + Av + Åpne nettstedet + Velg element + Søk eller skriv inn URL + Legg til denne telefonlenken + Legg til denne lenken + Legg til denne e-postlenken Fet Moderne Lekende @@ -15,10 +42,12 @@ Language: nb_NO %s %s valgt Få en innloggingslenke med e-post + Mikrofon Velg et oppsett Lag et innlegg eller en fortelling Lag en side Lag et innlegg + Du liker kanskje Skjul Videobildetekst. Tom. Oppdaterer tittelen. @@ -84,8 +113,7 @@ Language: nb_NO Velg bilde eller video Velg bilde Blokk fjernet - WordPress-logo - Oppgi din nettstedsadresse + Oppgi din eksisterende nettstedsadresse Fortsett med WordPress.com Registreringsbekreftelse Vi vil sende deg en registreringslenke for å opprette din nye WordPress.com-konto. @@ -102,8 +130,6 @@ Language: nb_NO Eller oppgi dit passord Opprett konto Send lenke med e-post - Lås opp kraften til en mest fleksible nettstedsbyggeren. - 37\% av alle nettsteder er bygget på WordPress. Tilbakestill ditt passord Gi ditt nettsted et navn som reflekterer des personlighet og emne Førsteinntrykket teller! Bestem din nettstedstittel @@ -359,7 +385,6 @@ Language: nb_NO Dette innlegget vil synkroniseres straks. Klar til å synkronisere? Dette domenet er ikke tilgengelig - Introduksjon -%s Vi klarte ikke å få tilgang til nettstedet ditt. Du må kontakte din nettvert for å løse dette. Straks ferdig! Vi må bare få bekreftet din e-postadresse koblet til Jetpack <b>%1$s</b> @@ -1164,7 +1189,6 @@ Language: nb_NO Rediger bilde Velg nettsted Ny konto - Logg inn Logget inn som Persondetalj Fildetaljer @@ -2086,7 +2110,6 @@ Language: nb_NO App-loggbøker har blitt kopiert til utklippstavlen Dette nettstedet er tomt Nye innlegg - Du vil kanskje like En feil oppstod under kopiering av tekst til utklippstavlen Laster opp innlegg %1$d år @@ -2241,6 +2264,7 @@ Language: nb_NO Du og en annen har likt dette Velg video Velg bilde + Registrer deg Klarte ikke å åpne %s Klarte ikke å vise bilde Klarte ikke å dele diff --git a/WordPress/src/main/res/values-nl/strings.xml b/WordPress/src/main/res/values-nl/strings.xml index c4b9685a52a2..a9c7e22fc5aa 100644 --- a/WordPress/src/main/res/values-nl/strings.xml +++ b/WordPress/src/main/res/values-nl/strings.xml @@ -1,11 +1,34 @@ + Kies + Overslaan + Niet ingesteld + Nieuwe categorie toevoegen + Categorie toevoegen + Lay-outs niet beschikbaar vanwege een fout + Lay-outs niet beschikbaar indien offline + Kies een ontwerp + Kunst + Koken + Voetbal + Tuinieren + Muziek + Politiek + Mijn toptien café\'s + Museums in Londen + Welkom bij de wereld\'s meest populaire sitebouwer. + Met deze krachtige editor kun je onderweg publiceren. + Bekijk reacties en berichten in real time. + Bekijk hoe je publiek groeit met in-dept analytics. + Volg je favoriete sites en ontdek nieuwe reads. + Sites om te volgen + Media laden is mislukt \'%s\' wordt niet volledig ondersteund We werken hard om meer blokken toe te voegen bij elke release. Ze worden als een nieuw blogbericht op je site gepubliceerd, zodat je publiek nooit iets mist. @@ -19,7 +42,6 @@ Language: nl Lay-out voorbeeld Pagina aangemaakt Blanco pagina aangemaakt - Er is een fout ontstaan bij het ophalen van de lay-outs Hoe maak je een verhaal bericht Je hebt vroege toegang tot verhaal berichten en we zouden het heel leuk vinden als je het eens probeert. Verhaal berichten introductie @@ -157,12 +179,11 @@ Language: nl Als je doorgaat met Google en nog geen WordPress.com account hebt, maakt je een account aan en gaat je akkoord met onze %1$sServicevoorwaarden%2$s. Inschrijfbevestiging Ga verder met WordPress.com - Voer je site adres in - WordPress logo Blok verwijderd Kies afbeelding Kies afbeelding of video Video kiezen + Voer je bestaande site adres in Als je doorgaat, ga je akkoord met onze %1$sServicevoorwaarden%2$s. We sturen je een link per e-mail om je nieuwe WordPress.com account aan te maken. We gebruiken dit e-mailadres om je nieuwe WordPress.com account aan te maken. @@ -176,8 +197,6 @@ Language: nl of We e-mailen je een link waarmee je direct kan inloggen, geen wachtwoord nodig. Stel je wachtwoord opnieuw in - 37\% van het web is gebouwd op WordPress. - Ontgrendel de kracht van de meest flexibele sitebouwer. Link per e-mail verzenden Account aanmaken Of typ je wachtwoord @@ -487,7 +506,6 @@ Language: nl Dit bericht wordt onmiddellijk gesynchroniseerd. Klaar om te synchroniseren? Dit domein is niet beschikbaar - Inleiding -%s We konden geen toegang krijgen tot je site. Neem contact op met je host om dit probleem te verhelpen. We konden geen toegang krijgen tot je site vanwege een probleem met het <b>SSL-certificaat</b>. Neem contact op met je host om dit probleem te verhelpen. @@ -1313,7 +1331,6 @@ Language: nl Foto bewerken Site kiezen Nieuw account - Inloggen Ingelogd als Persoonsgegevens Bestandsgegevens @@ -2247,7 +2264,6 @@ Language: nl Applicatielogs zijn gekopieerd naar het klembord Nieuwe berichten Er is een fout opgetreden tijdens het kopiëren van de tekst naar het klembord - Sites die je misschien ook leuk vindt Uploaden bericht Deze site is leeg Thema\'s ophalen … diff --git a/WordPress/src/main/res/values-pl/strings.xml b/WordPress/src/main/res/values-pl/strings.xml index 0032189a749b..801ff7fbc46e 100644 --- a/WordPress/src/main/res/values-pl/strings.xml +++ b/WordPress/src/main/res/values-pl/strings.xml @@ -1,11 +1,37 @@ + Pomiń + Wybierz + Wybierz wzór + Dodaj kategorię + Kategorie + Nie ustawiono + Kategorie + Dodaj nową kategorię + Szablony nie są dostępne z powodu błędu + Szablony nie są dostępne w trybie offline + Polityka + Muzyka + Ogrodnictwo + Piłka nożna + Gotowanie + Sztuka + Pamela Nguyen + Muzea w Londynie + Najlepsi na świecie fani + Moje dziesięć ulubionych kawiarni + Wiadomości Web + Dzięki potężnemu edytorowi możesz w locie dodawać nowe wpisy. + Witaj na najbardziej popularnej platformie do tworzenia witryny internetowej + Obserwuj jak rośnie twoja publiczność dzięki zaawansowanej analityce. + Nie udało się wczytać plików mediów + Witryny do obserwacji \'%s\' nie jest w pełni wspierana Z każdym wydaniem pracujemy nad dodawaniem większej ilości bloków. Utwórz wpis z relacją @@ -20,7 +46,6 @@ Language: pl Uzyskałeś/aś wczesny dostęp do wpisów z relacjami. Zachęcamy ciebie do wypróbowania tej funkcji. Wprowadzamy wpisy z relacjami Nowe relacje są dla każdego - Wystąpił błąd podczas pobierania układów Utworzono pustą stronę Utworzono stronę Podgląd układu @@ -158,11 +183,10 @@ Language: pl Wybierz zdjęcie lub film Wybierz obrazek Blok usunięto - Logo WordPressa - Wprowadź adres witryny Kontynuuj z WordPress.com Potwierdzenie rejestracji Jeśli kontynuujesz z logowamiem poprzez Google, a nie masz jeszcze konta na WordPress.com, zakładasz automatycznie konto i zgadzasz się z naszymi %1$sWarunkami świadczenia usługi%2$s. + Wprowadź adres swojej istniejącej witryny Kontynuując, zgadzasz się z naszymi %1$sWarunkami świadczenia usługi%2$s. Prześlemy tobie odnośnik, za pomocą którego utworzysz nowe konto na WordPress.com. Twój adres email będzie użyty to utworzenia twojego nowego konta na WordPress.com. @@ -181,8 +205,6 @@ Language: pl Lub wprowadź hasło Utwórz konto Wyślij odnośnik przez email - Wykorzystaj możliwości najbardziej elastycznego kreatora witryny. - 37\% internetu jest oparte na WordPressie. Zresetuj hasło Wystąpił błąd podczas obsługi tego zapytania. Proszę spróbować ponownie później. Ustaw tytuł witryny @@ -487,7 +509,6 @@ Language: pl Ten wpis będzie natychmiast zsynchronizowany. Gotowe do synchronizacji? Domena nie jest dostępna - Wprowadzenie -%s Nie byliśmy w stanie uzyskać dostępu do twojej witryny. Aby rozwiązać ten problem, sprawdź dostęp do hosta. Nie byliśmy w stanie uzyskać dostępu do twojej witryny z powodu problemu z <b>SSL Certificatem</b>. Aby rozwiązać ten problem, sprawdź dostęp do hosta. @@ -1309,7 +1330,6 @@ Language: pl Edytuj zdjęcie Wybierz witrynę Nowe konto - Zaloguj się Jeśli potrzebujesz więcej przestrzeni, rozważ rozszerzenie subskrypcji WordPress. Cofnięto dodanie polubienia Szczegóły powiadomienia %s @@ -2247,7 +2267,6 @@ Language: pl Wystąpił błąd podczas kopiowania tekstu do schowka Dziennik aplikacji został skopiowany do schowka Nowe wpisy - Blogi, które mogą ciebie zainteresować Przesyłanie wpisu Ta witryna jest pusta %1$d minut diff --git a/WordPress/src/main/res/values-pt-rBR/strings.xml b/WordPress/src/main/res/values-pt-rBR/strings.xml index c7ba78ca41e5..d762bbf2be15 100644 --- a/WordPress/src/main/res/values-pt-rBR/strings.xml +++ b/WordPress/src/main/res/values-pt-rBR/strings.xml @@ -1,11 +1,43 @@ + Estamos trabalhando arduamente para adicionar mais blocos a cada lançamento. + \'%s\' não é totalmente suportado + Botão de ajuda + Editar usando o editor web + Escolher imagens + Criar um post de Story + São publicados como novos posts em seu site para que sua audiência não perca nada. + Posts de Stories não desaparecem + Combine fotos, vídeos e texto para criar stories clicáveis e com alto engajamento que seus visitantes vão adorar. + Agora stories são para todo mundo + Título de exemplo to story + Como criar um post de story + Agora você tem acesso a posts de story e adoraríamos que você desse uma olhada nesse recurso. + Apresentando os posts de Story + Página vazia criada + Página criada + Visualizar layout + %1$s teve o acesso negado às suas fotos. Para corrigir, edite suas permissões e habilite %2$s e %3$s. + Inserção de imagem falhou. + Inserção de imagem com falha: %s + Escolher da biblioteca de mídias do WordPress + Voltar + Comece agora + Siga tópicos para descobrir novos blogs + Por + Este referenciador não pode ser marcado como spam + Desmarcar como spam + Marcar como spam + Abrir site + Enviando o arquivo gif + Enviando mídia + Enviando mídia Selecionar item Pesquisar ou digitar o URL Adicionar este telefone como link @@ -125,8 +157,6 @@ Language: pt_BR Escolher imagem ou vídeo Escolher imagem Bloco removido - Logo do WordPress - Digite o endereço de seu site Continuar com WordPress.com Confirmação de inscrição Se você continuar com o Google e ainda não tiver uma conta no WordPress.com, você criará uma conta e concordará com nossos %1$sTermos de Serviços%2$s. @@ -148,8 +178,6 @@ Language: pt_BR Ou digite sua senha Criar conta Enviar link por e-mail - Descubra o poder do criador de sites mais flexível. - 37\% da internet é construída com o WordPress. Redefinir sua senha Houve um problema ao completar a solicitação. Tente novamente mais tarde. Dê ao seu site um nome que reflita sua personalidade e tópico. As primeiras impressões contam! @@ -400,7 +428,9 @@ Language: pt_BR Remover o filtro atual Selecione um site ou tag para filtrar posts, janela popup Acessar o WordPress.com + Gerenciar tópicos e sites Acesse com o WordPress.com para ver os posts mais recentes dos sites que você segue + Acesse com o WordPress.com para ver os posts mais recentes dos tópicos que você segue Adicionar bloco antes Adicionar bloco depois Seguir um site @@ -409,6 +439,8 @@ Language: pt_BR Visualizar modelo Adicionar ao final Adicionar no início + Adicionar um tópico + Você pode seguir posts de um assunto específico adicionando um tópico Seguindo Filtrar Adicionar um shortcode… @@ -450,7 +482,6 @@ Language: pt_BR Este post será sincronizado imediatamente. Pronto para sincronizar? Este domínio não está disponível - Introdução -%s Não foi possível acessar seu site. Entre em contato com sua hospedagem para resolver isso. Não foi possível acessar seu site devido a um problema com o <b>certificado SSL</b>. Entre em contato com sua hospedagem para resolver isso. @@ -474,6 +505,7 @@ Language: pt_BR Explorar Curtidas Não conseguimos carregar os dados de seu site no momento. Tente novamente mais tarde. + Tópicos Biblioteca de mídia do WordPress Desagrupar Traduzir @@ -1053,6 +1085,8 @@ Language: pt_BR Escolha um tema Mais Toque em %1$s Temas %2$s para descobrir novos temas + Nenhum tópico seguido + Adicione tópicos aqui para encontrar posts sobre seus assuntos favoritos Tentar novamente Jetpack Jetpack instalado @@ -1272,7 +1306,6 @@ Language: pt_BR Comentário desaprovado Comentário aprovado Escolher site - Acessar Informações da notificação %s Notificações Botões de compartilhamento @@ -2135,6 +2168,7 @@ Language: pt_BR 1 curtida %,d seguidores Curtir + Editar tópicos e sites Post do leitor Configurações de notificações que são exibidas em seu dispositivo. Configurações de notificações que são enviadas ao e-mail vinculado à sua conta. @@ -2206,7 +2240,6 @@ Language: pt_BR Ocorreu um erro ao copiar o texto para a área de transferência Novos posts O logs do aplicativo foram copiados para a área de transferência - Talvez você goste destes sites Fazendo upload do post Este site está vazio Obtendo temas… @@ -2252,6 +2285,7 @@ Language: pt_BR Responder %s Saindo… Veja nossa seção de Perguntas Frequentes + Nenhum post neste tópico Não é possível executar esta ação Os posts deste blog não serão mais exibidos Bloquear este site @@ -2266,6 +2300,8 @@ Language: pt_BR Não é possível deixar de seguir este site Nenhum site recomendado Leitor do site + Tópicos seguidos + Digite um URL ou tópico para seguir Ajuda Perdeu a senha? Certificado SSL inválido @@ -2346,6 +2382,8 @@ Language: pt_BR Excluir página? Alterações locais Abrir licenças dos arquivos fonte + Não é possível adicionar este tópico + Não é possível remover este tópico Compartilhar link Coletando posts… Você e %,d outros curtiram isso @@ -2374,6 +2412,8 @@ Language: pt_BR Nenhum comentário ainda Reblogar Inscrever-se + Você já segue esse tópico + Esta tópico não é válido Temas Título Legenda diff --git a/WordPress/src/main/res/values-ro/strings.xml b/WordPress/src/main/res/values-ro/strings.xml index 39c2cc1ba1bc..addfab4dccbc 100644 --- a/WordPress/src/main/res/values-ro/strings.xml +++ b/WordPress/src/main/res/values-ro/strings.xml @@ -1,11 +1,50 @@ + <b>Johan Brandt</b> a răspuns la articolul tău + Azi ai primit <b>50 de aprecieri</b> pe sit + <b>Madison Ruiz</b> ți-a apreciat articolul + Meniul de blocuri care poate fi derulat este deschis. Selectează un bloc. + Meniul de blocuri care poate fi derulat este închis. + Sari + Alege + Alege aranjamentul preferat pentru prima pagină. Îl poți personaliza sau modifica mai târziu. + Alege un design + Atinge reîncearcă când ești din nou online sau creează o pagină goală folosind butonul de mai jos. + Aranjamentele nu sunt disponibile când ești offline + Atinge reîncearcă sau creează o pagină goală folosind butonul de mai jos. + Aranjamentele nu sunt disponibile din cauza unei erori + Adaugă o categorie + Adaugă o categorie nouă + Categorii + Nesetată + Categorii + Muzee din Londra + Cei mai buni fani din lume + Primele zece cafenele, pe gustul meu + Politică + Muzică + Grădinărit + Fotbal + Bucătărie + Artă + Rock and Roll în fiecare săptămână + Știri pe web + Pamela Nguyen + M-au inspirat foarte mult lucrările fotografului Cameron Karsten. Voi încerca aceste tehnici pe viitor + Inspiră-te + Urmărește-ți siturile preferate și descoperi lecturi noi. + Urmărești cum îți crește audiența cu analitice detaliate. + Vezi comentariile și notificările în timp real. + Cu acest editor puternic poți publica din mers. + Bine ai venit la cel mai popular constructor de situri web din lume. + Încărcarea media a eșuat + Situri de urmărit Lucrăm continuu pentru a adăuga mai multe blocuri la fiecare lansare. „%s” nu este acceptat în totalitate Buton pentru ajutor @@ -18,9 +57,8 @@ Language: ro Acum narațiunile sunt pentru toată lumea Exemplu de titlu narațiune Cum să creezi un articol narațiune - Acum ai acces Articole narațiune și ne-ar plăcea să le încerci. + Acum ai acces la Articole narațiune și ne-ar plăcea să le încerci. Prezentăm Articole narațiune - A apărut o eroare în timpul aducerii aranjamentelor A fost creată o pagină goală A fost creată o pagină Previzualizare aranjament @@ -158,8 +196,7 @@ Language: ro Alege o imagine sau un video Alege o imagine Bloc înlăturat - Logo WordPress - Introdu adresa sitului tău + Introdu adresa sitului tău de acum Continuă cu WordPress.com Confirmare înregistrare Dacă continui cu Google și nu ai deja un cont WordPress.com, îți creezi un cont și ești de acord cu %1$stermenii de utilizare ai serviciului%2$s nostru. @@ -181,8 +218,6 @@ Language: ro Sau tastează parola Creează un cont Trimite legătura prin email - Deblochează puterea celui mai flexibil constructor de situri web. - 37\% din web este construit cu WordPress. Resetează-ți parola A fost o problemă la gestionarea cererii. Te rog reîncearcă mai târziu. Dă-i sitului un nume care să-i reflecte personalitatea și subiectul (tema). Prima impresie este foarte importantă! @@ -487,7 +522,6 @@ Language: ro Acest articol va fi sincronizat imediat. Ești gata pentru sincronizare? Acest domeniu nu este disponibil - Introducere -%s Nu am putut să-ți accesăm situl. Pentru a rezolva acest lucru, va trebui să contactezi serviciul tău de găzduire. Nu am putut să-ți accesăm situl din cauza unei probleme cu <b>certificatul SSL</b>. Pentru a rezolva acest lucru, va trebui să contactezi serviciul tău de găzduire. @@ -1313,7 +1347,6 @@ Language: ro Editează fotografia Alege situl Cont nou - Autentificare Autentificat ca Detalii personale Detalii fișier @@ -2247,7 +2280,6 @@ Language: ro Jurnalele aplicației au fost copiate în clipbord Acest sit este gol Articole noi - Situri care s-ar putea să-ți placă A apărut o eroare în timp ce copiam textul în clipbord Încarc articolul %1$d ani diff --git a/WordPress/src/main/res/values-ru/strings.xml b/WordPress/src/main/res/values-ru/strings.xml index 250a42003251..203a15541546 100644 --- a/WordPress/src/main/res/values-ru/strings.xml +++ b/WordPress/src/main/res/values-ru/strings.xml @@ -1,11 +1,50 @@ + <b>Вася Пупкин</b> ответил на вашу запись + <b>Юля Петрова</b> отмечает вашу запись как понравившуюся + Вы сегодня получили <b>50 отметок \"нравится\"</b> на сайте + Выберите макет главной страницы. Его можно настроить или изменить позже. + Выбрать + Пропустить + Прокручиваемое меню блоков закрыто. + Прокручиваемое меню блоков открыто. Выберите блок. + Рубрики + Не указано + Рубрики + Добавить новую рубрику + Добавить рубрику + Макеты недоступны из-за ошибки + Нажмите повторить или создайте пустую страницу нажав на кнопку ниже. + Макеты недоступны при отсутствии подключения к сети + Нажмите повторить при восстановлении подключения к сети или создайте пустую страницу нажав на кнопку ниже. + Выбрать оформление + Меня восхищают работы фотографа Кэмерона Карстена. Я попробую его техники в будущем + Памела Нгуен + Новости веб + Еженедельный рок-н-ролл + Искусство + Кулинария + Футбол + Садоводство + Музыка + Политика + Мой выбор лучших 10 кафе + Лучшие мировые фанаты + Музеи Лондона + Добро пожаловать в самый популярный в мире конструктор сайтов. + С настолько мощным редактором вы можете создавать публикации на ходу. + Следите в реальном времени за комментариями и уведомлениями. + Следите за ростом вашей аудитории с глубокой аналитикой. + Подписывайтесь на избранные сайты и находите новое чтиво. + Вдохновляйтесь + Сайты для подписки + Загрузка медиафайла неудалась \'%s\' полностью не поддерживается Мы стараемся добавить как можно больше блоков в каждом выпуске. Они публикуются как новые записи блога на вашем сайте, ваша аудитория их не пропустит, это точно! @@ -19,7 +58,6 @@ Language: ru Просмотр макета Страница создана Создана пустая страница - Произошла ошибка при получении макетов Вам предоставлен ранний доступ к Историям, и мы хотим чтобы вы их попробовали. Представляем записи-истории Пример названия истории @@ -157,12 +195,11 @@ Language: ru Если вы продолжите работу с учетной записью Google, не имея учётной записи WordPress.com, вы создадите учётную запись и примете наши %1$sУсловия обслуживания%2$s. Подтверждение регистрации Продолжить с WordPress.com - Введите адрес вашего сайта - Логотип WordPress Блок удалён Выбор изображения Выбор изображения или видео Выбор видео + Введите адрес вашего сайта Продолжив, вы соглашаетесь с нашими %1$sПравилами пользования%2$s. Мы пришлём вам ссылку для создания учётной записи WordPress.com. Этот адрес эл.почты будет использован при создании вашей новой учётной записи WordPress.com. @@ -176,8 +213,6 @@ Language: ru или Мы пришлём вам ссылку для входа, которая позволит вам сразу войти без пароля. Сбросить пароль - 37\% всех сайтов сети основаны на WordPress. - Раскройте потенциал самой гибкой платформы для создания сайтов. Прислать ссылку по электронной почте Создать учётную запись или введите ваш пароль @@ -486,7 +521,6 @@ Language: ru Синхронизировать сейчас Эта запись будет синхронизирована прямо сейчас. Готовы начать синхронизацию? - Вступление -%s Этот домен недоступен Мы не смогли подключиться к сайту, поскольку он требует <b>HTTP авторизации</b>. Возможно потребуется обратиться в техподдержку хостинга, чтобы решить эту проблему. @@ -1292,7 +1326,6 @@ Language: ru Визуальный режим Переход в визуальный режим. ОТМЕНИТЬ - Войти Новая учетная запись Выберите сайт Изменить фото @@ -2248,7 +2281,6 @@ Language: ru Журналы приложения скопированы в буфер обмена. Новые записи Произошла ошибка при копировании текста в буфер обмена. - Сайты, подобранные для вас Этот сайт пуст. Получение тем… %1$d месяцев diff --git a/WordPress/src/main/res/values-sk/strings.xml b/WordPress/src/main/res/values-sk/strings.xml index 8203ffc9f4cb..d6e6886610d7 100644 --- a/WordPress/src/main/res/values-sk/strings.xml +++ b/WordPress/src/main/res/values-sk/strings.xml @@ -410,7 +410,6 @@ Language: sk Upraviť fotku Vybrať webovú stránku Nový účet - Prihlásenie Prihlásený ako Detail osoby Detaily súboru @@ -1340,7 +1339,6 @@ Language: sk Záznamy aplikácie boli skopírované do schránky Táto webová stránka je prázdna Nové príspevky - Webové stránky, ktoré sa vám možno páčia Pri kopírovaní textu do schránky nastala chyba Nahrávanie príspevku počet rokov: %1$d diff --git a/WordPress/src/main/res/values-sq/strings.xml b/WordPress/src/main/res/values-sq/strings.xml index 714a77515cf4..914cc79160f1 100644 --- a/WordPress/src/main/res/values-sq/strings.xml +++ b/WordPress/src/main/res/values-sq/strings.xml @@ -1,11 +1,46 @@ + U hap me blloqesh. Përzgjidhni një bllok. + Menuja e blloqeve u mbyll. + Anashkaloje + Zgjidhni + Zgjidhni skemën e parapëlqyer për faqen tuaj hyrëse. Mund ta përshtatni ose ndryshoni më vonë. + Zgjidhni një skemë + Prekni <code>Retry</code>, kur të jeni prapë të lidhur, ose krijoni një faqe të zbrazët duke përdorur butonin më poshtë. + S’mund të kihen skema, teksa jeni jashtë linje + Prekni <code>Retry</code>, ose krijoni një faqe të zbrazët duke përdorur butonin më poshtë. + Skema jo të passhme, për shkak të një gabimi + Shtoni Kategori + Shtoni Kategori të Re + Kategori + S’është caktuar + Kategori + Muzeume në Londër + Tifozët Më të Zjarrtë të Botës + Dhjetë Kafehanet e Mia Më të Mira + Politikë + Muzikë + Lulishtari + Futboll + Gatim + Art + Fyejt Gjatë Javës + Lajme Në Internet + Xhike Baulja + Jam kaq i frymëzuar nga vepra e gdhendësit Çome Çomanga. Do t’i provoj këto teknika në veprën time të ardhshme + Frymëzohuni + Ndiqni sajtet tuaj të parapëlqyer dhe zbuloni gjëra të reja për të lexuar. + Vëzhgoni shtimin e publikut tuaj, me mjete analizimi në thellësi. + Me fuqinë e përpunuesit mund të postoni kudo që ndodheni. + Mirë se vini te krijuesi më popullor në botë i sajteve. + Dështoi ngarkimi i medias + Sajte për t’u ndjekur Po punojmë fort për të shtuar më tepër blloqe me çdo hedhje në qarkullim. \'%s\' s’mbulohet plotësisht Buton ndihme @@ -15,12 +50,11 @@ Language: sq_AL Botohen si postim i ri blogu në sajtin tuaj, që kështu publiku juaj kurrë të mos humbë një gjë. Postimet e shkrimeve s’zhduken Ndërthurni foto, video dhe tekst, që të krijoni postime shkrimesh tërheqës, që vizitorët tuaj do t’i duan fort. - Tani më shkrimet janë për këdo + Tanimë shkrimet janë për këdo Titull shembulli shkrimi Si të krijohet një postim shkrimi Ju është dhënë mundësia e përdorimit të hershëm të Postimeve të Shkrimeve dhe do të donim ta provonit. Ju Paraqesim Postime Shkrimesh - Ndodhi një gabim teksa silleshin skemat U krijua faqe e zbrazët Faqja u krijua Paraparje Skeme @@ -157,8 +191,7 @@ Language: sq_AL Zgjidhni figurë ose video Zgjidhni figurë Blloku u hoq - Stema e WordPress-it - Jepni adresën e sajtit tuaj + Jepni adresën e sajtit tuaj ekzistues Vazhdoni me WordPress.com Ripohim regjistrimi Nëse vazhdoni me Google dhe ende s’keni një llogari WordPress.com, po krijoni një llogari dhe pajtoheni me %1$sKushtet tona të Shërbimit%2$s. @@ -180,8 +213,6 @@ Language: sq_AL Ose shtypni fjalëkalimin tuaj Krijo llogari Dërgoje lidhjen me email - Çlironi fuqinë e krijuesit më të zhdërvjellët të sajteve. - 37\% e web-it ngrihet mbi WordPress. Ricaktoni fjalëkalimi tuaj Pati një problem me trajtimin e kërkesës. Ju lutemi, riprovoni. Jepini sajtit tuaj një emër që pasqyron personalitetin dhe subjektin e tij. Përshtypja e parë ka vlerë! @@ -486,7 +517,6 @@ Language: sq_AL Ky postim do të njëkohësohet menjëherë. Gati për Njëkohësim? Kjo përkatësi s’është e lirë - Hyrje -%s S’qemë në gjendje të hyjmë në sajtin tuaj. Do t’ju duhet të lidheni me strehuesin tuaj që ta zgjidhni këtë problem. S’qemë në gjendje të hyjmë në sajtin tuaj, për shkak të një problemi me <b>Dëshminë SSL</b>. Do t’ju duhet të lidheni me strehuesin tuaj që ta zgjidhni këtë problem. @@ -1312,7 +1342,6 @@ Language: sq_AL Përpunoni Foto Zgjidhni sajt Llogari e re - Hyrje I futur si Hollësi personi Hollësi kartele @@ -2246,7 +2275,6 @@ Language: sq_AL Regjistrat e aplikacionit u kopjuan te e papastra Ky sajt është i zbrazët Postime të reja - Sajte që mund t’ju pëlqejnë Ndodhi një gabim gjatë kopjimit të tekstit te e papastra Po ngarkohet postimi %1$d vjet diff --git a/WordPress/src/main/res/values-sr/strings.xml b/WordPress/src/main/res/values-sr/strings.xml index 654286f6e9c9..e2ac88064d78 100644 --- a/WordPress/src/main/res/values-sr/strings.xml +++ b/WordPress/src/main/res/values-sr/strings.xml @@ -1,11 +1,1567 @@ + Прескочи + Одабери + Одаберите дизајн + Додај категорију + Додај нову категорију + Категорије + Није постављено + Категорије + Музеји у Лондону + Политика + Музика + Баштованство + Фудбал + Кување + Уметност + Pamela Nguyen + Учитавање садржаја није успело + \'%s\' није у потпуности подржан + Дајемо све од себе да додамо нове блокове са сваком објавом. + Дугме помоћи + Уреди помоћу веб уређивача + Одабери слике + Направи чланак приче + Чланци приче не нестају + Приче су сада за све + Пример наслова приче + Како направити чланак приче + Представљамо чланке приче + Празна страница је направљена + Страница направљена + Претпреглед распореда + Назад + Од + %s одабрано + Микрофон + Пријави овај чланак + Угао + Ваша акција није дозвољена + Добродошли у читач. Откријте милионе блогова на дохват руке. + Дошло је до интерне грешке сервера + још %1$s ставки + Изаберите распоред + Сакрити + Направити страницу + Направити чланак или причу + Направити чланак + Можда Вам се свиди + Натпис видео записа. Празно + Ажурира наслов. + Залепи блок након + Наслов странице. %s + Наслов странице. Празно + Грешка се појавила током пуштања Вашег видео записа + Овај уређај не подржава Camera2 API. + Видео запис није могао бити сачуван + Грешка приликом чувања слике + Види складиште + Операција у току, покушајте поново + Није могуће пронаћи слајд Приче + Недеовољно складишта на уређају + Није могуће сачувати %1$d слајдове + Није могуће сачувати 1 слајд + Управљај + %1$d слајдова захтева радњу + 1 слајд захтева радњу + Покушајте поново да сачувате или обришете слајдове, затим покушајте поново да објавите Вашу причу. + Није могуће отпремити \"%1$s\" + Није могуће отпремити \"%1$s\" + \"%1$s\" је објављен + Отпремање \"%1$s\"… + %1$d преосталих слајдова + 1 преостао слајд + неколико прича + Снимање \"%1$s\"… + Без наслова + Одбаци + Ваш чланак Приче се неће сачувати као нацрт. + Одбаци чланак приче? + Обриши + Овај слајд ће бити уклоњен из Ваше приче. + Овај слајд још увек није сачуван. Ако обришете овај слајд, изгубићете сва уређивања која сте направили. + Обриши слајд приче? + Обриши слајд + Измени боју текста + Измени поравнање текста + има грешку + одабрано + није одабрано + Напарвити страницу + Направити чланак или причу + Направити празну страницу + Направите чланак, страницу или причу + Слајд + Покушај поново + Сачувано + Затвори + Следећи + Готово + Подели + ПОДЕЛИ + Сачувано међу фотографијама + Покушај поново + Сачувано + Чува се + Окрени + Блиц + Звук + Текст + Више + Налепнице + Блиц + Окрени камеру + Ухвати + Преглед + Одабери распоред + Доделите својој причи наслов + Одабери са уређаја + Чланак приче + Прекорачено ограничење складиштења + Додај датотеку + Замени видео запис + Замени слику или видео запис + Одабери видео запис + Одабери слику или видео запис + Одабери слику + Уклоњен блок + Настави са WordPress.com + Потврда пријаве + Унесите постојећу адресу свој сајта + Наставком, прихваћате наше %1$sУслове коришћења%2$s. + или + Настави са Google-ом + Готово + Направите налог + Провери електронску пошту + Започните + Или укуцајте своју лозинку + Пошаљи повезницу електронском поштом + Ресетујте своју лозинку + Дајте својој веб локацији назив који одражава њену личност и тему. Први утисци се рачунају! + Поставите наслов свог сајта + Додирни <b>%1$s</b> за постављање новог наслова + опције блока %s + Дуплирај блок + Копирај блок + Ископиран блок + Подешавања блока + Блок залепљен + Блок дуплиран + Блок исечен + Блок копиран + Наслов сајта може да измени само корисник са администраторском улогом. + Тема + Несачуване измене + Отвори везу у прегледачу + Двапут тапни за одабир опције + %1$s · %2$s · %3$s + Аутор странице + Користи овај GIF + Сви + Ја + Одбаци + Није постављено + Датум објаве + Додај ознаке + Назад + Сачувај сада + Предај сада + Закажи сада + Објављује се + Ознаке + Датум објаве + Откажи + Пребаци у Нацрте + Пребаци чланак у Нацрте? + Одаберите своја интересовања + Одаберите своја интересовања + Готово + Одаберите неколико за наставак + Објављено + У отпаду + Заказано + Датум објаве + Прочитај објаву CCPA везану за приватност + Објава везана за приватност за кориснике из Калифорније + Статус & Видљивост + Ажурирај сада + %1$s · %2$s + Постави страницу чланака + Постави почетну страницу + \"%s није исправан корисник\" + Одабери страницу + Страница чланака + Статична почетна страница + Класичан блог + Прихвати + Неуспело учитавање страница + Подешавања почетне странице + Почетна страница + Неуспело ажурирање странице чланака + Страница чланак успешно ажурирана + Неуспело ажурирање почетне странице + Почетна страница успешно ажурирана + Добродошли на наш нови блог + Одабери боју + Додирни двапут за подешавања боја + исеци + Шта има ново у Вордпресу + Сазнај више + Убаци %d + Користи овај видео запис + Изабери видео запис + Копирајте адресу линка + Поделите путем + Није могуће поделити + Адреса везе је копирана + Настави + Управљај сајтовима + Шта има ново + Убаци + Настави + Копирај + Блог + Ура!\nСкоро готово + Откажи отпремање + субота + петак + четвртак + среда + уторак + понедељак + недеља + Тамна + Светла + Изглед + Шта људи кажу + Име пројекта + Не плачи јер је готово, насмеши се јер се догодило. + Заказано + Објављено + Направите чланак + Није повезано + Адреса сајта + Коментари + Праћења + Свиђања + Непрочитано + Не шаљи на отпад + Помери на отпад + Активност + Чланци и Странице + Опште + Додај нову картицу + Додај нову картицу статистике + Замени тренутни блок + Додај на крај + Додај на почетак + Додај блок пре + Додај блок после + Додај ознаку + Прати сајт + Филтрирај + Уреди видео запис + Аутор чланка + Креирај чланак + врло високо + високо + средње + ниско +   & %1$d %2$s + %1$s, %2$d %3$s + Направити чланак или страницу + %1$s. Тренутна вредност је %2$s + Померите слику напред + Померите слику назад + Особа која прави веб место + Не сад + Добро дошли у Вордпрес + Библиотека фотографија + Уклоњен одабир слике + ,Одабрано + Слика одабрана + Чланак блога + Додај нови + Објави + Синхронизуј сада + Овај чланак ће бити одмах синхронизован. + Спремни за синхронизацију? + Домен није доступан + -%s + Пошаљи верификациону електронску пошту + Страница сајта + Прати + Сачувано + Откриј + Свиђања + Ознаке + Веб места + %sQi + %sQa + %sT + %sB + %sM + %sk + Разгрупиши + Преведи + Наслов: + Додирни да сакријеш тастатуру + Додирни овде да се прикаже помоћ + Почни да пишеш… + Направи видео запис + Направи фотографију или видео запис + Направи фотографију + Величина + Исеци блок + Ресетуј блок + Прикажи одељак + %s блок. Празно + Наслов чланка. %s + Наслов чланка. Празно + Залепи URL + Отвори подешавања + Помери блок доле од реда %1$s до реда %2$s + Помери блок горе од реда %1$s до реда %2$s + Помери блок горе + Иконица помоћи + Сакриј тастатуру + Помери блок доле + Садржај… + Тренутна вредност је %s + Одабери свој уређај + Алтернативни текст + ДОДАЈ ВИДЕО ЗАПИС + Додај URL + ДОДАЈ СЛИКУ ИЛИ ВИДЕО ЗАПИС + ДОДАЈ СЛИКУ + ДОДАЈ БЛОК ОВДЕ + Додај опис + Јавила се непозната грешка. Молимо вас да покушате поново касније. + Обавештења + Искључи + Укључи + Обавештења за ову веб локацију + Обавештења за ову веб локацију + Додај слику или видео запис + Нисмо успели да закажемо чланак, али ћемо покушати поново касније. + Нисмо могли да отпремимо овај садржај. + Прављење прегледа… + Чување… + Направили сте измене на овом чланку које нису сачуване + Издање ове апликације + Обриши трајно + Верзија са другог уређаја + Ова радња се не може отказати. Корисничко име је можда већ ажурирано. + Ваше ново корисничко име је %1$s + Чување корисничког имена … + Промените корисничко име + Пажљиво! + Спремате се да промените своје корисничко име, које је тренутно %1$s%2$s%3$s. Нећете моћи да вратите своје корисничко име назад. + Перформансе и Брзина + Више + Побољшана Претрага + Jetpack Претрага + Искључено + Садржај + Видео хостинг без реклама + Укључено + Убрзавач сајта + Перформанса + Преузимања + Датотека + Преузимања датотеке + Статистика преузимања датотеке није бележена пре 28. јуна 2019. године. + Подели + Подразумевано + Десктоп + Иди назад + Иди напред + Датум и време + Искључено + 10 минута раније + 1 сат раније + Додај у календар + Обавештење + Прегледи + Чланак + %1$s: %2$s, %3$s: %4$s + Скупи + Рашири + Ставка скупљена + Ставка раширена + Уређивач + Скупи + Прошири + Потврдите адресу своје е-поште - упутства су послата на вашу е-пошту. + Потврдите адресу своје е-поште - упутства су послата на %s + Откажи + У реду + Уклонити везу + Уметнути везу + Отпремање медија.\nДодирните за опције. + Покушајте поново да отпремите + http(s):// + Отвори везу у новом прозору/језичку + Да би видели своју статистику, пријавите се на WordPress.com налог. + Нема чланака које се подударају са твојом претрагом + Овде ће вас људи наћи на интернету. + Изабери премиум назив домене + На први поглед + Данас + Све време + Сви WordPress.com пакети укључују жељени назив домена. Региструјте свој бесплатни премиум домен одмах. + Претражи чланке + Све време + Прегледи ове недеље + Тип + Боја + Одаберите свој сајт + Тамно + Светло + Боја + Одаберите свој сајт + Сајт + Прегледи ове седмице + Додај виџет + Покрајина (није доступно) + Лозинка ажурирана + Ажурирај лозинку + Одабери државу + Одабери покрајину + Честитамо + Региструјте домен + Поштански број + Покрајина + Град + Адреса 2 + Адреса + Држава + Код државе + Телефон + настави + Организација (опционо) + Региструј јавно + Региструј приватно са Заштитом приватности + Заштита приватности + Молимо унесите исправан %s + Одаберите домен + Ново + Одбаци + Испробајте сада + Управљајте својим статистикама + Ваш нарцт се отпрема + Отпремање нацрта + Нацрти + Региструј домен + Нема пронађених предлога + Годишње статистике сајта + Друштвено + Помери доле + Помери горе + Чланак враћен + Чланак се пребацује у нацрт + Чланак се враћа + Чланак се пребацује у отпад + Локалне измене + Уклони из увида + Пребаци у Нацрт + Немате чланке у нацртима + Немате чланке у отпаду + Немате заказане чланке + Још увек нисте објавили ниједан чланак + Молимо, пријавите се са својим корисничким именом и лозинком. + Укупно коментара + Просечно коментара / чланак + Просечно речи по чланку + Укупно речи + Просечно свиђања по чланку + Укупно свиђања + Чланци + Година + Ове године + Инсталирај додатак + Региструј домен + Просечно прегледа по дану + Прегледи + Период + Месечна и годишња + Објави: %s + Објављено: %s + Заказано за: %s + Закажи за: %s + Учитај више + Скорашње седмице + Данас + Најбољи дан + Најбоље време + Приказивање статистике за: + Не, хвала + Касније + Оцените сада + Лепо је видети те опет! Ако копате по апликацији, волели бисмо оцену у Гугл Плеј продавници. + Уживате у Вордпресу? + Више чланака + Мање чланака + Сајт још увек није учитан + Користи блоковски уређивач + Нема везе + Пребаци на блоковски уређивач + Пребаци на класични уређивач + Подаци нису учитани + Блоковски уређивач омогућен + излаз + Увећајте свој аудиторијум + Прилагодите свој сајт + Наредни кораци + Завршено (%d) + Уживајте у свом готовом производу! + Отпреми иконицу сајта + %1$d од %2$d завршено + Направи нацрт и објави свој први чланак + Истражи планове + Омогући дељење чланака + Направити нову страницу + Уклони наредне кораке + Уклони ово + Провери статистику свог сајта + Прескочи задатак + Сви задаци су завршени! + скупи + прошири + Подсетник + Најпопуларније време + Одабери наредни период + Одабери претходни период + %1$d%% од прегледа + %1$s (%2$s%%) + +%1$s (%2$s%%) + Очисти + Дошло је до проблема + Креирај сајт + Креирај сајт + Претражи домене + Дошло је до проблема + Дошло је до проблема + Ваша страница је креирана! + %1$d од %2$d + Креирај сајт + Предлози ажурирани + Конфликт верзија + Омогућите аутоматске извештаје о падовима који ће нам помоћи да побољшамо перформансе апликације. + Врати + Ажурирање чланка + Локална верзија одбачена + Веб верзија одбачена + Одбаци веб + Одбаци локално + Реши синхронизациони конфликт + Нема података за овај период + Уклони локацију из садржаја + Tumblr + Twitter + Facebook + Види више + Подели чланак + Креирај чланак + Прошло је %1$s од када је %2$s објављен. Ево какви су досадашњи резултати чланка: + Прегледи + Нема садржаја који се поклапају са Вашом претрагом + Аутор + Аутори + Прегледи + Термин за претрагу + Термини за претрагу + Прегледи + Наслов + Видео записи + Прегледи + Држава + Државе + Кликови + Веза + Кликови + Прегледи + Препоручилац + Препоручиоци + Чланци и Странице + Путања + LinkedIn + Google+ + Све време + Прошло је %1$s од када је %2$s објављен. Покрени причу и увећај прегледе свог чланка дељећи га: + Ознаке и категорије + Нисте до сада објавили ни један чланак. Кад почнете да објављујете, резиме Вашег најновијег чланка ће се појавити овде: + Коментари + Наслов + Аутор + Чланци и странице + Аутори + %1$s | %2$s + Пратиоци + %1$s - %2$s + Услуга + Прегледи + Наслов + Прегледи + Наслов + Од + Пратилац + Укупно %1$s пратилаца: %2$s + Електронска пошта + WordPress.com + Управљај увидима + Још нема података + Ниједан увид још увек није додан + Изборник отклањања грешака + Лозинка се мења… + Лозинка успешно промењена + Промени лозинку + Назив + (без наслова) + HTML преглед + Визуални преглед + Ревизија + Претходно + Следеће + %1$s искоришћено + Учитај + Чланак креиран %1$s у %2$s + Страница креирана %1$s у %2$s + Ревизије се учитавају + Ревизије су учитане + Још увек нема историјата + Када направите измене на свом чланку, бићете у могућности да видите историјат овде + Када направите измене на својој страници, бићете у могућности да видите историјат овде + Кориснички аватар + Пуна величина + Велико + Средње + Умањена слика + Историја + Одабрана страница није доступна + Чека се рецензија + Претражи странице + Трајно обриши + Пребаци у Отпад + Пребаци у Нацрт + Нема страница које одговарају Вашој претрази + Немате страница у отпаду + Немате заказаних страница + Немате страница у нацртима + Још увек немате ниједну објављену страницу + У отпаду + Заказано + Нацрти + Објављено + Постави родитеља + Види + Нема сајтова који се поклапају са Вашом претрагом + Не постоји WordPress.com налог који се поклапа са овим Google налогом. + Нема сајтова који се поклапају са Вашом претрагом + Родитељ странице је промењен + Страница је заказана + Страница је трајно обрисана + Страница је објављена + Страница је бачена у отпад + Страница је премештена у Нацрте + Највиши ниво + Постави родитеља + Догодио се проблем приликом брисања странице + Догодио се проблем приликом промене статуса странице + Догодио се проблем приликом промене родитеља странице + Да ли сте сигурни да желите да обришете страницу %s? + додирни овде + Направите и покрените своју веб локацију. + Креирај свој сајт + Види свој сајт + Подели свој сајт + Додирни %1$s Види сајт %2$s за преглед свог сајта + Додирни %1$s Дељење %2$s за наставак + Прихвати + Објави чланак + Прилагодите свој сајт + Никад + Не хвала + Прати друге сајтове + Одаберите тему + Крени + Откажи + Не сада + Више + Немате ниједан сајт + Нема праћених ознака + Додајте ознаке како бисте пронашли чланке о својим омиљеним темама + Покушај поново + Јавио се проблем + Jetpack инсталиран + Jetpack се инсталира + Инсталирај Jetpack + Jetpack + Jetpack Често Постављана Питања + Jetpack није могуће инсталирати у овом тренутку. + Jetpack се инсталира на Ваш сајт. Ово може да потраје неколико минута. + Пријавите се на WordPress.com налог који сте користили за повезивање Jetpack-а. + Постави + Одјавите се из Вордпреса? + Направити ознаку + Немате ниједну ознаку + Нема ознака које се поклапају са Вашом претрагом + Шта бисте желели да пронађете? + Нема тема које се поклапају са Вашом претрагом + Нема садржаја који се поклапа са Вашом претрагом + Још увек нема пратилаца + Још увек нема корисника + Још увек нема пратилаца електронске поште + Још увек нема гледалаца + Чланци који Вам се свиђају ће се појавити овде + Откриј сајтове + Нема праћених сајтова + Још нема свиђања + Још нема пратилаца + Још увек нема активности + Направити чланак + Направити страницу + Отпреми садржај + Немате никакав садржај + Галерија слика + иконица сајта + слика теме + издвојена слика + Одбаци + профилна слика + Да бисте наставили, унесите своју адресу е-поште и име + Молимо, унесите вашу адресу е-поште + Електронска пошта + Нова порука од \"Помоћи и Подршке\" + Вордпрес + Није подешено + Моји тикети + Пролазан + Дневник апликације + Премотавање у току + Премотавање на %1$s %2$s + Премотај сајт + Тренутно се Ваш сајт враћа у претходно стање + Ваш сајт је успешно враћен у претходно стање + Ваш сајт је успешно враћен у претходно стање\nПремотан на %1$s %2$s + Ваш сајт се враћа у претходно стање\nПремотавање на %1$s %2$s + Акционо дугме дневника активности + Аутоматско управљање + Нема пронађених резултата + Сачувај чланке за касније + Није могуће извршити претрагу + Прочитајте изворни чланак + Сајтови + Регистрација електронском поштом + Верификација кодом + Креденцијали за пријаву + Алтернативно: + Уклоњено + Сачувани чланци + Додато у сачуване чланке + Уклоњено из сачуваних чланака + Види све + Сачувано + Сачувај + Чланак сачуван + Нема сачуваних чланака - још увек! + Омогући + ово веб место + Иконица сајта + Немате дозволу да додате иконицу сајта. + Да ли желите да додате иконицу сајта? + Како бисте желели да уредите иконицу? + Уклони + Промени иконицу сајта + Откажи + Промени + Немате дозволу да уређујете иконицу сајта + Активност + Иконица активности + Омогући обавештења за %1$s%2$s%3$s? + Укључи обавештења сајта + Искључи обавештења сајта + Иконица Jetpack-а + Догађај + Дневник активности + Прочитај политику приватности + Политика приватности + Политика колачића + Подешавања приватности + Прикупи информације + Премотај + Чланак је предат + Немате налог? %1$sРегиструјте се%2$s + Могућност додатка захтева да сајт буде добро позициониран. + Могућност додатка захтева да претплата на главни домен буде везана за корисника. + Функција додатка захтева да страница буде јавна. + Могућност додатка захтева администраторске привилегије. + Додатак не може бити инсталиран на VIP сајтове. + Додатак не може бити инсталиран због ограничења простора на диску. + Могућност додатка захтева верификовану адресу електронске поште. + Могућност додатка захтева бизнис план. + Могућност додатка захтева поседовање сопственог домена. + Финална подешавања су у току - још мало, па готово… + Инсталирање додатка… + Инсталирај + Обавештења + Примајте обавештења о новим чланцима са ове странице + Пошаљите ми нове коментаре е-поштом + Инсталирај додатак + Седмично + Истог тренутка + Дневно + Нови чланци + ошаљи ми нове чланке електронском поштом + %s на %s + Сви моји праћени сајтови + Праћени сајтови + Да ли сте сигурни да желите да трајно обришете овај чланак? + Важно + Опште + Користи ову фотографију + %1$d од %2$d + Фотографије обезбеђене од стране %s + Додај %d + Прегледај %d + Претражите библиотеку бесплатних фотографија + Одаберите из библиотеке бесплатних фотографија + Није могуће сачувати празан нацрт + %1$s од неограниченог + Направити ознаку + Обавештења + квачица + Профилна слика од %s + прикажи више + Отвори спољну везу + фотографија + обриши + Пусти видео запис + пусти издвојени видео запис + лого додатка + банер додатка + покрени камеру + одабери са уређаја + информације о улози + пусти + преглед слике + преглед + звучни запис + пусти видео запис + отпад + покушај поново + уклони %s + Регистровање са Google-ом… + Већ сте повезани на Jetpack + Неуспело повезивање на Jetpack: %s + %s TB + Сачувај као нацрт + Преглед + ВРАТИ + Пребачено на HTML режим + HTML режим + Пребачено на визуални режим + Визуални режим + Нови налог + Коментар није одобрен + Коментар је одобрен + Коментар је означен да није нежељен + Коментар је означен као непожељан + Коментар је обрисан + Коментар је враћен + Коментар је послат у смеће + Коментар свиђан + Детаљ обавештења %s + Уреди фотографију + Одабери сајт + %s GB + %s MB + %s kB + %s B + %1$s од %2$s + Искоришћен простор + Ако Вам је потребан додатни простор, размислите о надоградњи Вордпрес плана. + Садржај + Обавештења + Дугмићи за дељење + Пријављен као + Детаљи особе + Детаљи датотеке + Читач + Ја + Мој сајт + Подешавања обавештења + Помоћ и Подршка + Лиценца + Издање %s + Допуштења + Издвојено + © %1$d %2$s + Модул дељења је онемогућен + Објављивач: %s + QP %s + Преостало %1$d страница / чланака + Преостала 1 страница + Преостало %1$d страница + Преостало %1$d чланака + Преостало %1$d страница / чланака и 1 датотека + Преостало %1$d чланака и 1 датотека + Преостало %1$d страница и 1 датотека + Преостао 1 чланак и 1 датотека + Преостала 1 страница и 1 датотека + Преостало %1$d страница / чланака и %2$d од %3$d датотека + Преостало %1$d чланака и %2$d од %3$d датотека + Преостало %1$d страница и %2$d од %3$d датотека + Преостао 1 чланак и %1$d од %2$d датотека + Преостала 1 страница и %1$d од %2$d датотека + %1$d чланака / страница није отпремљено + %1$d страница није отпремљено + 1 страница није отпремљена + %1$d чланака није отпремљено + 1 чланак није отпремљен + %1$d чланака / страница са %2$d датотека није отпремљено + %1$d страница са %2$d датотека није отпремљено + 1 страница са %1$d датотека није отпремљена + %1$d чланака са %2$d датотека није отпремљено + 1 чланак са %1$d датотека није отпремљен + %1$d чланака / страница са 1 датотеком није отпремљено + %1$d страница са 1 датотеком није отпремљено + 1 страница са 1 датотеком није отпремљена + %1$d чланака са 1 датотеком није отпремљено + 1 чланак са 1 датотеком није отпремљен + (Без наслова) + \@%s + %1$f, %2$f + Направити веб локацију + Додирните за наставак. + Веб локација направљена! + Промени корисничко име + Куцај за више предлога + Одбаци + Одбаци промену корисничког имена? + Сачувај + Додај аватар + Региструј се са Google-ом + Региструј се са електронском поштом + Електронска пошта већ постоји на WordPress.com.\nНаставите са пријавом. + Ажурирање налога… + Слање електронске поште + Регистровањем, прихватате наша %1$sПравила коришћења%2$s. + Да бисте креирали нови WordPress.com налог, унесите своју адресу е-поште. + Покушај поново + Затвори + Корисничко име + Лозинка (опционо) + Име које се приказује + Покушај поново + Врати претходно + Региструј се за WordPress.com + Пријави се са својим корисничким именом + Пријави се уношењем адресе свог сајта + Нема резултата + Направите нову веб локацију за своје предузеће, часопис или лични блог; или повежите постојећу Вордпрес инсталацију. + Потребно је ажурирање + Претражи додатке + Нови + Популарни + Види све + Управљај + Претрага додатака није успела + Грешка приликом инсталирања %s + Успешно инсталиран %s + Инсталирај + Свиђа ми се + Додај нови сајт + Инсталирај Jetpack + Ваша верзија Вордпреса + Захтева Вордпрес верзију + Последњи пут ажурирано + Верзија + 5 звездица + 4 звездице + 3 звездице + 2 звездице + 1 звездица + %s преузимања + %s оцена + Читај рецензије + Често постављана питања + Шта је ново + Инсталација + Опис + Подешавања + Инсталирано + Верзија %s инсталирана + Верзија %s + од %s + Промените фотографију + Није у могућности да учита додатке + Странице сајта + Снимање + Брисање + Трајно обриши ознаку \'%s\'? + Ознака са овим именом већ постоји + Ознака + Додај нову ознаку + Опис + Није могуће учитати временске зоне + Прилагођен облик + Прилагођено + Чланака по страници + Одаберите град у Вашој временској зони + Временска зона + Облик времена + Облик датума + Седмица почиње у + Ознаке + Саобраћај + Обриши + Спољна веза + Иконица додатка + Почетна страница додатка + WordPress.org страница додатка + Уклони додатак + Уклања се %s… + Онемогућава се %s… + Појавила се грешка приликом конфигурисања додатка: %s + Грешка приликом уклањања %s + Успешно уклоњен %s + Грешка приликом ажурирања %1$s: %2$s + Грешка приликом ажурирања %s. + Успешно ажуриран %s + Верзија %s је доступна + Аутоматска ажурирања + Укључено + Додаци + Додаци + Укључен + Искључен + Отвори везу у новом прозору/језичку + Веза ка + Дошло је до грешке. + Молимо проверите своју лозинку за наставак. + Пријављивање обустављено + Молимо сачекајте док траје пријављивање. + Пријављивање у току… + Додирни за наставак. + Успешна пријава! + Молимо унесите лозинку + Величина + Преостала 1 датотека + Преостало %1$d од %2$d датотека + Преостао 1 чланак + Отпремање… + Напиши чланак + %d датотека успешно отпремљено + , %d успешно отпремљено + 1 датотека отпремљена + 1 датотека није отпремљена + %d датотека отпремљено + %d датотека није отпремљено + Уклони из чланка + Уклони ову слику из чланка? + Прилагоди + Детаљи датотеке + Улогуј се са Google-ом. + Затвори + Уклони издвојену слику + Одабери издвојену слику + Улогуј се на WordPress.com како би поделио садржај. + Прекинута веза сајта + Грешка приликом прекида везе сајта + Прекини везу + Да ли сте сигурни да желите да прекинете везу Jetpack-а са сајтом? + Дозволи WordPress.com пријаву + WordPress.com пријава + Пошаљите пуш обавештења + Пошаљите обавештења е-поштом + Безбедност + Jetpack подешавања + Додаје се + Одабери сајт + Додај у библиотеку садржаја + Додај новом чланку + Неисправан IP или IP распон + Брише се + Обриши овај видео запис? + Обриши ову слику? + Детаљи документа + Детаљи звучног записа + Детаљи видео записа + Детаљи слике + Преглед + Датум отпремања + Трајање + Димензије видео записа + Димензије слике + Тип датотеке + Име датотеке + URL + Резервни текст + Трептање светла + Повежи сајт + Вибрација уређаја + Одабери звук + Електронска пошта од WordPress.com + Обавести ме о нацртима на чекању + Коментари на другим сајтовима + Остало + Сви моји сајтови + Ваши сајтови + Омогући обавештења + Онемогући обавештења + Искључено + Укључено + Највећа величина видео записа + Највећа величина слике + Посети %s за више + Додавање садржаја + Отпремање неуспело за \"%s\" + Ваш чланак се отпрема + Отпремање садржаја… + Чланак заказан + Страница заказана + Покушај поново + Чланак на чекању + Отпремање \"%s\" + Веза са сервером је изгубљена + Моји сајтови + Мој сајт + Молимо унесите верификациони код + Унесите корисничко име + Улогујте се на WordPress.com како би приступили чланку. + Грешка приликом додавања сајта. Код грешке: %s + Потребна додатна помоћ? + Која је адреса мог сајта? + Потребна помоћ приликом тражења адресе сајта? + Адреса сајта + Унесите адресу Вордпрес сајта који бисте желели да повежете. + Већ сте пријављени на WordPress.com + Унесите своју WordPress.com лозинку. + Настави + Повежи други сајт + Скоро смо тамо! Унесите верификациони код из апликације Authenticator. + Следећи + Отвори пошту + Укључи оптимизацију слика + Профилна слика + Остави искључено + Укључи + Неочекивани одговор од сервера + Наслов + Понови + Врати + Поделите своју причу овде… + Приватно + Нацрт + Чека рецензију + Објави + Промени локацију + Уклони локацију + Локација + Сада + Ознаке + Облик чланка + Подложак + Одломак + Није подешено + Више опција + Категорије & ознаке + Све + Највиши ниво + Родитељска категорија (опционо): + Немате ниједан звучни запис + Немате ниједан документ + Немате ниједан видео запис + Немате ниједну слику + Одзив сервера је трајао предуго + Датотека је превелика да би се отпрмила на овај сајт + Датотека премашује максималну величину за отпремање на овом сајту + Видео запис је превелик за отпремање + Звучни запис + Видео записи + Документи + Слике + Све + Погледајте коментаре + Оптимизуј видео записе + Нацрти отпремљени + Пријави грешку + Квалитет видео записа + Камера + Складиште + Уреди допуштења + Дозволи + Потребна дозвола за %s за приступ Вашим фотографијама + Повезивање налога + Повезано + Twitter + Свиђања + Дугмад + Уреди дугме \"Више\" + Дугме \"Више\" садржи падајући мени који приказује дугмад за дељење + Корисничко име на Твитеру + Свиђања коментара + Дугмићи за дељење + Етикета + Стил дугмета + Прикажи дугме свиђања + Само текст + Званична дугмад + Само иконица + Иконица & текст + Повезивање %s + Прекини повезаност са %s? + Повежи други налог? + Поново повежи + Прекини повезаност + Повежи + Повезани налози + Дељење + Обавештења. Управљајте обавештењима. + Повезаности + Дељење + Не сада + Нацрт сачуван на уређају + Нацрт сачуван онлајн + Чланак сачуван на уређају + Синхронизуј + Одабери видео запис са уређаја + Одабери фотографију са уређаја + Додај као галерију + Додај више фотографија + %d колоне + 1 колона + Поново пошаљи електронску пошту + Молим вас потврдите вашу адресу е-поште + Чување чланка као нацрт + Направи видео запис + Направи фотографију + Обриши сајт? + Није могуће послати електронску пошту + Одабери фотографију + Максималан + Врло висок + Висок + Средњи + Низак + Отпремљено + Неуспело отпремање + Обрисано + Брише се + Отпрема се + На чекању + Квалитет слике + Непознат облик чланка + Предај + Није могуће учитати садржај + Садржај није могуће пронаћи + Оптимизуј слике + Покушај поново + Потврди + Претражи праћене сајтове + Ваш нацрт \'%1$s\' Вас чека - не заборавите да га објавите! + Да ли сте знали да је \'%1$s\' још увек нацрт? Објавите га! + Посети + Превуците за више + Није вам дозвољено да погледате овај пост. Пробајте да се пријавите у WordPress.com прво или искористите дугме на врху да отворите пост у интернет прегледачу. + Није вам дозвољено да гледате овај пост. Пробајте да се пријавите у WordPress.com прво. + Није вам дозвољено да гледате овај пост. Кликните на дугме на врху да отворите пост у интернет прегледачу. + Не можете да лајкујете док се не улогујете у WordPress.com + Већ сте лајковали овај коментар + Коментар није пронађен! + Пост није лајкован + Није одговорено на коментар. Молимо вас да покушате касније. + Коментар није одобрен. Молимо вас да покушате касније. + Коментар није лајкован. Молимо вас да покушате касније. + Додирните екран да бисте их приказали + Нова обавештења + Одговарам… + Одобравам… + Лајкујем… + Обрађујем… + Акција одрађена! + Коментар лајкован + Одјави се + Пријави се у WordPress.com + Више на WordPress.com + Више у %s + Отвори подешавања телефона + %s: Неисправан емаил + %s: Корисник је блокирао позивнице + %s: Већ прати + %s: Већ је члан + %s: Корисник није пронађен + Коментар одобрен! + Лајк + сада + Посетилац + Пратилац + Нема конекције, нисмо могли да сачувамо ваш профил + Десно + Лево + Без + Изабрани %1$d + Нисмо могли да прикажемо кориснике сајта + Емаил Пратилац + Пратилац + Преузимам кориснике… + Гледаоци + Пошаљите е-пошту пратиоцима + Пратиоци + Тим + Позовите до 10 адреса е-поште и/или корисничких имена са WordPress.com. Онима којима је потребно корисничко име биће послата упутства како да га креирају. + Ако га уклоните, овај пратилац ће престати да добија обавештења о овом сајту, осим уколико не крену поново да га прате. Да ли и даље желите да уклоните овог пратиоца? + Од %1$s + Пратилац није уклоњен + Није могуће уклонити прегледач + Нисмо могли да преузмемо емаилове пратиоца сајта + Нисмо могли да преузмемо пратиоце сајта + Неки од медија које сте послали нису успешно послати. У овом тренутку не можете да се пребаците у HTML мод.\nЖелите ли да уклоните све неуспешне медије и да наставите? + Ширина + Линк ка + Натпис + Промене сачуване + Одбаци сачуване промене? + Заустави слање? + %1$s: %2$s + Притисните да пробате поново! + Позивница је успешно послата + Позивнице су послате али је дошло до грешака! + Дошло је до грешке приликом слања позивница! + Неисправни емаил-ови или корисничка имена: позивнице нису познате + Корисничко име или емаил нису исправни: позивница није послата + Молимо вас додајте бар једно корисничко име + (Опционо) Можете да додате личну поруку дужине до 500 карактера која ће бити укључена у позивницу корисницима. + %d карактера је преостало + 1 карактер је преостао + 0 карактера је преостало + Лична порука + Позови + Корисничка имена или емаилови + Позови људе + Пошаљи линк + Спољни + Обриши историју претраге + Обриши историју претраге? + Претражи WordPress.com + Линкови су искључени на демо екрану + Пошаљи + Успешно уклоњен %1$s + Ако уклоните %1$s, тај корисник више неће моћи да приступи сајту, али ће сав садржај који је креирао %1$s остати на сајту. \n\nДа ли и даље желите да уклоните овог корисника? + Уклони %1$s + Улога + Људи + Сајтови на овој листи нису ништа објавили неко време + Корисник није уклоњен + Није промењена улога корисника + Грешка приликом мењања вашег Граватара + Грешка током учитавања вашег Граватара + Грешка приликом налажења исечене слике + Грешка током сечења слике + Проверавам емаил + Тренутно недоступно. Молимо вас унесите вашу шифру + Пријављивање + Унесите вашу шифру + Приказано свима када коментаришете. + Сликај камером или изабери слику + Планови + План + Ваши постови, стране и подешавања ће вам бити послата емаилом на %s. + Извезите ваш садржај + Извозим садржај… + Прикажи купљено + Премијум унапређења + Бришем сајт… + Обриши сајт + Главни домен + Дошло је до грешке приликом брисања вашег сајта. Молимо вас контактирајте подршку за више информација + Грешка током брисања сајта + Молимо вас унесите %1$s у поље испод да потврдите. Ваш сајт ће затим бити заувек обрисан. + Извези садржај + Потврди брисање сајта + Контактирајте подршку + Ако желите веб локацију, али не желите чланке и странице које имате сада, наш тим за подршку може за вас избрисати ваше чланке, странице, медије и коментаре.\n\nОво ће одржавати вашу веб локацију и УРЛ активним, али ће вам дати нови почетак стварања садржаја. Само нас контактирајте како бисмо уклонили ваш тренутни садржај. + Пустите нас да помогнемо + Почни поново + Подешавања апликације + Уклони неуспела слања + Напредно + Нема коментара у смећу + Нема коментара који чекају + Нема одобрених коментара + Центар + Видео + Статус + Стандардни + Цитат + Линк + Слика + Галерија + Ћаскање + Звук + Са стране + Заједница + Предлози + Одговори на моје коментаре + Истражи + Предлози корисничког имена + Достигнућа интернет места + Свиђања мојих чланака + Праћена сајта + Лајкови на мојим коментарима + Коментари на мом веб месту + %d предмета + 1 предемт + Сви корисници + Коментари познатих корисника + Нема коментара + %d коментара по страни + 1 коментар по страни + Потребно допуштење за више од 0 веза + Аутоматски одобри коментаре свих. + Аутоматски одобри ако корисник има претходно одобрени коментар + Захтевајте ручно одобрење за коментаре свих. + 1 дан + %d дана + Веб адреса + Примарно веб место + Тренутно није могуће освежити коментаре - приказују се старији коментари + Постави издвојену слику + Издвојена слика + Адреса е-поште + Трајно избрисати ове коментаре? + Трајно обриши овај коментар + Обриши + Коментар обрисан + Поврати + Нема нежељених коментара + Сви + Није могуће учитати страницу + Искључено + Језик интерфејса + О апликацији + Није било могуће сачувати Ваша подешавања налога + Није било могуће повратити Ваша подешавања налога + Није било могуће повратити Ваш налог + Код језика није препознат + Онемогућено + Претрага + Уклони + Изворна величина + Ваша локација је видљива свима, али тражи од претраживача да је не индексирају + Ваша локација је видљива свима и претраживачи је могу индексирати + Неколико речи о вама … + Име за приказ подразумевано ће бити ваше корисничко име ако није другачије постављено + О мени + Јавно име за приказ + Мој профил + Презиме + Име + Није могуће сачувати информације о сајту + Није било могуће повратити информације о сајту + Аутоматски затвори + Аутоматски затвори коментаре на чланцима. + Коментара по страници + Затвори коментарисање + Унесите реч или фразу + Нема ставки + У \"Ажурирању\" + У \"Апликацијама\" + У \"Мобилном\" + Прикажи слике + Прикажи заглавље + Прикажи везане чланке + Аутор коментара мора имати претходно одобрени коментар + Корисници морају бити регистровани и пријављени да би могли коментарисати + Аутор коментара мора попунити име и адресу е-поште + Прикажите коментаре у комадима одређене величине + Омогућите угнежђене коментаре до одређене дубине + Утврђује редослед приказа коментара + Забрани коментаре након наведеног времена + Омогућите читаоцима да постављају коментаре + Види сва могућа подешавања Расправе + Прикажи или сакриј везане чланке у читачу + Поставља нови облик чланка + Поставља нову категорију чланка + Измените своју лозинку + Тренутни кориснички налог + Језик у ком је овај блог првенствено написан + Контролише ко може да види Ваш сајт + Промена адресе тренутно није подржана + Коментари познатих корисника + Коментари свих корисника + Нивои %d + Приватно + Сакривено + Јавно + Обриши сајт + Црна листа + Везе у коментарима + Аутоматски одобри + Сортирај по + Корисници морају бити улоговани + Мора да садржи име и е-пошту + Примај повратне везе + Шаљи повратне везе + Дозволите коментаре + Подразумевани облик + Подразумевана категорија + Адреса + Наслов сајта + Подразумеване ствари за нове чланке + Писање + Налог + Уопштено + Прво најновије + Коментари + Прво најстарије + Затвори након + Везани чланци + Приватност + Расправа + Непознато + Никад + Овај чланак више не постоји + Нема скорашњих чланака + Одабрана тема + Није могуће учитати тему + Нешто је пошло по злу. Није било могуће активирати тему + од стране %1$s + Хвала што сте одабрали %1$s + УПРАВЉАЈ САЈТОМ + ГОТОВО + Подршка + Детаљи + Види + Испробај & Прилагоди + Укључи + Укључено + Подршка + Детаљи + Прилагоди + Тренутна тема + Чланак је објављен + Страница ажурирана + Чланак ажуриран + Страница објављена + Жао нам је, ниједна тема није пронађена + Учитај више чланака + Претражи сајтове + Идите у Читач + Будите примећени: коментаришите чланке које сте прочитали + Придружите се разговору: коментаришите чланке блогова које пратите + Будите активни! Коментaришите чланке блогова које пратите. + Још увек нема коментара + У току сте са свим! %s коментара 1 коментар Одговор на чланак… @@ -16,6 +1572,8 @@ Language: sr_RS %s свиђања 1 свиђање Свиђање + %,d пратилаца + Уреди ознаке и сајтове Чланак читача Подешавања за обавештења која се појављују на вашем уређају. Подешавања за обавештења која су послата на е-пошту повезану са вашим налогом. @@ -27,26 +1585,35 @@ Language: sr_RS Е-пошта Обавештења апликације Језичак обавештења + Увек ћемо слати важну е-пошту везану за ваш налог, али такође можете да добијете неке додатке који могу да Вам помогну. Резиме последњег чланка Нема везе Чланак је послат на отпад - Статистике Отпад Предпреглед Поглед Уређивање Објављивање + Статистика + Нисте овлашћени да приступите овом веб месту + Овај сајт не може бити пронађен Врати - Најбољи погледи икада + Захтев је истекао. Пријавите се на WordPress.com да бисте покушали поново. Занемари - Данашње статистике - Чланци, погледи и посетиоци целог времена + Најбољи прегледи икад + Данашња статистика + Чланци, прегледи и посетиоци за све време Увиди + Одјави се са WordPress.com Пријава/одјава - \"%s\" није сакривен зато што је тренутно веб место + Пријави се на WordPress.com Помоћ и подршка + „%s“ није сакривен зато што је тренутно веб место + Подешавања налога + Направи WordPress.com сајт Прикажи/сакриј веб места Додај самостално угошћено веб место + Додај нови сајт Види управљање Види веб место Одабери веб место @@ -64,6 +1631,7 @@ Language: sr_RS Језик Код за потврђивање Погрешан верификациони код + Пријавите се поново за наставак. Објављивач: Није могуће отворити обавештење Непознати термини за претрагу @@ -77,7 +1645,8 @@ Language: sr_RS Извештај о променама у апликацији је копиран у оставу Нови чланци Дошло је до грешке приликом копирања текста у оставу - Веб места која би могла да вам се свиде + Отпремање чланка + Овај сајт је празан Добављам теме… %1$d месеци Година @@ -94,11 +1663,11 @@ Language: sr_RS Пратиоци Државе Свиђања - Година Прегледа Посетиоци Чланци и стране - Publicize + Објави + Годишња Детаљи %d изабрано Нема коментара @@ -117,13 +1686,27 @@ Language: sr_RS Коментар Коментар померен на отпад Не постоје чланци. Зашто не бисте направили нови? - Одговор %s Немате дозволу да прегледате или уређујете стране Прегледајте наша ЧПП - Није могуће обавити ову радњу + Одјава… + Одговорите %s + Нема чланака са овом темом + Није могуће обавити овај поступак + Није могуће блокирати ову страницу + Блокирај овај сајт + Чланци са овог сајта више неће бити приказивани Заказати Ажурирај Веб места која пратите + Унесите URL или ознаку за праћење + Сајт запраћен + Није могуће приказати овај сајт + Већ пратите овај сајт + Није могуће пратити овај сајт + Није могуће отпратити овај сајт + Нема препоручених сајтова + Сајт читача + Праћене теме Помоћ Изгубили сте лозинку? Неисправна SSL потврда @@ -149,6 +1732,7 @@ Language: sr_RS Уграђена SD картица је неопходна за отпремање медија Чланци тренутно не могу бити освежени Није непожељен коментар + Дошло је до грешке приликом брисања чланка Одаберите категорије Подешавања стране Грешка у успостављању везе @@ -175,16 +1759,13 @@ Language: sr_RS Неопходно овлашћење Нови чланак Нови медиј - Полиса приватности Локалне измене Подешавања слике Неки медији тренутно не могу бити обрисани. Пробајте поново касније. Текст везе (опционо) Локални нацрт Подешавања чланка - Дозволе Отвореног кода Вордпрес блог - Овај блог је сакривен и не може се учитати. У подешавањима поново омогућите да буде видљив и пробајте поново. Додај нову категорију Име категорије Није могуће направити привремену датотеку за отпремање медија. Потврдите да имате довољно слободног простора за складиштење на свом уређају. @@ -196,13 +1777,25 @@ Language: sr_RS Непожељни коментар Дошло је до грешке током учитавања чланка. Освежите списак чланака и пробајте поново. Уклони веб место + Дошло је до грешке приликом приступа овом додатку. + Није могуће пронаћи датотеку за отпремање. Да ли је можда обрисана или померена? + Проверите да ли је унета УРЛ адреса тачна + Тренутно није могуће освежити медије + Политика приватности + Лиценце отвореног кода + Овај блог је скривен и не може се учитати. Поново га омогућите у подешавањима и покушајте поново. + Нема обавештења … још увек. + Обриши страницу? + Обриши чланак? + Није могуће додати ову тему + Није могуће уклонити ову тему Подели везу Добављам чланке… Коментар означен као непожељан Одговор - Не можете делити на Вордпрес без видљивог блога Вама и %,d других се ово свиђа - %,d људи се ово свиђа + Не можете делити на Вордпресу без видљивог блога + %,d људи ово воли Одаберите фотографију Одаберите видео снимак Није могуће добавити овај чланак @@ -221,7 +1814,10 @@ Language: sr_RS Није могуће отворити %s Овај списак је празан Одговор на коментар… - Недељу дана + Још увек нема коментара + Пријави се + Већ пратите ову тему + Ово није исправна тема Квадрати Кругови Наслов @@ -230,21 +1826,25 @@ Language: sr_RS Извори упућивања Данас Јуче - Дани - Месеци Подели - Статистике - Притискања Теме - Поплочан Ажурирање неуспешно Укључи Приказ слајдова Ознаке и категорије + Статистика + Кликови + Плочице + Месечна + Недељна + Дневна Управљајте + Одбаци Успешно одговорено %d нових обавештења и %d још. + Пријава + Праћења Учитавање… HTTP корисничко име HTTP лозинка @@ -252,12 +1852,14 @@ Language: sr_RS Неисправно корисничко име или лозинка. Лозинка Корисничко име + Пријавите се Читач Стране Нема доступне мреже Чланци Анониман У реду + обављено URL Вордпрес за Андроид Услови сервиса @@ -270,11 +1872,15 @@ Language: sr_RS Подеси име пречице Подешавања Име пречице не може бити празно + Приватна Наслов Категорије + Одвојите ознаке зарезима + Брисање коментара SD картица неопходна Медији Обриши + Одобри Ниједан Грешка Откажи @@ -287,4 +1893,5 @@ Language: sr_RS Да Не Подешавања обавештења + Објави одмах diff --git a/WordPress/src/main/res/values-sv/strings.xml b/WordPress/src/main/res/values-sv/strings.xml index 7cf8a8c34ebd..b37d2a68f26e 100644 --- a/WordPress/src/main/res/values-sv/strings.xml +++ b/WordPress/src/main/res/values-sv/strings.xml @@ -1,11 +1,50 @@ + Du har fått <b>50 gilla-markeringar</b> på webbplatsen i dag + <b>Erik S</b> har svarat på ditt inlägg + <b>Anna S</b> gillade ditt inlägg + Hoppa över + Välj + Välj favoritlayout på din startsida Du kan anpassa eller ändra det senare. + Den rullningsbara blockmenyn har stängts. + Den rullningsbara blockmenyn har öppnats. Välj ett block. + Välj en design + Lägg till kategori + Lägg till ny kategori + Kategorier + Inte inställd + Kategorier + Layouter är inte tillgängliga på grund av ett fel + Tryck på ”Försök igen” eller skapa en tom sida med hjälp av knappen nedan. + Layouter är inte tillgängliga när du saknar internetförbindelse + Tryck på ”Försök igen” när du har internetuppkoppling igen eller skapa en tom sida med knappen nedan. + Musik + Matlagning + Konst + Webbnyheter + Pamela Nguyen + Jag inspireras verkligen av fotografen Cameron Karstens verk. Jag tänker prova mig på dessa tekniker snart + Veckans nytt om rock’n roll + Fotboll + Trädgårdsskötsel + Politik + Mina tio favoritcaféer + Världens bästa fans + Muséer i London + Välkommen till världens populäraste webbplatsbyggare. + Med den kraftfulla redigeringsmiljön kan du skapa inlägg på rörlig fot. + Se hur din publik växer med hjälp av detaljerad statistik. + Följ dina favoritwebbplatser och hitta nya saker att läsa. + Låt dig inspireras + Se kommentarer och aviseringar i realtid. + Webbplatser att följa + Hämtningen av mediafiler misslyckades ”%s” stöds inte fullt ut Vi arbetar för fullt med att lägga till fler block i varje utgåva. Välj bilder @@ -19,7 +58,6 @@ Language: sv_SE %1$s nekades åtkomst till dina bilder. Korrigera genom att redigera dina rättigheter och aktivera %2$s och %3$s. Sidan har skapats En blank sida har skapats - Ett fel inträffade när layouterna skulle hämtas Lär känna berättelseinlägg Du har fått förhandsåtkomst till berättelseinlägg och det vore härligt om du ville prova dem. Du kanske vill skapa ett berättelseinlägg @@ -155,14 +193,13 @@ Language: sv_SE Byt video Ersätt en bild eller ett videoklipp Fortsätt med WordPress.com - Ange din webbplatsadress Välj bild Välj video Välj bild eller video Om du fortsätter med Google och inte redan har ett WordPress.com-konto, skapar du ett konto och samtycker till våra %1$sanvändarvillkor%2$s. Bekräftelse av registrering - WordPress-logga Blocket har tagits bort + Ange din befintliga webbplatsadress Genom att gå vidare accepterar du våra %1$sanvändarvillkor%2$s. Vi kommer att skicka e-post med en registreringslänk som låter dig skapa ett nytt WordPress.com-konto. Vi kommer att använda den här e-postadressen för att skapa ditt nya WordPress.com-konto. @@ -181,9 +218,7 @@ Language: sv_SE Kom igång Ange din e-postadress för att logga in eller skapa ett WordPress.com-konto. Skicka länk via e-post - Ta vara på kraften hos den mest flexibla webbplatsskaparen. Kolla din e-post - 37\% av webben är byggd på WordPress. Ett problem inträffade när din begäran skulle behandlas. Försök igen senare. Ge din webbplats ett namn som återspeglar dess personlighet och ämne. Första intrycket har betydelse! Tryck <b>%1$s</b> för att ange en ny rubrik @@ -487,7 +522,6 @@ Language: sv_SE Inlägget kommer att synkroniseras direkt. Är du klar att synkronisera? -%s - Inledning Denna domän är inte tillgänglig Vi kunde inte nå din webbplats eftersom den kräver <b>HTTP-autentisering</b>. För att lösa detta behöver du kontakta ditt webbhotell. Vi kunde inte nå din webbplats på grund av ett problem med <b>SSL-certifikatet</b>. För att lösa detta behöver du kontakta ditt webbhotell. @@ -1301,7 +1335,6 @@ Language: sv_SE Använt utrymme Media Kommentaren är markerad som ”inte skräppost” - Inloggning Nytt konto Välj webbplats Redigera foto @@ -2247,7 +2280,6 @@ Language: sv_SE App-loggar har kopierats till urklipp Nya inlägg Ett fel uppstod när text skulle kopieras till urklipp - Webbplatser du kanske gillar Laddar upp inlägg Denna webbplats är tom Hämtar teman… diff --git a/WordPress/src/main/res/values-th/strings.xml b/WordPress/src/main/res/values-th/strings.xml index 21c2267f0eb3..fa1bf61b37b5 100644 --- a/WordPress/src/main/res/values-th/strings.xml +++ b/WordPress/src/main/res/values-th/strings.xml @@ -191,7 +191,6 @@ Language: th เรื่องใหม่ มีความผิดพลาดเกิดขึ้นขณะกำลังคัดลอกข้อความไปยังคลิปบอร์ด ปูมแอพพลิเคชั่นถูกคัดลอกลงคลิปบอร์ด - เว็บที่คุณอาจจะชอบ เรื่องและหน้า วีดีโอ ผู้ติดตาม diff --git a/WordPress/src/main/res/values-tr/strings.xml b/WordPress/src/main/res/values-tr/strings.xml index 7ebb7d77cd22..12f0d12654b2 100644 --- a/WordPress/src/main/res/values-tr/strings.xml +++ b/WordPress/src/main/res/values-tr/strings.xml @@ -1,11 +1,43 @@ + Her sürümde daha fazla blok eklemek için elimizden geleni yapıyoruz. + \"%s\" tamamen desteklenmiyor + Yardım düğmesi + Web düzenleyicisini kullanarak düzenle + Görsel seç + Öykü Yayını Oluştur + Sitenizde yeni bir blog yazısı olarak yayınlanırlar, böylece kitleniz hiçbir şeyi kaçırmaz. + Öykü yayınları kaybolmaz + Ziyaretçilerinizin seveceği ilgi çekici ve dokunulabilir öykü yayınları oluşturmak için fotoğrafları, videoları ve metinleri birleştirin. + Artık herkes öykülere erişebilir + Öykü başlığı örneği + Öykü yayını nasıl oluşturulur? + Öykü yayınlarına erken erişiminiz var ve denemenizi çok isteriz. + Öykü Yayınlarına Giriş + Boş sayfa oluşturuldu + Sayfa oluşturuldu + Düzen Önizlemesi + %1$s fotoğraflarınıza erişimi reddedildi. Bunu düzeltmek için izinlerinizi düzenleyip %2$s ve %3$s özelliklerini açın. + Medya eklenemedi. + Medya eklenemedi: %s + WordPress Ortam Kütüphanesi\'nden seçin + Geri + Başlayın + Yeni bloglar keşfetmek için konular takip edin + gönderen: + Bu yönlendiren, istenmeyen posta olarak işaretlenemez + İstenmeyen posta işaretini kaldır + Spam Olarak İşaretle + Web Sitesini Aç + Gif medyası yükleme + Stok medya yükleme + Ortam dosyası yükleniyor URL arayın veya yazın Öğeyi seç Bu telefon bağlantısını ekle @@ -122,8 +154,6 @@ Language: tr Video veya görseli değiştir Videoyu değiştir WordPress.com ile devam et - Site adresinizi girin - WordPress logosu Blok kaldırıldı Görsel seç Görsel veya video seç @@ -143,11 +173,9 @@ Language: tr E-postayı görmüyor musunuz? İstenmeyen posta klasörünüzü kontrol edin. Size anında giriş yapacağınız bir bağlantıyı e-posta ile göndereceğiz, hiç bir parolaya gerek yok. Parolanızı sıfırlayın - En esnek web sitesi oluşturma platformunun gücünü açığa çıkarın. Hesap oluştur Başlayın E-postayı kontrol et - Web\'in %37\'si WordPress üzerine kurulmuştur. Bağlantıyı e-postayla gönder Veya parolanızı yazın Giriş yapmak veya bir WordPress.com hesabı oluşturmak için e-posta adresinizi girin. @@ -400,7 +428,9 @@ Language: tr Yazıları filtrelemek için bir Site veya Etiket seçin Geçerli filtreyi kaldır WordPress.com\'da oturum aç + Konuları ve Siteleri Yönetme Takip ettiğiniz sitelerdeki en son yazıları görmek için WordPress.com\'da oturum açın + Takip ettiğiniz konulardaki en yeni yazıları görmek için WordPress.com\'da oturum açın Bir siteyi takip et Blok sonrasına ekle Blok öncesine ekle @@ -409,6 +439,8 @@ Language: tr Mevcut bloğu değiştir Şablon ön izlemesi Takip ettiğiniz sitelerdeki en yeni yazıları görün + Bir konu ekle + Bir konu ekleyerek o konudaki yazıları takip edebilirsiniz Filtrele Takip edilen Video yazısı. %s @@ -450,7 +482,6 @@ Language: tr Senkronize etmeye hazır mısınız? Şimdi senkronize et -%s - Önsöz Bu alan adı kullanılamaz. Sitenize erişimi başaramadık. Bu sorunu çözmek için sunucunuza ulaşmanız gerekecektir. <b>SSL Sertifikası</b> ile ilgili bir sorun nedeniyle sitenize erişemedik. Bu sorunu çözmek için sunucunuza ulaşmanız gerekecektir. @@ -474,6 +505,7 @@ Language: tr Keşfet Beğeniler Şu anda siteniz için veri yüklemesi yapamıyoruz. Lütfen daha sonra tekrar deneyin + Konular WordPress Ortam Kütüphanesi Fotoğraf Çek Yazmaya başlayın… @@ -1053,6 +1085,8 @@ Language: tr Bir tema seç Yeni temalar keşfetmek için %1$s Temalar %2$s seçeneğine dokunun Daha fazlası + Takip edilen konu yok + En sevdiğiniz konularla ilgili yazıları bulmak için buraya konu ekleyin Jetpack SSS Jetpack Jetpack\'i kur @@ -1271,7 +1305,6 @@ Language: tr Bildirim ayrıntısı %s Site seç Yeni hesap - Oturum aç Fotoğrafı düzenle Kullanılan alan Oturum açmak için kullanılan hesap: @@ -2135,6 +2168,7 @@ Language: tr Beğen 1 beğeni %,d takipçi + Konu ve site düzenleme Okuyucu yazısı Cihazınızda görünen bildirimlerin ayarları. Hesabınıza bağlı e-posta adresine gönderilen bildirimlerin ayarları. @@ -2205,7 +2239,6 @@ Language: tr Yazılar alınıyor… Uygulama günlükleri panoya kopyalandı Metin panoya kopyalanırken bir hata oluştu - Hoşunuza gidebilecek siteler Yazı yükleniyor Bu site boş Yeni yazılar @@ -2252,6 +2285,7 @@ Language: tr Şunu cevapla: %s Boş bir yazı yayımlanamaz Yorum yap + Bu konuyla ilgili hiç yazı yok Bu eylemi gerçekleştirilemiyor Bu site engellenemiyor Bu siteden yazılar artık gösterilmeyecek @@ -2266,6 +2300,8 @@ Language: tr Bu sitenin takibi bırakılamıyor Önerilen site yok Okuyucu sitesi + Takip edilen konular + Bir URL veya takip etmek istediğiniz konuyu girin Yardım Parolanızı mı kaybettiniz? Geçersiz SSL setifikası @@ -2346,6 +2382,8 @@ Language: tr Yazıyı silinsin mi? Yazı yüklenirken hata oluştu. Yazıyı yenileyin ve tekrar deneyin Bağlantı yazısı (opsiyonel) + Bu konu eklenemiyor + Bu konu silinemiyor Bağlantıyı paylaş Yazılar getiriliyor… Yanıtla @@ -2374,6 +2412,8 @@ Language: tr Henüz yorum yok Görsel görüntülenemedi Üye ol + Bu konuyu zaten takip ediyorsunuz + Bu geçerli bir konu değil Temalar Başlık Manşet diff --git a/WordPress/src/main/res/values-vi/strings.xml b/WordPress/src/main/res/values-vi/strings.xml index 36382348a463..91ad4ab6708e 100644 --- a/WordPress/src/main/res/values-vi/strings.xml +++ b/WordPress/src/main/res/values-vi/strings.xml @@ -435,7 +435,6 @@ Language: vi_VN Người xuất bản: Không biết Điều kiện tìm kiếm Một lỗi đã xảy ra trong khi sao chép văn bản vào bộ nhớ tạm - Các site bạn có thể thích Bài viết mới Bản ghi ứng dụng đã được sao chép vào clipboard Site này không có gì diff --git a/WordPress/src/main/res/values-zh-rCN/strings.xml b/WordPress/src/main/res/values-zh-rCN/strings.xml index 3a88380cbed8..1cd1d1a19b93 100644 --- a/WordPress/src/main/res/values-zh-rCN/strings.xml +++ b/WordPress/src/main/res/values-zh-rCN/strings.xml @@ -20,7 +20,6 @@ Language: zh_CN 如何创作故事文章 您可以抢先体验故事文章,我们也希望您能亲身感受一下故事文章的创作乐趣。 故事文章简介 - 获取布局时出错 创建了空白页面 页面已创建 布局预览 @@ -152,8 +151,6 @@ Language: zh_CN 选择图片或视频 选择图像 区块已删除 - WordPress 徽标 - 输入您的站点地址 继续使用 WordPress.com 注册确认 如果您继续使用 Google,并且还没有 WordPress.com 帐户,则需要创建一个帐户,并同意我们的%1$s服务条款%2$s。 @@ -175,8 +172,6 @@ Language: zh_CN 或者输入您的密码 创建帐户 通过电子邮件发送链接 - 发挥最灵活的网站生成器的强大功能。 - 网上有 37\% 的内容是在 WordPress 上构建的。 重置密码 为您的站点提供一个反映其个性和主题的名称。 第一印象很重要! 设置您的站点标题 @@ -478,7 +473,6 @@ Language: zh_CN 此文章将立即同步。 准备好同步了? 此域不可用 - 简介 -%s 我们无法访问您的站点。您需要联系您的主机来解决这个问题。 由于 <b>SSL 证书</b>的问题,我们无法访问您的站点。您需要联系您的主机来解决这个问题。 @@ -1304,7 +1298,6 @@ Language: zh_CN 编辑照片 选择站点 新建帐户 - 登录 登录身份为 个人详情 文件详情 @@ -2233,7 +2226,6 @@ Language: zh_CN 已将应用程序日志复制到剪贴板 新文章 复制文本到剪贴板时出错 - 您可能喜欢的博客 正在上传文章 此站点为空 正在获取主题… diff --git a/WordPress/src/main/res/values-zh-rHK/strings.xml b/WordPress/src/main/res/values-zh-rHK/strings.xml index 238eee7497e4..96bf127ce436 100644 --- a/WordPress/src/main/res/values-zh-rHK/strings.xml +++ b/WordPress/src/main/res/values-zh-rHK/strings.xml @@ -1,11 +1,43 @@ + 我們正在努力開發,讓每次版本更新都可以新增更多區塊。 + 無法完全支援「%s」 + 「協助」按鈕 + 使用網頁編輯器編輯 + 選擇圖片 + 建立限時動態文章 + 它們會以新網誌文章的形式發佈在你的網站上,這樣你的讀者群永遠不會錯過任何精彩內容。 + 故事文章不會消失 + 結合相片、視訊和文字,建立吸睛又可點按的故事,你的訪客一定會喜歡。 + 現在人人都可以發佈限時動態 + 限時動態標題例子 + 如何建立限時動態文章 + 你可以搶先使用「限時動態文章」 (Story Posts),我們很樂意為你開放試用。 + 限時動態文章介紹 + 已建立空白頁面 + 已建立頁面 + 版面配置預覽 + %1$s 存取你的相片時遭拒絕。 若要解決此問題,請編輯存取權限並開啟 %2$s 和 %3$s。 + 媒體插入失敗。 + 媒體插入失敗:%s + 從 WordPress 媒體庫選擇 + 返回 + 開始使用 + 關注主題以探索新網誌 + 發表者: + 此推薦連結不能被標記為垃圾郵件 + 取消標記為垃圾郵件 + 標記為垃圾 + 開啟網站 + 正在上傳 GIF 媒體 + 正在上傳庫存媒體 + 正在上傳媒體 選取項目 搜尋或輸入 URL 新增此電話連結 @@ -122,8 +154,6 @@ Language: zh_TW 選擇圖片或視訊 選擇圖片 已移除區塊 - WordPress 標誌 - 請輸入你的網站位址 繼續使用 WordPress.com 註冊確認 如果你繼續使用 Google 操作且尚未擁有 WordPress.com 帳號,即表示你正在建立帳號,並同意我們的%1$s服務條款%2$s。 @@ -145,8 +175,6 @@ Language: zh_TW 或輸入你的密碼 建立帳號 以電子郵件傳送連結 - 最具彈性、功能最強大的網站建置系統,釋放無限可能。 - 37\% 的網站皆選用 WordPress 建置。 重設密碼 為網站取一個能反映出自我風格和主題的名稱。 第一印象很重要! 設定你的網站標題 @@ -395,7 +423,9 @@ Language: zh_TW 已選取 選擇網站或標籤以篩選文章 移除目前的篩選條件 + 管理主題和網站 登入 WordPress.com + 登入 WordPress.com,查看你關注主題的最新文章 登入 WordPress.com,查看關注網站的最新文章 範例預覽 取代目前區塊 @@ -403,7 +433,9 @@ Language: zh_TW 新增至「開始」 在之前新增區塊 在之後新增區塊 + 新增主題 關注網站 + 你可以新增主題以關注特定主題的文章 查看你關注網站的最新聞張 關注中 篩選 @@ -446,7 +478,6 @@ Language: zh_TW 此文章會立即同步。 準備好要同步了嗎? 此網域不可使用 - 簡介 -%s 無法存取你的網站。你必須聯絡主機商以解決此問題。 由於 <b>SSL 憑證</b>發生問題,我們無法存取你的網站。你必須聯絡主機商以解決此問題。 @@ -461,6 +492,7 @@ Language: zh_TW 按讚數 探索 已儲存 + 主題 網站 %sQi %sQa @@ -1038,6 +1070,8 @@ Language: zh_TW 現在不要 更多 你沒有任何網站 + 沒有關注任何主題 + 請在此處新增主題,以尋找你最愛的主題文章 請登入你用來連結 Jetpack 的 WordPress.com 帳號。 重試 設定 @@ -1256,7 +1290,6 @@ Language: zh_TW 編輯照片 挑選網站 新增帳號 - 登入 已登入為 個人詳細資料 檔案詳細資料 @@ -2107,6 +2140,7 @@ Language: zh_TW %s 個讚 1 個讚 + 編輯主題和網站 讀者文章 在裝置上顯示通知的相關設定。 將通知傳送至帳號所繫結之電子郵件地址的相關設定。 @@ -2175,7 +2209,6 @@ Language: zh_TW 應用程式記錄已複製到剪貼簿 這個網站沒有內容 新文章 - 你或許也會喜歡以下網站: 將文字複製到剪貼簿時發生錯誤 正在上傳文章 %1$d 年 @@ -2203,6 +2236,7 @@ Language: zh_TW 已選取:%d 瀏覽常見問題集 尚未有留言 + 沒有任何與此主題相關的文章 按讚 查看原始文章 留言已關閉 @@ -2233,7 +2267,9 @@ Language: zh_TW 你已經關注此網站 無法顯示此網站 已關注的網站 + 輸入要關注的 URL 或主題 關注的網站 + 已關注的主題 讀者網站 如果你通常能夠順利連線至此網站,則此錯誤可能代表有人正嘗試冒充該網站,因此請勿繼續操作。是否仍然要信任憑證? 無效的 SSL 憑證 @@ -2261,6 +2297,8 @@ Language: zh_TW 存取此網誌時發生錯誤 無法擷取媒體項目 沒有可用的網路 + 無法移除此主題 + 無法新增此主題 應用程式記錄檔 建立應用程式資料庫時發生錯誤。請嘗試重新安裝應用程式。 此網誌已隱藏,無法載入。請在設定中啟用網誌,然後再試一次。 @@ -2330,6 +2368,8 @@ Language: zh_TW 無法開啟 %s 無法檢視圖片 無法分享 + 這不是有效的主題 + 你已經關注了此主題 無法張貼你的回應 你說這個讚 1 人說這個讚 diff --git a/WordPress/src/main/res/values-zh-rTW/strings.xml b/WordPress/src/main/res/values-zh-rTW/strings.xml index 1eaf81d9fb27..96bf127ce436 100644 --- a/WordPress/src/main/res/values-zh-rTW/strings.xml +++ b/WordPress/src/main/res/values-zh-rTW/strings.xml @@ -1,11 +1,43 @@ + 我們正在努力開發,讓每次版本更新都可以新增更多區塊。 + 無法完全支援「%s」 + 「協助」按鈕 + 使用網頁編輯器編輯 + 選擇圖片 + 建立限時動態文章 + 它們會以新網誌文章的形式發佈在你的網站上,這樣你的讀者群永遠不會錯過任何精彩內容。 + 故事文章不會消失 + 結合相片、視訊和文字,建立吸睛又可點按的故事,你的訪客一定會喜歡。 + 現在人人都可以發佈限時動態 + 限時動態標題例子 + 如何建立限時動態文章 + 你可以搶先使用「限時動態文章」 (Story Posts),我們很樂意為你開放試用。 + 限時動態文章介紹 + 已建立空白頁面 + 已建立頁面 + 版面配置預覽 + %1$s 存取你的相片時遭拒絕。 若要解決此問題,請編輯存取權限並開啟 %2$s 和 %3$s。 + 媒體插入失敗。 + 媒體插入失敗:%s + 從 WordPress 媒體庫選擇 + 返回 + 開始使用 + 關注主題以探索新網誌 + 發表者: + 此推薦連結不能被標記為垃圾郵件 + 取消標記為垃圾郵件 + 標記為垃圾 + 開啟網站 + 正在上傳 GIF 媒體 + 正在上傳庫存媒體 + 正在上傳媒體 選取項目 搜尋或輸入 URL 新增此電話連結 @@ -122,8 +154,6 @@ Language: zh_TW 選擇圖片或視訊 選擇圖片 已移除區塊 - WordPress 標誌 - 請輸入你的網站位址 繼續使用 WordPress.com 註冊確認 如果你繼續使用 Google 操作且尚未擁有 WordPress.com 帳號,即表示你正在建立帳號,並同意我們的%1$s服務條款%2$s。 @@ -145,7 +175,6 @@ Language: zh_TW 或輸入你的密碼 建立帳號 以電子郵件傳送連結 - 最具彈性、功能最強大的網站建置系統,釋放無限可能。 重設密碼 為網站取一個能反映出自我風格和主題的名稱。 第一印象很重要! 設定你的網站標題 @@ -394,7 +423,9 @@ Language: zh_TW 已選取 選擇網站或標籤以篩選文章 移除目前的篩選條件 + 管理主題和網站 登入 WordPress.com + 登入 WordPress.com,查看你關注主題的最新文章 登入 WordPress.com,查看關注網站的最新文章 範例預覽 取代目前區塊 @@ -402,7 +433,9 @@ Language: zh_TW 新增至「開始」 在之前新增區塊 在之後新增區塊 + 新增主題 關注網站 + 你可以新增主題以關注特定主題的文章 查看你關注網站的最新聞張 關注中 篩選 @@ -445,7 +478,6 @@ Language: zh_TW 此文章會立即同步。 準備好要同步了嗎? 此網域不可使用 - 簡介 -%s 無法存取你的網站。你必須聯絡主機商以解決此問題。 由於 <b>SSL 憑證</b>發生問題,我們無法存取你的網站。你必須聯絡主機商以解決此問題。 @@ -460,6 +492,7 @@ Language: zh_TW 按讚數 探索 已儲存 + 主題 網站 %sQi %sQa @@ -1037,6 +1070,8 @@ Language: zh_TW 現在不要 更多 你沒有任何網站 + 沒有關注任何主題 + 請在此處新增主題,以尋找你最愛的主題文章 請登入你用來連結 Jetpack 的 WordPress.com 帳號。 重試 設定 @@ -1255,7 +1290,6 @@ Language: zh_TW 編輯照片 挑選網站 新增帳號 - 登入 已登入為 個人詳細資料 檔案詳細資料 @@ -2106,6 +2140,7 @@ Language: zh_TW %s 個讚 1 個讚 + 編輯主題和網站 讀者文章 在裝置上顯示通知的相關設定。 將通知傳送至帳號所繫結之電子郵件地址的相關設定。 @@ -2174,7 +2209,6 @@ Language: zh_TW 應用程式記錄已複製到剪貼簿 這個網站沒有內容 新文章 - 你或許也會喜歡以下網站: 將文字複製到剪貼簿時發生錯誤 正在上傳文章 %1$d 年 @@ -2202,6 +2236,7 @@ Language: zh_TW 已選取:%d 瀏覽常見問題集 尚未有留言 + 沒有任何與此主題相關的文章 按讚 查看原始文章 留言已關閉 @@ -2232,7 +2267,9 @@ Language: zh_TW 你已經關注此網站 無法顯示此網站 已關注的網站 + 輸入要關注的 URL 或主題 關注的網站 + 已關注的主題 讀者網站 如果你通常能夠順利連線至此網站,則此錯誤可能代表有人正嘗試冒充該網站,因此請勿繼續操作。是否仍然要信任憑證? 無效的 SSL 憑證 @@ -2260,6 +2297,8 @@ Language: zh_TW 存取此網誌時發生錯誤 無法擷取媒體項目 沒有可用的網路 + 無法移除此主題 + 無法新增此主題 應用程式記錄檔 建立應用程式資料庫時發生錯誤。請嘗試重新安裝應用程式。 此網誌已隱藏,無法載入。請在設定中啟用網誌,然後再試一次。 @@ -2329,6 +2368,8 @@ Language: zh_TW 無法開啟 %s 無法檢視圖片 無法分享 + 這不是有效的主題 + 你已經關注了此主題 無法張貼你的回應 你說這個讚 1 人說這個讚 From f178af8f9ad12c7d9d8fc4cf03e0beab30994338 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Thu, 12 Nov 2020 12:56:19 +0100 Subject: [PATCH 305/343] Bump version number --- WordPress/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 5db8db21ac9c..8be8ac8f424f 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -54,9 +54,9 @@ android { if (project.hasProperty("versionName")) { versionName project.property("versionName") } else { - versionName "alpha-256" + versionName "alpha-257" } - versionCode 953 + versionCode 955 minSdkVersion rootProject.minSdkVersion targetSdkVersion rootProject.targetSdkVersion @@ -93,9 +93,9 @@ android { dimension "buildType" // Only set the release version if one isn't provided if (!project.hasProperty("versionName")) { - versionName "16.1-rc-3" + versionName "16.1-rc-4" } - versionCode 952 + versionCode 954 buildConfigField "boolean", "ME_ACTIVITY_AVAILABLE", "false" buildConfigField "boolean", "TENOR_AVAILABLE", "false" buildConfigField "long", "REMOTE_CONFIG_FETCH_INTERVAL", "3600" From 4977f25cb2a421e799735342d409d24d3e3735ab Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 13:57:27 +0100 Subject: [PATCH 306/343] Replace reader_fullscreen_empty_with_action with ActionableEmptyView --- .../ui/reader/discover/ReaderDiscoverFragment.kt | 9 ++------- .../res/layout/reader_discover_fragment_layout.xml | 13 ++++++++++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt index 0c5c5e9c2645..0221ff253149 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt @@ -12,9 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.reader_discover_fragment_layout.* -import kotlinx.android.synthetic.main.reader_fullscreen_empty_with_action.* import kotlinx.android.synthetic.main.reader_fullscreen_error_with_retry.* import org.wordpress.android.R import org.wordpress.android.R.dimen @@ -89,9 +87,6 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme error_retry.setOnClickListener { viewModel.onRetryButtonClick() } - empty_action.setOnClickListener { - viewModel.onEmptyActionClick() - } } private fun initViewModel() { @@ -109,7 +104,7 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme uiHelpers.setTextOrHide(error_title, it.titleResId) } is EmptyUiState -> { - uiHelpers.setTextOrHide(empty_title, it.titleResId) + uiHelpers.setTextOrHide(actionable_empty_view.title, it.titleResId) } } uiHelpers.updateVisibility(recycler_view, it.contentVisiblity) @@ -117,7 +112,7 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme uiHelpers.updateVisibility(progress_text, it.fullscreenProgressVisibility) uiHelpers.updateVisibility(error_layout, it.fullscreenErrorVisibility) uiHelpers.updateVisibility(progress_loading_more, it.loadMoreProgressVisibility) - uiHelpers.updateVisibility(empty_layout, it.fullscreenEmptyVisibility) + uiHelpers.updateVisibility(actionable_empty_view, it.fullscreenEmptyVisibility) ptr_layout.isEnabled = it.swipeToRefreshEnabled ptr_layout.isRefreshing = it.reloadProgressVisibility }) diff --git a/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml b/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml index bc49dbcea2f9..74ae2170e356 100644 --- a/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml +++ b/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml @@ -30,7 +30,18 @@ - + Date: Thu, 12 Nov 2020 13:57:39 +0100 Subject: [PATCH 307/343] Remove unused reader_fullscreen_empty_with_action.xml --- .../reader_fullscreen_empty_with_action.xml | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 WordPress/src/main/res/layout/reader_fullscreen_empty_with_action.xml diff --git a/WordPress/src/main/res/layout/reader_fullscreen_empty_with_action.xml b/WordPress/src/main/res/layout/reader_fullscreen_empty_with_action.xml deleted file mode 100644 index 9da9ec696253..000000000000 --- a/WordPress/src/main/res/layout/reader_fullscreen_empty_with_action.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - From 94a5f0b4dfde779200304d14720c283d5d3d442a Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 14:00:31 +0100 Subject: [PATCH 308/343] Add subtitle, action and illustration to discover empty view --- .../reader/discover/ReaderDiscoverFragment.kt | 6 ++++ .../discover/ReaderDiscoverViewModel.kt | 36 ++++++++++++------- WordPress/src/main/res/values/strings.xml | 3 ++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt index 0221ff253149..0511542cf0e1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt @@ -105,6 +105,12 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme } is EmptyUiState -> { uiHelpers.setTextOrHide(actionable_empty_view.title, it.titleResId) + uiHelpers.setTextOrHide(actionable_empty_view.subtitle, it.subTitleRes) + uiHelpers.setImageOrHide(actionable_empty_view.image, it.illustrationResId) + uiHelpers.setTextOrHide(actionable_empty_view.button, it.buttonResId) + actionable_empty_view.button.setOnClickListener {_ -> + it.action.invoke() + } } } uiHelpers.updateVisibility(recycler_view, it.contentVisiblity) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index 85902035100b..4088c7d98153 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -128,7 +128,7 @@ class ReaderDiscoverViewModel @Inject constructor( launch { val userTags = getFollowedTagsUseCase.get() if (userTags.isEmpty()) { - _uiState.value = ShowNoFollowedTagsUiState + _uiState.value = ShowNoFollowedTagsUiState { parentViewModel.onShowReaderInterests() } } else { if (posts != null && posts.cards.isNotEmpty()) { _uiState.value = ContentUiState( @@ -139,7 +139,9 @@ class ReaderDiscoverViewModel @Inject constructor( ) swipeToRefreshTriggered = false } else { - _uiState.value = ShowNoPostsUiState + _uiState.value = ShowNoPostsUiState { + // TODO malinjir navigate to reader tag settings + } } } } @@ -423,10 +425,6 @@ class ReaderDiscoverViewModel @Inject constructor( } } - fun onEmptyActionClick() { - parentViewModel.onShowReaderInterests() - } - sealed class DiscoverUiState( val contentVisiblity: Boolean = false, val fullscreenProgressVisibility: Boolean = false, @@ -451,13 +449,25 @@ class ReaderDiscoverViewModel @Inject constructor( titleResId = R.string.reader_error_request_failed_title ) } - sealed class EmptyUiState constructor(val titleResId: Int) : DiscoverUiState(fullscreenEmptyVisibility = true) { - object ShowNoFollowedTagsUiState : EmptyUiState( - titleResId = R.string.reader_discover_empty_title - ) - object ShowNoPostsUiState : EmptyUiState( - titleResId = R.string.reader_discover_no_posts_title - ) + + sealed class EmptyUiState : DiscoverUiState(fullscreenEmptyVisibility = true) { + abstract val titleResId: Int + abstract val buttonResId: Int + open val subTitleRes: Int? = null + abstract val action: () -> Unit + open val illustrationResId: Int? = null + + data class ShowNoFollowedTagsUiState(override val action: () -> Unit) : EmptyUiState() { + override val titleResId: Int = R.string.reader_discover_empty_title + override val buttonResId: Int = R.string.reader_discover_empty_button_text + } + + data class ShowNoPostsUiState(override val action: () -> Unit) : EmptyUiState() { + override val titleResId = R.string.reader_discover_no_posts_title + override val buttonResId: Int = R.string.reader_discover_no_posts_button_text + override val subTitleRes = R.string.reader_discover_no_posts_subtitle + override val illustrationResId: Int? = R.drawable.img_illustration_empty_results_216dp + } } } } diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index fe45a23e50e1..3a72f53db6b9 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1338,7 +1338,10 @@ By %1$s more items Follow topics to discover new blogs + No recent posts + Try following more topics to broaden the search Get Started + Follow topics Post saved online From fae55802789a0fb4dd974dbf1becd1a105f9cb95 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 14:02:02 +0100 Subject: [PATCH 309/343] Add subtitle empty view on discover tab --- .../android/ui/reader/discover/ReaderDiscoverViewModel.kt | 1 + WordPress/src/main/res/values/strings.xml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index 4088c7d98153..164169e73798 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -459,6 +459,7 @@ class ReaderDiscoverViewModel @Inject constructor( data class ShowNoFollowedTagsUiState(override val action: () -> Unit) : EmptyUiState() { override val titleResId: Int = R.string.reader_discover_empty_title + override val subTitleRes = R.string.reader_discover_empty_subtitle override val buttonResId: Int = R.string.reader_discover_empty_button_text } diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 3a72f53db6b9..364bbe3b8303 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1337,7 +1337,8 @@ Sites to follow By %1$s more items - Follow topics to discover new blogs + Welcome! + Follow topics to discover new blogs No recent posts Try following more topics to broaden the search Get Started From 9373d73f046c1cacd6e013ce5624a529633992c2 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 14:27:45 +0100 Subject: [PATCH 310/343] Replace reader_fullscreen_error_with_retry with ActionableEmptyView --- .../reader/discover/ReaderDiscoverFragment.kt | 11 +-------- .../discover/ReaderDiscoverViewModel.kt | 24 +++++++++---------- .../reader_discover_fragment_layout.xml | 7 ++---- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt index 0511542cf0e1..e67d1d0170d6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt @@ -13,7 +13,6 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.reader_discover_fragment_layout.* -import kotlinx.android.synthetic.main.reader_fullscreen_error_with_retry.* import org.wordpress.android.R import org.wordpress.android.R.dimen import org.wordpress.android.WordPress @@ -27,7 +26,6 @@ import org.wordpress.android.ui.reader.ReaderActivityLauncher.OpenUrlType import org.wordpress.android.ui.reader.ReaderPostWebViewCachingFragment import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState -import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ErrorUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.OpenEditorForReblog import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.OpenPost import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.SharePost @@ -84,9 +82,6 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme WPSwipeToRefreshHelper.buildSwipeToRefreshHelper(ptr_layout) { viewModel.swipeToRefresh() } - error_retry.setOnClickListener { - viewModel.onRetryButtonClick() - } } private fun initViewModel() { @@ -100,15 +95,12 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme recycler_view.scrollToPosition(0) } } - is ErrorUiState -> { - uiHelpers.setTextOrHide(error_title, it.titleResId) - } is EmptyUiState -> { uiHelpers.setTextOrHide(actionable_empty_view.title, it.titleResId) uiHelpers.setTextOrHide(actionable_empty_view.subtitle, it.subTitleRes) uiHelpers.setImageOrHide(actionable_empty_view.image, it.illustrationResId) uiHelpers.setTextOrHide(actionable_empty_view.button, it.buttonResId) - actionable_empty_view.button.setOnClickListener {_ -> + actionable_empty_view.button.setOnClickListener { _ -> it.action.invoke() } } @@ -116,7 +108,6 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme uiHelpers.updateVisibility(recycler_view, it.contentVisiblity) uiHelpers.updateVisibility(progress_bar, it.fullscreenProgressVisibility) uiHelpers.updateVisibility(progress_text, it.fullscreenProgressVisibility) - uiHelpers.updateVisibility(error_layout, it.fullscreenErrorVisibility) uiHelpers.updateVisibility(progress_loading_more, it.loadMoreProgressVisibility) uiHelpers.updateVisibility(actionable_empty_view, it.fullscreenEmptyVisibility) ptr_layout.isEnabled = it.swipeToRefreshEnabled diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index 164169e73798..56453ac1ceb0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -26,9 +26,9 @@ import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderPostUiSt import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderRecommendedBlogsCardUiState.ReaderRecommendedBlogUiState import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderWelcomeBannerCardUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState +import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.RequestFailedUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoFollowedTagsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoPostsUiState -import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ErrorUiState.RequestFailedErrorUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowPostsByTag @@ -240,7 +240,7 @@ class ReaderDiscoverViewModel @Inject constructor( when (uiState) { is LoadingUiState -> { // show fullscreen error - _uiState.value = RequestFailedErrorUiState + _uiState.value = RequestFailedUiState{ onRetryButtonClick() } } is ContentUiState -> { _uiState.value = uiState.copy( @@ -428,7 +428,6 @@ class ReaderDiscoverViewModel @Inject constructor( sealed class DiscoverUiState( val contentVisiblity: Boolean = false, val fullscreenProgressVisibility: Boolean = false, - open val fullscreenErrorVisibility: Boolean = false, val swipeToRefreshEnabled: Boolean = false, open val fullscreenEmptyVisibility: Boolean = false, open val scrollToTop: Boolean = false @@ -444,11 +443,6 @@ class ReaderDiscoverViewModel @Inject constructor( ) : DiscoverUiState(contentVisiblity = true, swipeToRefreshEnabled = true) object LoadingUiState : DiscoverUiState(fullscreenProgressVisibility = true) - sealed class ErrorUiState constructor(val titleResId: Int) : DiscoverUiState(fullscreenErrorVisibility = true) { - object RequestFailedErrorUiState : ErrorUiState( - titleResId = R.string.reader_error_request_failed_title - ) - } sealed class EmptyUiState : DiscoverUiState(fullscreenEmptyVisibility = true) { abstract val titleResId: Int @@ -457,17 +451,23 @@ class ReaderDiscoverViewModel @Inject constructor( abstract val action: () -> Unit open val illustrationResId: Int? = null + data class RequestFailedUiState(override val action: () -> Unit) : EmptyUiState() { + override val titleResId = R.string.connection_error + override val subTitleRes = R.string.reader_error_request_failed_title + override val buttonResId = R.string.retry + } + data class ShowNoFollowedTagsUiState(override val action: () -> Unit) : EmptyUiState() { - override val titleResId: Int = R.string.reader_discover_empty_title + override val titleResId = R.string.reader_discover_empty_title override val subTitleRes = R.string.reader_discover_empty_subtitle - override val buttonResId: Int = R.string.reader_discover_empty_button_text + override val buttonResId = R.string.reader_discover_empty_button_text } data class ShowNoPostsUiState(override val action: () -> Unit) : EmptyUiState() { override val titleResId = R.string.reader_discover_no_posts_title - override val buttonResId: Int = R.string.reader_discover_no_posts_button_text + override val buttonResId = R.string.reader_discover_no_posts_button_text override val subTitleRes = R.string.reader_discover_no_posts_subtitle - override val illustrationResId: Int? = R.drawable.img_illustration_empty_results_216dp + override val illustrationResId = R.drawable.img_illustration_empty_results_216dp } } } diff --git a/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml b/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml index 74ae2170e356..d12002d82d5a 100644 --- a/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml +++ b/WordPress/src/main/res/layout/reader_discover_fragment_layout.xml @@ -1,6 +1,7 @@ @@ -28,11 +29,7 @@ app:layout_constraintTop_toBottomOf="@+id/progress_bar" app:layout_constraintVertical_bias="0" /> - - - Date: Thu, 12 Nov 2020 15:08:37 +0100 Subject: [PATCH 311/343] Update FluxC version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e2138650c54..91292b36a68c 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '1.6.26-beta-5' + fluxCVersion = '8fcdaa159c8f64f35491f73154f3ee703443f68d' appCompatVersion = '1.0.2' coreVersion = '1.2.0' From bb0fa512c04e7f6ac285d1b3a11bc27014088ca9 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 12 Nov 2020 15:35:47 +0100 Subject: [PATCH 312/343] Only start loading data when the app has storage permissions --- .../android/ui/mediapicker/MediaPickerViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt index 08aa00b23f15..c95828d77f51 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mediapicker/MediaPickerViewModel.kt @@ -354,8 +354,10 @@ class MediaPickerViewModel @Inject constructor( _domainModel.value = domainModel } } - launch(bgDispatcher) { - loadActions.send(LoadAction.Start()) + if (permissionsHandler.hasStoragePermission()) { + launch(bgDispatcher) { + loadActions.send(LoadAction.Start()) + } } } if (mediaPickerSetup.defaultSearchView) { From 9376d80f26dbe73010d29ea4b9aa7c27ee9720e2 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 12 Nov 2020 15:51:06 +0100 Subject: [PATCH 313/343] Fix unrelated FluxC --- .../ui/sitecreation/theme/HomePagePickerViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt index 70206f350bf6..4486d692d835 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt @@ -52,7 +52,7 @@ class HomePagePickerViewModelTest { private fun mockResponse(isError: Boolean = false, block: suspend CoroutineScope.() -> T) = test { val response = if (isError) OnStarterDesignsFetched(emptyList(), ThemesError(ThemeErrorType.GENERIC_ERROR)) else OnStarterDesignsFetched( - listOf(StarterDesignModel(0, "slug", "title", "site", "demo", "theme", "image")), + listOf(StarterDesignModel(0, "slug", "title", "site", "demo", "theme", null, "image")), null ) whenever(fetchHomePageLayoutsUseCase.fetchStarterDesigns()).thenReturn(response) From d3cf5600f56824e41416666ff833edcf41875f79 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 16:21:00 +0100 Subject: [PATCH 314/343] Add missing string resource for empty discover state --- WordPress/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index fe45a23e50e1..ed805a337943 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1338,6 +1338,7 @@ By %1$s more items Follow topics to discover new blogs + No recent posts Get Started From 7a88a3ddc464a43c3765f5403dbd4e9a0c980f6a Mon Sep 17 00:00:00 2001 From: malinajirka Date: Thu, 12 Nov 2020 16:30:38 +0100 Subject: [PATCH 315/343] Add unit test for NoPosts discover state --- .../discover/ReaderDiscoverViewModelTest.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt index 05681dd96d87..c3b07cb20a42 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt @@ -42,6 +42,7 @@ import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderWelcomeB import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoFollowedTagsUiState +import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoPostsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.OpenEditorForReblog import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview @@ -201,7 +202,7 @@ class ReaderDiscoverViewModelTest { } @Test - fun `ShowFollowInterestsEmptyUiState is shown when the user does NOT follow any tags `() = test { + fun `ShowFollowInterestsEmptyUiState is shown when the user does NOT follow any tags`() = test { // Arrange whenever(getFollowedTagsUseCase.get()).thenReturn(ReaderTagList()) val uiStates = init().uiStates @@ -212,6 +213,16 @@ class ReaderDiscoverViewModelTest { assertThat(uiStates[1]).isInstanceOf(ShowNoFollowedTagsUiState::class.java) } + @Test + fun `ShowNoPostsUiState is shown when the discoverFeed does not contain any posts`() = test { + // Arrange + val uiStates = init(autoUpdateFeed = false).uiStates + // Act + fakeDiscoverFeed.value = createDummyReaderCardsList(numberOfItems = 0) + // Assert + assertThat(uiStates[1]).isInstanceOf(ShowNoPostsUiState::class.java) + } + @Test fun `load more action is initiated when we are close to the end of the list`() = test { // Arrange @@ -583,8 +594,12 @@ class ReaderDiscoverViewModelTest { // since we are adding an InterestsYouMayLikeCard we remove one item from the numberOfItems since it counts as 1. private fun createDummyReaderCardsList(numberOfItems: Long = NUMBER_OF_ITEMS): ReaderDiscoverCards { return ReaderDiscoverCards( - createInterestsYouMayLikeCardList() - .plus(createDummyReaderPostCardList(numberOfItems - 1)) + if (numberOfItems != 0L) { + createInterestsYouMayLikeCardList() + .plus(createDummyReaderPostCardList(numberOfItems - 1)) + } else { + listOf() + } ) } From 9364fa08f0370f28ce6f2cc73af85f19a4237d3a Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Thu, 12 Nov 2020 16:53:52 +0100 Subject: [PATCH 316/343] Fix UI tests after media picker update --- .../java/org/wordpress/android/e2e/pages/EditorPage.java | 4 ++-- .../wordpress/android/ui/screenshots/WPScreenshotTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPress/src/androidTest/java/org/wordpress/android/e2e/pages/EditorPage.java b/WordPress/src/androidTest/java/org/wordpress/android/e2e/pages/EditorPage.java index 3a6657b9eae1..7f6adce75e5b 100644 --- a/WordPress/src/androidTest/java/org/wordpress/android/e2e/pages/EditorPage.java +++ b/WordPress/src/androidTest/java/org/wordpress/android/e2e/pages/EditorPage.java @@ -73,7 +73,7 @@ public void enterImage() { waitForElementToBeDisplayed(onView(withText("WordPress media"))); // wait for images to load before clicking idleFor(2000); - onView(withIndex(withId(R.id.media_grid_item_image), 0)).perform(click()); + onView(withIndex(withId(R.id.image_thumbnail), 0)).perform(click()); // Click the confirm button clickOn(confirmButton); @@ -107,7 +107,7 @@ public void addATag(String tag) { public void setFeaturedImage() { clickOn(onView(withId(R.id.post_add_featured_image_button))); clickOn(onView(withId(R.id.icon_wpmedia))); - onView(withIndex(withId(R.id.media_grid_item_image), 0)).perform(click()); + onView(withIndex(withId(R.id.image_thumbnail), 0)).perform(click()); } public boolean publishPost() { diff --git a/WordPress/src/androidTest/java/org/wordpress/android/ui/screenshots/WPScreenshotTest.java b/WordPress/src/androidTest/java/org/wordpress/android/ui/screenshots/WPScreenshotTest.java index 5833357a20b7..dc7549318cf7 100644 --- a/WordPress/src/androidTest/java/org/wordpress/android/ui/screenshots/WPScreenshotTest.java +++ b/WordPress/src/androidTest/java/org/wordpress/android/ui/screenshots/WPScreenshotTest.java @@ -146,7 +146,7 @@ private void manageMedia() { clickOn(R.id.nav_sites); clickOn(R.id.quick_action_media_button); - waitForElementToBeDisplayedWithoutFailure(R.id.media_grid_item_image); + waitForElementToBeDisplayedWithoutFailure(R.id.image_thumbnail); takeScreenshot("5-share-from-anywhere"); From c0ed8e4a88f2275762940c68426eb078da55db13 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Thu, 12 Nov 2020 17:45:24 +0100 Subject: [PATCH 317/343] Fix configuration for Connected Tests and Screenshots --- .circleci/config.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 35c230f897bf..32f6b9ffcae8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,10 +23,14 @@ commands: name: Setup gradle.properties command: cp gradle.properties-example gradle.properties && cp libs/utils/WordPressUtils/gradle.properties-example libs/utils/WordPressUtils/gradle.properties update-gradle-memory: + parameters: + jvmargs: + type: string + default: "Xmx2048m" steps: - run: name: Update memory setting - command: sed -i "s/org.gradle.jvmargs=.*/org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false /" gradle.properties + command: sed -i "s/org.gradle.jvmargs=.*/org.gradle.jvmargs=-<< parameters.jvmargs >> -XX:+HeapDumpOnOutOfMemoryError -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false /" gradle.properties npm-install: steps: - restore_cache: @@ -320,7 +324,8 @@ jobs: - checkout-submodules - android/restore-gradle-cache - copy-gradle-properties - - update-gradle-memory + - update-gradle-memory: + jvmargs: "Xmx1024m" - restore-gutenberg-bundle-cache - run: name: Ensure assets folder exists @@ -331,7 +336,12 @@ jobs: name: Build environment: SUPPRESS_GUTENBERG_MOBILE_JS_BUNDLE_BUILD: 1 - command: ./gradlew WordPress:assembleVanillaDebug WordPress:assembleVanillaDebugAndroidTest --stacktrace + command: ./gradlew WordPress:assembleVanillaDebug --stacktrace + - run: + name: Build Tests + environment: + SUPPRESS_GUTENBERG_MOBILE_JS_BUNDLE_BUILD: 1 + command: ./gradlew WordPress:assembleVanillaDebugAndroidTest --stacktrace - run: name: Decrypt credentials command: openssl aes-256-cbc -md sha256 -d -in .circleci/.firebase.secrets.json.enc -out .circleci/.firebase.secrets.json -k "${FIREBASE_SECRETS_ENCRYPTION_KEY}" @@ -368,7 +378,8 @@ jobs: cache_key_prefix: v1-raw-screenshots - android/restore-gradle-cache - copy-gradle-properties - - update-gradle-memory + - update-gradle-memory: + jvmargs: "Xmx1024m" - restore-gutenberg-bundle-cache - run: name: Ensure assets folder exists @@ -385,7 +396,12 @@ jobs: name: Build environment: SUPPRESS_GUTENBERG_MOBILE_JS_BUNDLE_BUILD: 1 - command: ./gradlew WordPress:assembleVanillaDebug WordPress:assembleVanillaDebugAndroidTest --stacktrace + command: ./gradlew WordPress:assembleVanillaDebug --stacktrace + - run: + name: Build Tests + environment: + SUPPRESS_GUTENBERG_MOBILE_JS_BUNDLE_BUILD: 1 + command: ./gradlew WordPress:assembleVanillaDebugAndroidTest --stacktrace - run: name: Decrypt credentials command: openssl aes-256-cbc -md sha256 -d -in .circleci/.firebase.secrets.json.enc -out .circleci/.firebase.secrets.json -k "${FIREBASE_SECRETS_ENCRYPTION_KEY}" From 7da4b3e5b4405e6c5f2f98023ccd01feb3bf13fd Mon Sep 17 00:00:00 2001 From: khaykov Date: Thu, 12 Nov 2020 19:12:51 -0800 Subject: [PATCH 318/343] Pointing to new flux-c version. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e2138650c54..9f4f2e2614cd 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '1.6.26-beta-5' + fluxCVersion = '1.6.26-beta-6' appCompatVersion = '1.0.2' coreVersion = '1.2.0' From 05f9163fee6d403eb10e7f959d474a48c0714add Mon Sep 17 00:00:00 2001 From: khaykov Date: Thu, 12 Nov 2020 19:13:15 -0800 Subject: [PATCH 319/343] Hide non p2 related items from my-site. --- .../main/java/org/wordpress/android/ui/main/MySiteFragment.kt | 4 ++-- .../org/wordpress/android/ui/themes/ThemeBrowserActivity.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt index 0122fb0ae44b..6a70b8e03cbb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.kt @@ -242,7 +242,7 @@ class MySiteFragment : Fragment(), val isSelfHostedWithoutJetpack = !SiteUtils.isAccessedViaWPComRest( site ) && !site.isJetpackConnected - if (isNotAdmin || isSelfHostedWithoutJetpack) { + if (isNotAdmin || isSelfHostedWithoutJetpack || site.isWpForTeamsSite) { row_activity_log.visibility = View.GONE } else { row_activity_log.visibility = View.VISIBLE @@ -1024,7 +1024,7 @@ class MySiteFragment : Fragment(), // Hide the Plan item if the Plans feature is not available for this blog val planShortName = site.planShortName - if (!TextUtils.isEmpty(planShortName) && site.hasCapabilityManageOptions) { + if (!TextUtils.isEmpty(planShortName) && site.hasCapabilityManageOptions && !site.isWpForTeamsSite) { if (site.isWPCom || site.isAutomatedTransfer) { my_site_current_plan_text_view.text = planShortName row_plan.visibility = View.VISIBLE diff --git a/WordPress/src/main/java/org/wordpress/android/ui/themes/ThemeBrowserActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/themes/ThemeBrowserActivity.java index e3668f9a13a1..8a6e60daf2e8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/themes/ThemeBrowserActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/themes/ThemeBrowserActivity.java @@ -48,7 +48,8 @@ public class ThemeBrowserActivity extends LocaleAwareActivity implements ThemeBrowserFragmentCallback { public static boolean isAccessible(SiteModel site) { // themes are only accessible to admin wordpress.com users - return site != null && site.isUsingWpComRestApi() && site.getHasCapabilityEditThemeOptions(); + return site != null && site.isUsingWpComRestApi() && site.getHasCapabilityEditThemeOptions() + && !site.isWpForTeamsSite(); } public static final int ACTIVATE_THEME = 1; From e257ce07912c2ea1e18598741563ca10b139d15b Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 13 Nov 2020 09:11:00 +0200 Subject: [PATCH 320/343] [Home Page Picker] Connect Choose to the flow of existing UI's (#13316) * Connects Choose button * Connects skip button * Increase list bottom padding * Fixes card border overflow * Updated FluxC version --- .../android/ui/mlp/LayoutViewHolder.kt | 8 ++- .../ui/sitecreation/SiteCreationActivity.kt | 15 ++++- .../ui/sitecreation/SiteCreationMainVM.kt | 6 ++ .../domains/SiteCreationDomainsFragment.kt | 12 ++-- .../domains/SiteCreationDomainsViewModel.kt | 4 +- .../previews/SitePreviewViewModel.kt | 1 + .../services/SiteCreationServiceData.kt | 1 + .../theme/HomePagePickerFragment.kt | 35 +++-------- .../theme/HomePagePickerViewHolder.kt | 8 ++- .../theme/HomePagePickerViewModel.kt | 46 ++++++++++++-- .../usecases/CreateSiteUseCase.kt | 2 +- .../usecases/FetchDomainsUseCase.kt | 10 ++- .../ic_layout_selection_check_24dp.xml | 6 ++ .../ic_layout_selection_overlay_153dp.xml | 12 ---- .../modal_layout_picker_titlebar.xml | 2 +- .../res/layout/home_page_picker_fragment.xml | 2 +- .../main/res/layout/home_page_picker_item.xml | 12 ++-- .../res/layout/home_page_picker_titlebar.xml | 2 +- .../modal_layout_picker_layouts_card.xml | 12 ++-- .../layout/modal_layout_picker_titlebar.xml | 2 +- WordPress/src/main/res/values/dimens.xml | 1 + WordPress/src/main/res/values/strings.xml | 1 + .../previews/CreateSiteUseCaseTest.kt | 3 +- .../previews/SitePreviewViewModelTest.kt | 3 +- .../SiteCreationServiceManagerTest.kt | 3 +- .../theme/HomePagePickerViewModelTest.kt | 62 +++++++++++++++---- build.gradle | 3 +- 27 files changed, 186 insertions(+), 88 deletions(-) create mode 100644 WordPress/src/main/res/drawable/ic_layout_selection_check_24dp.xml delete mode 100644 WordPress/src/main/res/drawable/ic_layout_selection_overlay_153dp.xml diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mlp/LayoutViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mlp/LayoutViewHolder.kt index 4d6d7a338458..53d17155ec80 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mlp/LayoutViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mlp/LayoutViewHolder.kt @@ -2,11 +2,11 @@ package org.wordpress.android.ui.mlp import android.graphics.drawable.Drawable import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.ImageView.ScaleType.FIT_CENTER import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.card.MaterialCardView import kotlinx.android.synthetic.main.modal_layout_picker_layouts_card.view.* import org.wordpress.android.R import org.wordpress.android.util.image.ImageManager @@ -25,7 +25,7 @@ class LayoutViewHolder(internal val parent: ViewGroup) : false ) ) { - private val container: View = itemView.layout_container + private val container: MaterialCardView = itemView.layout_container private val preview: ImageView = itemView.preview private val selected: ImageView = itemView.selected_overlay @@ -45,6 +45,10 @@ class LayoutViewHolder(internal val parent: ViewGroup) : selected.setVisible(uiState.selectedOverlayVisible) preview.contentDescription = parent.context.getString(uiState.contentDescriptionResId, uiState.title) + preview.context?.let { ctx -> + container.strokeWidth = if (uiState.selectedOverlayVisible) + ctx.resources.getDimensionPixelSize(R.dimen.picker_header_selection_overlay_width) else 0 + } container.setOnClickListener { uiState.onItemTapped.invoke() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt index 97f305414ed3..d5a62b29869c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt @@ -34,6 +34,7 @@ import org.wordpress.android.ui.sitecreation.previews.SitePreviewViewModel.Creat import org.wordpress.android.ui.sitecreation.segments.SegmentsScreenListener import org.wordpress.android.ui.sitecreation.segments.SiteCreationSegmentsFragment import org.wordpress.android.ui.sitecreation.theme.HomePagePickerFragment +import org.wordpress.android.ui.sitecreation.theme.HomePagePickerViewModel import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.config.HomePagePickerFeatureConfig import org.wordpress.android.util.wizard.WizardNavigationTarget @@ -50,13 +51,16 @@ class SiteCreationActivity : LocaleAwareActivity(), @Inject internal lateinit var uiHelpers: UiHelpers @Inject internal lateinit var homePagePickerFeatureConfig: HomePagePickerFeatureConfig private lateinit var mainViewModel: SiteCreationMainVM + private lateinit var hppViewModel: HomePagePickerViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (application as WordPress).component().inject(this) setContentView(R.layout.site_creation_activity) mainViewModel = ViewModelProviders.of(this, viewModelFactory).get(SiteCreationMainVM::class.java) + hppViewModel = ViewModelProviders.of(this, viewModelFactory).get(HomePagePickerViewModel::class.java) mainViewModel.start(savedInstanceState) + hppViewModel.loadSavedState(savedInstanceState) observeVMState() } @@ -64,6 +68,7 @@ class SiteCreationActivity : LocaleAwareActivity(), override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) mainViewModel.writeToBundle(outState) + hppViewModel.writeToBundle(outState) } private fun observeVMState() { @@ -103,6 +108,12 @@ class SiteCreationActivity : LocaleAwareActivity(), mainViewModel.onBackPressedObservable.observe(this, Observer { super.onBackPressed() }) + hppViewModel.onBackButtonPressed.observe(this, Observer { + mainViewModel.onBackPressed() + }) + hppViewModel.onDesignActionPressed.observe(this, Observer { design -> + mainViewModel.onSiteDesignSelected(design.template, design.segmentId) + }) } override fun onSegmentSelected(segmentId: Long) { @@ -128,11 +139,11 @@ class SiteCreationActivity : LocaleAwareActivity(), private fun showStep(target: WizardNavigationTarget) { val screenTitle = getScreenTitle(target.wizardStep) val fragment = when (target.wizardStep) { - SEGMENTS -> if (homePagePickerFeatureConfig.isEnabled()) HomePagePickerFragment() // FIXME: Only for #13192 + SEGMENTS -> if (homePagePickerFeatureConfig.isEnabled()) HomePagePickerFragment() else SiteCreationSegmentsFragment.newInstance(screenTitle) DOMAINS -> SiteCreationDomainsFragment.newInstance( screenTitle, - target.wizardState.segmentId!! + target.wizardState.segmentId ) SITE_PREVIEW -> SiteCreationPreviewFragment.newInstance(screenTitle, target.wizardState) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationMainVM.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationMainVM.kt index 180efd580be3..d317506bc2e1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationMainVM.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationMainVM.kt @@ -34,6 +34,7 @@ const val KEY_SITE_CREATION_STATE = "key_site_creation_state" @SuppressLint("ParcelCreator") data class SiteCreationState( val segmentId: Long? = null, + val siteDesign: String? = null, val domain: String? = null ) : WizardState, Parcelable @@ -96,6 +97,11 @@ class SiteCreationMainVM @Inject constructor( wizardManager.showNextStep() } + fun onSiteDesignSelected(siteDesign: String, segmentId: Long?) { + siteCreationState = siteCreationState.copy(siteDesign = siteDesign, segmentId = segmentId) + wizardManager.showNextStep() + } + fun onBackPressed() { return if (wizardManager.isLastStep()) { if (siteCreationCompleted) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsFragment.kt index 6fe9820b0544..420c70d3f6c0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsFragment.kt @@ -122,11 +122,7 @@ class SiteCreationDomainsFragment : SiteCreationBaseFormFragment() { (recycler_view.adapter as SiteCreationDomainsAdapter).update(contentState.items) } - private fun getSegmentIdFromArguments(): Long { - return requireNotNull(arguments?.getLong(EXTRA_SEGMENT_ID)) { - "SegmentId is missing. Have you created the fragment using SiteCreationDomainsFragment.newInstance(..)?" - } - } + private fun getSegmentIdFromArguments(): Long? = arguments?.getLong(EXTRA_SEGMENT_ID) override fun onDestroyView() { super.onDestroyView() @@ -137,11 +133,13 @@ class SiteCreationDomainsFragment : SiteCreationBaseFormFragment() { const val TAG = "site_creation_domains_fragment_tag" private const val EXTRA_SEGMENT_ID = "extra_segment_id" - fun newInstance(screenTitle: String, segmentId: Long): SiteCreationDomainsFragment { + fun newInstance(screenTitle: String, segmentId: Long?): SiteCreationDomainsFragment { val fragment = SiteCreationDomainsFragment() val bundle = Bundle() bundle.putString(EXTRA_SCREEN_TITLE, screenTitle) - bundle.putLong(EXTRA_SEGMENT_ID, segmentId) + if (segmentId != null) { + bundle.putLong(EXTRA_SEGMENT_ID, segmentId) + } fragment.arguments = bundle return fragment } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsViewModel.kt index 4d6a955a85e8..f41c6c605a02 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/domains/SiteCreationDomainsViewModel.kt @@ -59,7 +59,7 @@ class SiteCreationDomainsViewModel @Inject constructor( override val coroutineContext: CoroutineContext get() = bgDispatcher + job private var isStarted = false - private var segmentId by Delegates.notNull() + private var segmentId: Long? = null private val _uiState: MutableLiveData = MutableLiveData() val uiState: LiveData = _uiState @@ -90,7 +90,7 @@ class SiteCreationDomainsViewModel @Inject constructor( dispatcher.unregister(fetchDomainsUseCase) } - fun start(segmentId: Long) { + fun start(segmentId: Long?) { if (isStarted) { return } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModel.kt index 908c1c03f2f4..094720f6c3bd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModel.kt @@ -134,6 +134,7 @@ class SitePreviewViewModel @Inject constructor( siteCreationState.apply { val serviceData = SiteCreationServiceData( segmentId, + siteDesign, urlWithoutScheme ) _startCreateSiteService.value = SitePreviewStartServiceData(serviceData, previousState) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceData.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceData.kt index edef246e6d90..d6fcfc12f7e4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceData.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceData.kt @@ -8,5 +8,6 @@ import kotlinx.android.parcel.Parcelize @SuppressLint("ParcelCreator") data class SiteCreationServiceData( val segmentId: Long?, + val siteDesign: String?, val domain: String ) : Parcelable diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt index e97f19cdfdd4..c565aa04fc38 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt @@ -19,7 +19,6 @@ import kotlinx.android.synthetic.main.modal_layout_picker_subtitle_row.* import kotlinx.android.synthetic.main.modal_layout_picker_title_row.* import org.wordpress.android.R import org.wordpress.android.WordPress -import org.wordpress.android.fluxc.model.StarterDesignModel import org.wordpress.android.ui.sitecreation.theme.HomePagePickerViewModel.UiState import org.wordpress.android.util.AniUtils import org.wordpress.android.util.AniUtils.Duration @@ -39,11 +38,6 @@ class HomePagePickerFragment : Fragment() { @Inject lateinit var viewModelFactory: ViewModelProvider.Factory private lateinit var viewModel: HomePagePickerViewModel - companion object { - const val FETCHED_LAYOUTS = "FETCHED_LAYOUTS" - const val SELECTED_LAYOUT = "SELECTED_LAYOUT" - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -68,8 +62,8 @@ class HomePagePickerFragment : Fragment() { } setupUi() + setupViewModel() setupActionListeners() - setupViewModel(savedInstanceState) } override fun onAttach(context: Context) { @@ -77,15 +71,7 @@ class HomePagePickerFragment : Fragment() { (requireActivity().applicationContext as WordPress).component().inject(this) } - override fun onSaveInstanceState(outState: Bundle) { - (viewModel.uiState.value as? UiState.Content)?.let { - outState.putParcelableArrayList(FETCHED_LAYOUTS, ArrayList(viewModel.layouts)) - outState.putString(SELECTED_LAYOUT, it.selectedLayoutSlug) - } - super.onSaveInstanceState(outState) - } - - private fun setupViewModel(savedInstanceState: Bundle?) { + private fun setupViewModel() { viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory) .get(HomePagePickerViewModel::class.java) @@ -97,7 +83,8 @@ class HomePagePickerFragment : Fragment() { layoutsRecyclerView.setVisible(!uiState.loadingIndicatorVisible && !uiState.errorViewVisible) AniUtils.animateBottomBar(bottomToolbar, uiState.isToolbarVisible) when (uiState) { - is UiState.Loading -> {} + is UiState.Loading -> { // Nothing more to do here + } is UiState.Content -> { (layoutsRecyclerView.adapter as? HomePagePickerAdapter)?.setData(uiState.layouts) } @@ -107,13 +94,7 @@ class HomePagePickerFragment : Fragment() { } }) - savedInstanceState?.let { - val layouts = it.getParcelableArrayList(FETCHED_LAYOUTS) - val selected = it.getString(SELECTED_LAYOUT) - viewModel.loadSavedState(layouts, selected) - } ?: run { - viewModel.start() - } + viewModel.start() } private fun setupUi() { @@ -127,10 +108,7 @@ class HomePagePickerFragment : Fragment() { chooseButton.setOnClickListener { viewModel.onChooseTapped() } skipButton.setOnClickListener { viewModel.onSkippedTapped() } errorView.button.setOnClickListener { viewModel.onRetryClicked() } - backButton.setOnClickListener { - requireActivity().onBackPressed() // FIXME: This is temporary for PR #13192 - viewModel.onBackPressed() - } + backButton.setOnClickListener { viewModel.onBackPressed() } setScrollListener() } @@ -140,6 +118,7 @@ class HomePagePickerFragment : Fragment() { appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> viewModel.onAppBarOffsetChanged(verticalOffset, scrollThreshold) }) + viewModel.onAppBarOffsetChanged(0, scrollThreshold) } private fun setTitleVisibility(visible: Boolean) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewHolder.kt index d91e1e57c239..4fc17a36814d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewHolder.kt @@ -6,14 +6,16 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.ImageView.ScaleType.FIT_CENTER import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.card.MaterialCardView import kotlinx.android.synthetic.main.home_page_picker_item.view.* +import org.wordpress.android.R import org.wordpress.android.util.image.ImageManager import org.wordpress.android.util.image.ImageManager.RequestListener import org.wordpress.android.util.image.ImageType import org.wordpress.android.util.setVisible class HomePagePickerViewHolder(view: View, val parent: ViewGroup) : RecyclerView.ViewHolder(view) { - private val container: View = itemView.layout_container + private val container: MaterialCardView = itemView.layout_container private val preview: ImageView = itemView.preview private val selected: ImageView = itemView.selected_overlay @@ -33,6 +35,10 @@ class HomePagePickerViewHolder(view: View, val parent: ViewGroup) : RecyclerView selected.setVisible(uiState.selectedOverlayVisible) preview.contentDescription = parent.context.getString(uiState.contentDescriptionResId, uiState.title) + preview.context?.let { ctx -> + container.strokeWidth = if (uiState.selectedOverlayVisible) + ctx.resources.getDimensionPixelSize(R.dimen.picker_header_selection_overlay_width) else 0 + } container.setOnClickListener { uiState.onItemTapped() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt index 22683d864cac..8c12e1cb8523 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt @@ -1,5 +1,6 @@ package org.wordpress.android.ui.sitecreation.theme +import android.os.Bundle import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -16,10 +17,16 @@ import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD import org.wordpress.android.ui.sitecreation.usecases.FetchHomePageLayoutsUseCase import org.wordpress.android.util.NetworkUtilsWrapper +import org.wordpress.android.viewmodel.SingleLiveEvent import javax.inject.Inject import javax.inject.Named import kotlin.coroutines.CoroutineContext +const val defaultTemplateSlug = "default" + +private const val FETCHED_LAYOUTS = "FETCHED_LAYOUTS" +private const val SELECTED_LAYOUT = "SELECTED_LAYOUT" + class HomePagePickerViewModel @Inject constructor( private val networkUtils: NetworkUtilsWrapper, private val dispatcher: Dispatcher, @@ -36,6 +43,17 @@ class HomePagePickerViewModel @Inject constructor( private val _uiState: MutableLiveData = MutableLiveData() val uiState: LiveData = _uiState + private val _onDesignActionPressed = SingleLiveEvent() + val onDesignActionPressed: LiveData = _onDesignActionPressed + + private val _onBackButtonPressed = SingleLiveEvent() + val onBackButtonPressed: LiveData = _onBackButtonPressed + + sealed class DesignSelectionAction(val template: String, val segmentId: Long?) { + object Skip : DesignSelectionAction(defaultTemplateSlug, null) + class Choose(template: String, segmentId: Long?) : DesignSelectionAction(template, segmentId) + } + init { dispatcher.register(fetchHomePageLayoutsUseCase) } @@ -47,7 +65,9 @@ class HomePagePickerViewModel @Inject constructor( } fun start() { - fetchLayouts() + if (uiState.value !is UiState.Content) { + fetchLayouts() + } } private fun fetchLayouts() { @@ -98,15 +118,21 @@ class HomePagePickerViewModel @Inject constructor( } fun onChooseTapped() { - // TODO + (uiState.value as? UiState.Content)?.let { state -> + layouts.firstOrNull { it.slug != null && it.slug == state.selectedLayoutSlug }?.let { layout -> + _onDesignActionPressed.value = DesignSelectionAction.Choose(layout.slug!!, layout.segmentId) + return + } + } + updateUiState(UiState.Error(toast = R.string.hpp_choose_error)) } fun onSkippedTapped() { - // TODO + _onDesignActionPressed.value = DesignSelectionAction.Skip } fun onBackPressed() { - // TODO + _onBackButtonPressed.call() } fun onRetryClicked() { @@ -117,7 +143,10 @@ class HomePagePickerViewModel @Inject constructor( } } - fun loadSavedState(layouts: List?, selected: String?) { + fun loadSavedState(savedInstanceState: Bundle?) { + if (savedInstanceState == null) return + val layouts = savedInstanceState.getParcelableArrayList(FETCHED_LAYOUTS) + val selected = savedInstanceState.getString(SELECTED_LAYOUT) if (layouts == null || layouts.isEmpty()) { fetchLayouts() return @@ -128,6 +157,13 @@ class HomePagePickerViewModel @Inject constructor( loadLayouts() } + fun writeToBundle(outState: Bundle) { + (uiState.value as? UiState.Content)?.let { + outState.putParcelableArrayList(FETCHED_LAYOUTS, ArrayList(layouts)) + outState.putString(SELECTED_LAYOUT, it.selectedLayoutSlug) + } + } + private fun updateUiState(uiState: UiState) { _uiState.value = uiState } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt index 3c301a5ec8b3..f80f6deb8f76 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/CreateSiteUseCase.kt @@ -53,7 +53,7 @@ class CreateSiteUseCase @Inject constructor( languageWordPressId, siteVisibility, siteData.segmentId, - null, + siteData.siteDesign, dryRun ) continuation = cont diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/FetchDomainsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/FetchDomainsUseCase.kt index 58eb4e6a2a47..0973d333ea93 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/FetchDomainsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/usecases/FetchDomainsUseCase.kt @@ -33,7 +33,7 @@ class FetchDomainsUseCase @Inject constructor( suspend fun fetchDomains( query: String, - segmentId: Long, + segmentId: Long?, includeVendorDot: Boolean = FETCH_DOMAINS_SHOULD_INCLUDE_DOT_BLOG_VENDOR, size: Int = FETCH_DOMAINS_SIZE ): OnSuggestedDomains { @@ -43,6 +43,14 @@ class FetchDomainsUseCase @Inject constructor( size, includeVendorDot ) + + /** + * Depending on the payload the server may override the following values. Setting this values here to get + * reasonable results in case SKIP button is pressed ("default" template) + */ + payload.includeWordpressCom = true + payload.onlyWordpressCom = true + return suspendCancellableCoroutine { cont -> pair = Pair(payload.query, cont) dispatcher.dispatch(SiteActionBuilder.newSuggestDomainsAction(payload)) diff --git a/WordPress/src/main/res/drawable/ic_layout_selection_check_24dp.xml b/WordPress/src/main/res/drawable/ic_layout_selection_check_24dp.xml new file mode 100644 index 000000000000..e4287dc460cc --- /dev/null +++ b/WordPress/src/main/res/drawable/ic_layout_selection_check_24dp.xml @@ -0,0 +1,6 @@ + + + + diff --git a/WordPress/src/main/res/drawable/ic_layout_selection_overlay_153dp.xml b/WordPress/src/main/res/drawable/ic_layout_selection_overlay_153dp.xml deleted file mode 100644 index 65a797a638ba..000000000000 --- a/WordPress/src/main/res/drawable/ic_layout_selection_overlay_153dp.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/WordPress/src/main/res/layout-land/modal_layout_picker_titlebar.xml b/WordPress/src/main/res/layout-land/modal_layout_picker_titlebar.xml index ac5f485007b6..6ed1bdfa8843 100644 --- a/WordPress/src/main/res/layout-land/modal_layout_picker_titlebar.xml +++ b/WordPress/src/main/res/layout-land/modal_layout_picker_titlebar.xml @@ -11,7 +11,7 @@ android:backgroundTint="@color/transparent" android:contentDescription="@string/navigate_back_desc" android:src="@drawable/ic_arrow_left_white_24dp" - android:tint="?attr/colorControlNormal" /> + android:tint="?attr/colorOnSurface" /> @@ -18,10 +20,12 @@ diff --git a/WordPress/src/main/res/layout/home_page_picker_titlebar.xml b/WordPress/src/main/res/layout/home_page_picker_titlebar.xml index 3b4a0325bd4a..1c7cb36355d4 100644 --- a/WordPress/src/main/res/layout/home_page_picker_titlebar.xml +++ b/WordPress/src/main/res/layout/home_page_picker_titlebar.xml @@ -11,7 +11,7 @@ android:backgroundTint="@color/transparent" android:contentDescription="@string/navigate_back_desc" android:src="@drawable/ic_arrow_left_white_24dp" - android:tint="?attr/colorControlNormal" /> + android:tint="?attr/colorOnSurface" /> @@ -17,10 +19,12 @@ diff --git a/WordPress/src/main/res/layout/modal_layout_picker_titlebar.xml b/WordPress/src/main/res/layout/modal_layout_picker_titlebar.xml index c1039c8c4529..2548148cbe8d 100644 --- a/WordPress/src/main/res/layout/modal_layout_picker_titlebar.xml +++ b/WordPress/src/main/res/layout/modal_layout_picker_titlebar.xml @@ -11,7 +11,7 @@ android:backgroundTint="@color/transparent" android:contentDescription="@string/navigate_back_desc" android:src="@drawable/ic_arrow_left_white_24dp" - android:tint="?attr/colorControlNormal" /> + android:tint="?attr/colorOnSurface" /> -54dp @dimen/margin_extra_large + 2dp 281dp diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index fe45a23e50e1..96c57c9810a1 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2936,6 +2936,7 @@ Layouts not available while offline Tap retry when you\'re back online. Please Check your internet connection and retry. + There was an error while selecting the design. Limited Story Editing diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/CreateSiteUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/CreateSiteUseCaseTest.kt index e1ca0edea434..c78f5cf4319e 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/CreateSiteUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/CreateSiteUseCaseTest.kt @@ -26,7 +26,8 @@ import org.wordpress.android.util.UrlUtilsWrapper private val DUMMY_SITE_DATA: SiteCreationServiceData = SiteCreationServiceData( 123, - "slug" + "slug", + "domain" ) private const val LANGUAGE_ID = "lang_id" diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModelTest.kt index a18514ebb84b..201abe9fd4a8 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/previews/SitePreviewViewModelTest.kt @@ -40,6 +40,7 @@ import org.wordpress.android.ui.sitecreation.services.SiteCreationServiceState import org.wordpress.android.ui.sitecreation.services.SiteCreationServiceState.SiteCreationStep.CREATE_SITE import org.wordpress.android.ui.sitecreation.services.SiteCreationServiceState.SiteCreationStep.FAILURE import org.wordpress.android.ui.sitecreation.services.SiteCreationServiceState.SiteCreationStep.SUCCESS +import org.wordpress.android.ui.sitecreation.theme.defaultTemplateSlug import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.util.UrlUtilsWrapper @@ -48,7 +49,7 @@ private const val DOMAIN = ".wordpress.com" private const val URL = "$SUB_DOMAIN$DOMAIN" private const val REMOTE_SITE_ID = 1L private const val LOCAL_SITE_ID = 2 -private val SITE_CREATION_STATE = SiteCreationState(1, URL) +private val SITE_CREATION_STATE = SiteCreationState(1, defaultTemplateSlug, URL) @InternalCoroutinesApi @ExperimentalCoroutinesApi diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceManagerTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceManagerTest.kt index 32bb8708c83d..ef44fdb15dfd 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceManagerTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/services/SiteCreationServiceManagerTest.kt @@ -33,7 +33,8 @@ private const val NEW_SITE_REMOTE_ID = 1234L private val DUMMY_SITE_DATA: SiteCreationServiceData = SiteCreationServiceData( 123, - "slug" + "slug", + "domain" ) private val IDLE_STATE = SiteCreationServiceState(IDLE) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt index 70206f350bf6..cbc386e604ad 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt @@ -20,11 +20,15 @@ import org.wordpress.android.fluxc.store.ThemeStore.OnStarterDesignsFetched import org.wordpress.android.fluxc.store.ThemeStore.ThemeErrorType import org.wordpress.android.fluxc.store.ThemeStore.ThemesError import org.wordpress.android.test +import org.wordpress.android.ui.sitecreation.theme.HomePagePickerViewModel.DesignSelectionAction import org.wordpress.android.ui.sitecreation.theme.HomePagePickerViewModel.UiState import org.wordpress.android.ui.sitecreation.usecases.FetchHomePageLayoutsUseCase import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.util.NoDelayCoroutineDispatcher +private const val mockedDesignSlug = "mockedDesignSlug" +private const val mockedDesignSegmentId = 1L + @RunWith(MockitoJUnitRunner::class) class HomePagePickerViewModelTest { @Rule @@ -34,6 +38,7 @@ class HomePagePickerViewModelTest { @Mock lateinit var networkUtils: NetworkUtilsWrapper @Mock lateinit var fetchHomePageLayoutsUseCase: FetchHomePageLayoutsUseCase @Mock lateinit var uiStateObserver: Observer + @Mock lateinit var onDesignActionObserver: Observer private lateinit var viewModel: HomePagePickerViewModel @@ -47,12 +52,24 @@ class HomePagePickerViewModelTest { NoDelayCoroutineDispatcher() ) viewModel.uiState.observeForever(uiStateObserver) + viewModel.onDesignActionPressed.observeForever(onDesignActionObserver) } private fun mockResponse(isError: Boolean = false, block: suspend CoroutineScope.() -> T) = test { val response = if (isError) OnStarterDesignsFetched(emptyList(), ThemesError(ThemeErrorType.GENERIC_ERROR)) else OnStarterDesignsFetched( - listOf(StarterDesignModel(0, "slug", "title", "site", "demo", "theme", "image")), + listOf( + StarterDesignModel( + 0, + mockedDesignSlug, + "title", + "site", + "demo", + "theme", + mockedDesignSegmentId, + "image" + ) + ), null ) whenever(fetchHomePageLayoutsUseCase.fetchStarterDesigns()).thenReturn(response) @@ -99,24 +116,25 @@ class HomePagePickerViewModelTest { @Test fun `when the user taps on a layout the layout is selected if the thumbnail has loaded`() = mockResponse { viewModel.start() - viewModel.onThumbnailReady("slug") - viewModel.onLayoutTapped("slug") - assertThat(requireNotNull(viewModel.uiState.value as UiState.Content).selectedLayoutSlug).isEqualTo("slug") + viewModel.onThumbnailReady(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug) + assertThat(requireNotNull(viewModel.uiState.value as UiState.Content).selectedLayoutSlug) + .isEqualTo(mockedDesignSlug) } @Test fun `when the user taps on a layout the layout is not selected if the thumbnail has not loaded`() = mockResponse { viewModel.start() - viewModel.onLayoutTapped("slug") + viewModel.onLayoutTapped(mockedDesignSlug) assertThat(requireNotNull(viewModel.uiState.value as UiState.Content).selectedLayoutSlug).isNull() } @Test fun `when the user taps on a selected layout the layout is deselected`() = mockResponse { viewModel.start() - viewModel.onThumbnailReady("slug") - viewModel.onLayoutTapped("slug") - viewModel.onLayoutTapped("slug") + viewModel.onThumbnailReady(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug) assertThat(requireNotNull(viewModel.uiState.value as UiState.Content).selectedLayoutSlug).isNull() } @@ -127,10 +145,32 @@ class HomePagePickerViewModelTest { } @Test - fun `when the user selects a layout the toolbar is shown`() = mockResponse { + fun `when the user selects a design the toolbar is shown`() = mockResponse { viewModel.start() - viewModel.onThumbnailReady("slug") - viewModel.onLayoutTapped("slug") + viewModel.onThumbnailReady(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug) assertThat(viewModel.uiState.value?.isToolbarVisible).isEqualTo(true) } + + @Test + fun `when the user presses skip the default design is selected`() = mockResponse { + viewModel.start() + viewModel.onSkippedTapped() + val captor = ArgumentCaptor.forClass(DesignSelectionAction::class.java) + verify(onDesignActionObserver).onChanged(captor.capture()) + assertThat(captor.value.template).isEqualTo(defaultTemplateSlug) + assertThat(captor.value.segmentId).isNull() + } + + @Test + fun `when the user chooses a design the design info is passed to the next step`() = mockResponse { + viewModel.start() + viewModel.onThumbnailReady(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug) + viewModel.onChooseTapped() + val captor = ArgumentCaptor.forClass(DesignSelectionAction::class.java) + verify(onDesignActionObserver).onChanged(captor.capture()) + assertThat(captor.value.template).isEqualTo(mockedDesignSlug) + assertThat(captor.value.segmentId).isEqualTo(mockedDesignSegmentId) + } } diff --git a/build.gradle b/build.gradle index 7e2138650c54..af7573362a55 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,8 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '1.6.26-beta-5' + fluxCVersion = '1.6.26-beta-6' + appCompatVersion = '1.0.2' coreVersion = '1.2.0' From f2e4094ba57173e828b0b9d4e87cf04dac4d66e9 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 13 Nov 2020 09:25:08 +0200 Subject: [PATCH 321/343] [Home Page Picker] Hides preview button (Round 1) (#13321) * Hides preview button --- .../main/res/layout-sw600dp/home_page_picker_bottom_toolbar.xml | 2 ++ .../src/main/res/layout/home_page_picker_bottom_toolbar.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/WordPress/src/main/res/layout-sw600dp/home_page_picker_bottom_toolbar.xml b/WordPress/src/main/res/layout-sw600dp/home_page_picker_bottom_toolbar.xml index 25d356e64ef1..07c5f6acc201 100644 --- a/WordPress/src/main/res/layout-sw600dp/home_page_picker_bottom_toolbar.xml +++ b/WordPress/src/main/res/layout-sw600dp/home_page_picker_bottom_toolbar.xml @@ -26,6 +26,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/mlp_bottom_button_horizontal_central_margin" android:layout_marginStart="@dimen/mlp_bottom_button_horizontal_margin" + android:visibility="gone" android:text="@string/mlp_preview_page" /> diff --git a/WordPress/src/main/res/layout/home_page_picker_bottom_toolbar.xml b/WordPress/src/main/res/layout/home_page_picker_bottom_toolbar.xml index a9ea56c4de5b..f344d5f23fc2 100644 --- a/WordPress/src/main/res/layout/home_page_picker_bottom_toolbar.xml +++ b/WordPress/src/main/res/layout/home_page_picker_bottom_toolbar.xml @@ -27,6 +27,7 @@ android:layout_marginEnd="@dimen/mlp_bottom_button_horizontal_central_margin" android:layout_marginStart="@dimen/mlp_bottom_button_horizontal_margin" android:layout_weight="1" + android:visibility="gone" android:text="@string/mlp_preview_page" /> From eba211b0238974a59b9a88103dc2baefb4787897 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 13 Nov 2020 09:47:44 +0200 Subject: [PATCH 322/343] [Home Page Picker] Analytic Events (#13336) * Added new analytics events * Adds tracking for design step analytics --- .../sitecreation/misc/SiteCreationTracker.kt | 33 +++++++++++++++++++ .../theme/HomePagePickerViewModel.kt | 14 +++++++- .../theme/HomePagePickerViewModelTest.kt | 3 ++ .../android/analytics/AnalyticsTracker.java | 6 ++++ .../analytics/AnalyticsTrackerNosara.java | 12 +++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt index 0e891818b59e..2445f585aabc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt @@ -89,4 +89,37 @@ class SiteCreationTracker @Inject constructor(val tracker: AnalyticsTrackerWrapp fun trackSiteCreationServiceStateUpdated(props: Map) { tracker.track(AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_BACKGROUND_SERVICE_UPDATED, props) } + + fun trackSiteDesignViewed() { + tracker.track(AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_VIEWED) + } + + fun trackSiteDesignSkipped() { + tracker.track(AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_SKIPPED) + } + + fun trackSiteDesignSelected(template: String) { + tracker.track(AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_SELECTED, mapOf("template" to template)) + } + + fun trackSiteDesignPreviewViewed(template: String) { + tracker.track( + AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_VIEWED, + mapOf("template" to template) + ) + } + + fun trackSiteDesignPreviewLoading(template: String) { + tracker.track( + AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_LOADING, + mapOf("template" to template) + ) + } + + fun trackSiteDesignPreviewLoaded(template: String) { + tracker.track( + AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SUCCESS_PREVIEW_LOADED, + mapOf("template" to template) + ) + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt index 8c12e1cb8523..e9c4bec9ce9e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt @@ -15,6 +15,9 @@ import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.model.StarterDesignModel import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD +import org.wordpress.android.ui.sitecreation.misc.SiteCreationErrorType.INTERNET_UNAVAILABLE_ERROR +import org.wordpress.android.ui.sitecreation.misc.SiteCreationErrorType.UNKNOWN +import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker import org.wordpress.android.ui.sitecreation.usecases.FetchHomePageLayoutsUseCase import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.viewmodel.SingleLiveEvent @@ -24,6 +27,7 @@ import kotlin.coroutines.CoroutineContext const val defaultTemplateSlug = "default" +private const val ERROR_CONTEXT = "design" private const val FETCHED_LAYOUTS = "FETCHED_LAYOUTS" private const val SELECTED_LAYOUT = "SELECTED_LAYOUT" @@ -31,6 +35,7 @@ class HomePagePickerViewModel @Inject constructor( private val networkUtils: NetworkUtilsWrapper, private val dispatcher: Dispatcher, private val fetchHomePageLayoutsUseCase: FetchHomePageLayoutsUseCase, + private val analyticsTracker: SiteCreationTracker, @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher ) : ViewModel(), CoroutineScope { @@ -66,6 +71,7 @@ class HomePagePickerViewModel @Inject constructor( fun start() { if (uiState.value !is UiState.Content) { + analyticsTracker.trackSiteDesignViewed() fetchLayouts() } } @@ -76,6 +82,7 @@ class HomePagePickerViewModel @Inject constructor( val event = fetchHomePageLayoutsUseCase.fetchStarterDesigns() withContext(mainDispatcher) { if (event.isError) { + analyticsTracker.trackErrorShown(ERROR_CONTEXT, UNKNOWN, "Error fetching designs") updateUiState(UiState.Error()) } else { layouts = event.designs @@ -120,14 +127,18 @@ class HomePagePickerViewModel @Inject constructor( fun onChooseTapped() { (uiState.value as? UiState.Content)?.let { state -> layouts.firstOrNull { it.slug != null && it.slug == state.selectedLayoutSlug }?.let { layout -> - _onDesignActionPressed.value = DesignSelectionAction.Choose(layout.slug!!, layout.segmentId) + val template = layout.slug!! + analyticsTracker.trackSiteDesignSelected(template) + _onDesignActionPressed.value = DesignSelectionAction.Choose(template, layout.segmentId) return } } + analyticsTracker.trackErrorShown(ERROR_CONTEXT, UNKNOWN, "Error choosing design") updateUiState(UiState.Error(toast = R.string.hpp_choose_error)) } fun onSkippedTapped() { + analyticsTracker.trackSiteDesignSkipped() _onDesignActionPressed.value = DesignSelectionAction.Skip } @@ -139,6 +150,7 @@ class HomePagePickerViewModel @Inject constructor( if (networkUtils.isNetworkAvailable()) { fetchLayouts() } else { + analyticsTracker.trackErrorShown(ERROR_CONTEXT, INTERNET_UNAVAILABLE_ERROR, "Retry error") updateUiState(UiState.Error(toast = R.string.hpp_retry_error)) } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt index cbc386e604ad..85e17737a557 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt @@ -20,6 +20,7 @@ import org.wordpress.android.fluxc.store.ThemeStore.OnStarterDesignsFetched import org.wordpress.android.fluxc.store.ThemeStore.ThemeErrorType import org.wordpress.android.fluxc.store.ThemeStore.ThemesError import org.wordpress.android.test +import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker import org.wordpress.android.ui.sitecreation.theme.HomePagePickerViewModel.DesignSelectionAction import org.wordpress.android.ui.sitecreation.theme.HomePagePickerViewModel.UiState import org.wordpress.android.ui.sitecreation.usecases.FetchHomePageLayoutsUseCase @@ -39,6 +40,7 @@ class HomePagePickerViewModelTest { @Mock lateinit var fetchHomePageLayoutsUseCase: FetchHomePageLayoutsUseCase @Mock lateinit var uiStateObserver: Observer @Mock lateinit var onDesignActionObserver: Observer + @Mock lateinit var analyticsTracker: SiteCreationTracker private lateinit var viewModel: HomePagePickerViewModel @@ -48,6 +50,7 @@ class HomePagePickerViewModelTest { networkUtils, dispatcher, fetchHomePageLayoutsUseCase, + analyticsTracker, NoDelayCoroutineDispatcher(), NoDelayCoroutineDispatcher() ) diff --git a/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 7067c8e624e6..c509b7429660 100644 --- a/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -447,6 +447,12 @@ public enum Stat { ENHANCED_SITE_CREATION_EXITED, ENHANCED_SITE_CREATION_ERROR_SHOWN, ENHANCED_SITE_CREATION_BACKGROUND_SERVICE_UPDATED, + ENHANCED_SITE_CREATION_SITE_DESIGN_VIEWED, + ENHANCED_SITE_CREATION_SITE_DESIGN_SELECTED, + ENHANCED_SITE_CREATION_SITE_DESIGN_SKIPPED, + ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_VIEWED, + ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_LOADING, + ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_LOADED, // This stat is part of a funnel that provides critical information. Before // making ANY modification to this stat please refer to: p4qSXL-35X-p2 SITE_CREATED, diff --git a/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java b/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java index 143e42dd3bde..ed10fbfead68 100644 --- a/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java +++ b/libs/analytics/WordPressAnalytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java @@ -1379,6 +1379,18 @@ public static String getEventNameForStat(AnalyticsTracker.Stat stat) { return "enhanced_site_creation_error_shown"; case ENHANCED_SITE_CREATION_BACKGROUND_SERVICE_UPDATED: return "enhanced_site_creation_background_service_updated"; + case ENHANCED_SITE_CREATION_SITE_DESIGN_VIEWED: + return "enhanced_site_creation_site_design_viewed"; + case ENHANCED_SITE_CREATION_SITE_DESIGN_SELECTED: + return "enhanced_site_creation_site_design_selected"; + case ENHANCED_SITE_CREATION_SITE_DESIGN_SKIPPED: + return "enhanced_site_creation_site_design_skipped"; + case ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_VIEWED: + return "enhanced_site_creation_site_design_preview_viewed"; + case ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_LOADING: + return "enhanced_site_creation_site_design_preview_loading"; + case ENHANCED_SITE_CREATION_SITE_DESIGN_PREVIEW_LOADED: + return "enhanced_site_creation_site_design_preview_loaded"; case SITE_CREATED: // This stat is part of a funnel that provides critical information. Before // making ANY modification to this stat please refer to: p4qSXL-35X-p2 From d62d540e9ad46aa70062dd7fc1382921678e9357 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 10:43:16 +0100 Subject: [PATCH 323/343] Update FluxC to a tag --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 91292b36a68c..acc65fa51871 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ ext { androidxWorkVersion = "2.0.1" daggerVersion = '2.29.1' - fluxCVersion = '8fcdaa159c8f64f35491f73154f3ee703443f68d' + fluxCVersion = '1.6.26-beta-7' appCompatVersion = '1.0.2' coreVersion = '1.2.0' From c82568b6ec8a45069cc3bafeae874e3ec5a6e1dc Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 12:04:25 +0100 Subject: [PATCH 324/343] Make LiveDataUtils.throttle method testable --- .../org/wordpress/android/util/LiveDataUtils.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/util/LiveDataUtils.kt b/WordPress/src/main/java/org/wordpress/android/util/LiveDataUtils.kt index 195d35e08fd9..a15ef18584cd 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/LiveDataUtils.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/LiveDataUtils.kt @@ -4,7 +4,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Observer import androidx.lifecycle.Transformations +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.wordpress.android.viewmodel.SingleMediatorLiveEvent @@ -376,9 +378,16 @@ fun LiveData.fold(action: (previous: T, current: T) -> T): MediatorLiveDa fun LiveData.throttle( coroutineScope: CoroutineScope, distinct: Boolean = false, - offset: Long = 100 + offset: Long = 100, + backgroundDispatcher: CoroutineDispatcher = Dispatchers.Default, + mainDispatcher: CoroutineDispatcher = Dispatchers.Main ): ThrottleLiveData { - val mediatorLiveData: ThrottleLiveData = ThrottleLiveData(coroutineScope = coroutineScope, offset = offset) + val mediatorLiveData: ThrottleLiveData = ThrottleLiveData( + coroutineScope = coroutineScope, + offset = offset, + backgroundDispatcher = backgroundDispatcher, + mainDispatcher = mainDispatcher + ) mediatorLiveData.addSource(this) { if ((it != mediatorLiveData.value || !distinct) && it != null) { mediatorLiveData.postValue(it) From 2ca9c7293dc35b3b251923c56a433b15d21354e4 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 12:05:00 +0100 Subject: [PATCH 325/343] Make throttle usage in ReaderDiscoverDataProvider testable --- .../ui/reader/repository/ReaderDiscoverDataProvider.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt index 9f16704b2f06..0d9963866ec6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt @@ -12,6 +12,7 @@ import org.greenrobot.eventbus.ThreadMode.BACKGROUND import org.wordpress.android.models.ReaderTag import org.wordpress.android.models.discover.ReaderDiscoverCards import org.wordpress.android.modules.IO_THREAD +import org.wordpress.android.modules.UI_THREAD import org.wordpress.android.ui.reader.ReaderEvents.FetchDiscoverCardsEnded import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsChanged import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.CHANGED @@ -45,6 +46,7 @@ private const val DISCOVER_FEED_THROTTLE = 500L class ReaderDiscoverDataProvider @Inject constructor( @Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher, + @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher, private val eventBusWrapper: EventBusWrapper, private val readerTagWrapper: ReaderTagWrapper, private val getDiscoverCardsUseCase: GetDiscoverCardsUseCase, @@ -66,7 +68,12 @@ class ReaderDiscoverDataProvider @Inject constructor( /* Since we listen to all updates of the database the feed is sometimes updated several times within a few ms. For example, when we are about to insert posts, we delete them first. However, we don't need/want to propagate this state to the VM. */ - .throttle(this, offset = DISCOVER_FEED_THROTTLE) + .throttle( + this, + offset = DISCOVER_FEED_THROTTLE, + backgroundDispatcher = ioDispatcher, + mainDispatcher = mainDispatcher + ) private var hasMoreCards = true From abd022ca38641f59bddba763ee6b51012038b96d Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 12:05:12 +0100 Subject: [PATCH 326/343] Add tests for ReaderDiscoverDataProviderTest.kt --- .../ReaderDiscoverDataProviderTest.kt | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt index 82a69f482f2c..8bcf80421e27 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt @@ -49,7 +49,6 @@ class ReaderDiscoverDataProviderTest { @JvmField val coroutineScope = MainCoroutineScopeRule() private lateinit var dataProvider: ReaderDiscoverDataProvider - private lateinit var readerTag: ReaderTag @Mock private lateinit var eventBusWrapper: EventBusWrapper @Mock private lateinit var getDiscoverCardsUseCase: GetDiscoverCardsUseCase @@ -60,6 +59,7 @@ class ReaderDiscoverDataProviderTest { @Before fun setUp() { dataProvider = ReaderDiscoverDataProvider( + TEST_DISPATCHER, TEST_DISPATCHER, eventBusWrapper, readerTagWrapper, @@ -121,18 +121,41 @@ class ReaderDiscoverDataProviderTest { } @Test - fun `on cards updated has new the data gets posted to discover feed`() = test { - // Arrange + fun `when new observer added and db is empty, does NOT post anything to discover feed`() = test { + whenever(shouldAutoUpdateTagUseCase.get(dataProvider.readerTag)).thenReturn(false) + whenever(getDiscoverCardsUseCase.get()).thenReturn(ReaderDiscoverCards(listOf())) + val event = FetchDiscoverCardsEnded(REQUEST_FIRST_PAGE, HAS_NEW) + + dataProvider.discoverFeed.observeForever { } + + Assertions.assertThat(dataProvider.discoverFeed.value).isNull() + } + @Test + fun `when new observer added and db is NOT empty, the data gets posted to discover feed`() = test { + whenever(shouldAutoUpdateTagUseCase.get(dataProvider.readerTag)).thenReturn(false) whenever(getDiscoverCardsUseCase.get()).thenReturn(createDummyReaderCardsList()) + val event = FetchDiscoverCardsEnded(REQUEST_FIRST_PAGE, HAS_NEW) + + dataProvider.discoverFeed.observeForever { } + + Assertions.assertThat(dataProvider.discoverFeed.value).isNotNull + } + @Test + fun `when request first page finishes, the data gets posted to discover feed`() = test { + // Make sure the provider doesn't emit any values when a new observer is added + whenever(getDiscoverCardsUseCase.get()).thenReturn(ReaderDiscoverCards(listOf())) + whenever(shouldAutoUpdateTagUseCase.get(dataProvider.readerTag)).thenReturn(false) + dataProvider.discoverFeed.observeForever { } + // Make sure the db returns some data + whenever(getDiscoverCardsUseCase.get()).thenReturn(createDummyReaderCardsList()) val event = FetchDiscoverCardsEnded(REQUEST_FIRST_PAGE, HAS_NEW) dataProvider.onCardsUpdated(event) Assertions.assertThat(requireNotNull(dataProvider.discoverFeed.value)) .isInstanceOf(ReaderDiscoverCards::class.java) - Assertions.assertThat(requireNotNull(dataProvider.discoverFeed.value).cards.size) .isEqualTo(NUMBER_OF_ITEMS) } From ca060cc08d0f7cd1cf3e803c8b64e5044581f030 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 12:23:22 +0100 Subject: [PATCH 327/343] Catch IllegalArgumentException coming from Google Photos --- .../src/main/java/org/wordpress/android/util/MediaUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java index d052d0758dc1..c9bebda663f8 100644 --- a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java @@ -182,6 +182,10 @@ public static boolean isInMediaStore(Uri mediaUri) { result = cursor.getString(columnIndexDisplayName); } return result; + } catch (IllegalArgumentException exception) { + // This exception happens when Google Photos tries to retrieve latitude for videos even if it's not + // a requested column + return null; } finally { if (cursor != null) { cursor.close(); From c916f92a1edd563554036235b5df87ce2339c666 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 12:29:20 +0100 Subject: [PATCH 328/343] Remove unused import --- .../ui/reader/repository/ReaderDiscoverDataProviderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt index 8bcf80421e27..b4614c996bf5 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt @@ -15,7 +15,6 @@ import org.mockito.junit.MockitoJUnitRunner import org.wordpress.android.MainCoroutineScopeRule import org.wordpress.android.TEST_DISPATCHER import org.wordpress.android.models.ReaderPost -import org.wordpress.android.models.ReaderTag import org.wordpress.android.models.discover.ReaderDiscoverCard.ReaderPostCard import org.wordpress.android.models.discover.ReaderDiscoverCards import org.wordpress.android.test From 5512424f1cfa34ea1c699190982461e916cbfd3f Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 12:42:05 +0100 Subject: [PATCH 329/343] Update release notes --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f2c9400719be..d3ac1bf8ca5b 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 16.2 ----- * [*] Posts List: fixed bug that prevented showing the Featured Image of a post for pure self-hosted sites [https://github.com/wordpress-mobile/WordPress-Android/pull/13323] +* [*] Reader: Fixes an infinite loading indicator on Discover tab in Reader. 16.1 ----- From 57c46d1b4ffdc85dc3372a072eff3e8317722c7d Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 13:10:34 +0100 Subject: [PATCH 330/343] Add implementation for follow topics button --- .../android/ui/reader/discover/ReaderDiscoverFragment.kt | 4 ++++ .../android/ui/reader/discover/ReaderDiscoverViewModel.kt | 3 ++- .../android/ui/reader/discover/ReaderNavigationEvents.kt | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt index e67d1d0170d6..1f1300732907 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverFragment.kt @@ -36,6 +36,7 @@ import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowNoSit import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowPostDetail import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowPostsByTag import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowReaderComments +import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowReaderSubs import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowReportPost import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowSitePickerForResult import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowVideoViewer @@ -143,6 +144,9 @@ class ReaderDiscoverFragment : ViewPagerFragment(R.layout.reader_discover_fragme OpenUrlType.INTERNAL ) } + is ShowReaderSubs -> { + ReaderActivityLauncher.showReaderSubs(context) + } } } }) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index 56453ac1ceb0..d1ebbbafd736 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -32,6 +32,7 @@ import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.Discover import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowPostsByTag +import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowReaderSubs import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowSitePickerForResult import org.wordpress.android.ui.reader.reblog.ReblogUseCase import org.wordpress.android.ui.reader.repository.ReaderDiscoverCommunication @@ -140,7 +141,7 @@ class ReaderDiscoverViewModel @Inject constructor( swipeToRefreshTriggered = false } else { _uiState.value = ShowNoPostsUiState { - // TODO malinjir navigate to reader tag settings + _navigationEvents.value = Event(ShowReaderSubs) } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderNavigationEvents.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderNavigationEvents.kt index eff4174b261f..a30fc13db57e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderNavigationEvents.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderNavigationEvents.kt @@ -33,4 +33,5 @@ sealed class ReaderNavigationEvents { data class ShowVideoViewer(val videoUrl: String) : ReaderNavigationEvents() data class ShowBlogPreview(val siteId: Long, val feedId: Long) : ReaderNavigationEvents() data class ShowReportPost(val url: String) : ReaderNavigationEvents() + object ShowReaderSubs : ReaderNavigationEvents() } From 21730da2910153be681cd35250e90b7ea35e344b Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 13:49:16 +0100 Subject: [PATCH 331/343] Copy media to local storage in PhotoPickerActivity --- .../ui/photopicker/PhotoPickerActivity.java | 3 +- .../ui/photopicker/PhotoPickerFragment.kt | 42 ++++++++++++++++ .../ui/photopicker/PhotoPickerViewModel.kt | 48 ++++++++++++++----- .../media/CopyMediaToAppStorageUseCase.kt | 6 +-- 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerActivity.java index 29b9d0d12d0a..70b3c4eb9646 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerActivity.java @@ -187,7 +187,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { case RequestCodes.PICTURE_LIBRARY: case RequestCodes.VIDEO_LIBRARY: if (data != null) { - doMediaUrisSelected(WPMediaUtils.retrieveMediaUris(data), PhotoPickerMediaSource.ANDROID_PICKER); + List mediaUris = WPMediaUtils.retrieveMediaUris(data); + getPickerFragment().urisSelectedFromSystemPicker(mediaUris); } break; case RequestCodes.TAKE_PHOTO: diff --git a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt index acea8f44d981..74385ae58e48 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt @@ -9,12 +9,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.PopupMenu +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AlertDialog.Builder import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.android.synthetic.main.photo_picker_fragment.* import kotlinx.android.synthetic.main.photo_picker_fragment.view.* import org.wordpress.android.R @@ -24,6 +27,8 @@ import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.media.MediaBrowserActivity import org.wordpress.android.ui.media.MediaBrowserType import org.wordpress.android.ui.media.MediaPreviewActivity +import org.wordpress.android.ui.mediapicker.MediaPickerViewModel.ProgressDialogUiModel +import org.wordpress.android.ui.mediapicker.MediaPickerViewModel.ProgressDialogUiModel.Visible import org.wordpress.android.ui.photopicker.PhotoPickerFragment.PhotoPickerIcon.WP_MEDIA import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.ActionModeUiModel import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.BottomBarUiModel @@ -41,6 +46,7 @@ import org.wordpress.android.util.AniUtils.Duration.MEDIUM import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T.POSTS import org.wordpress.android.util.DisplayUtils +import org.wordpress.android.util.UriWrapper import org.wordpress.android.util.ViewWrapper import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils @@ -197,6 +203,8 @@ class PhotoPickerFragment : Fragment() { } }) + setupProgressDialog() + viewModel.start(selectedIds, browserType, lastTappedIcon, site) } @@ -304,6 +312,36 @@ class PhotoPickerFragment : Fragment() { } } + private fun setupProgressDialog() { + var progressDialog: AlertDialog? = null + viewModel.uiState.observe(viewLifecycleOwner, Observer { + it?.progressDialogUiModel?.apply { + when (this) { + is Visible -> { + if (progressDialog == null || progressDialog?.isShowing == false) { + val builder: Builder = MaterialAlertDialogBuilder(requireContext()) + builder.setTitle(this.title) + builder.setView(R.layout.media_picker_progress_dialog) + builder.setNegativeButton( + R.string.cancel + ) { _, _ -> this.cancelAction() } + builder.setOnCancelListener { this.cancelAction() } + builder.setCancelable(true) + progressDialog = builder.show() + } + } + ProgressDialogUiModel.Hidden -> { + progressDialog?.let { dialog -> + if (dialog.isShowing) { + dialog.dismiss() + } + } + } + } + } + }) + } + private fun canShowMediaSourceBottomBar( hideMediaBottomBarInPortrait: Boolean ): Boolean { @@ -433,6 +471,10 @@ class PhotoPickerFragment : Fragment() { viewModel.clickIcon(wpMedia) } + fun urisSelectedFromSystemPicker(uris: List) { + viewModel.urisSelectedFromSystemPicker(uris.map { UriWrapper(it) }) + } + companion object { private const val KEY_LAST_TAPPED_ICON = "last_tapped_icon" private const val KEY_SELECTED_POSITIONS = "selected_positions" diff --git a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt index 79c711614cd1..3490e2a0087d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt @@ -4,6 +4,8 @@ import android.Manifest.permission import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker @@ -21,6 +23,7 @@ import org.wordpress.android.ui.media.MediaBrowserType import org.wordpress.android.ui.media.MediaBrowserType.AZTEC_EDITOR_PICKER import org.wordpress.android.ui.media.MediaBrowserType.GRAVATAR_IMAGE_PICKER import org.wordpress.android.ui.media.MediaBrowserType.SITE_ICON_PICKER +import org.wordpress.android.ui.mediapicker.MediaPickerViewModel.ProgressDialogUiModel import org.wordpress.android.ui.photopicker.PhotoPickerFragment.PhotoPickerIcon import org.wordpress.android.ui.photopicker.PhotoPickerFragment.PhotoPickerIcon.ANDROID_CAPTURE_PHOTO import org.wordpress.android.ui.photopicker.PhotoPickerFragment.PhotoPickerIcon.ANDROID_CAPTURE_VIDEO @@ -37,6 +40,7 @@ import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.BottomBarUiMode import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.BottomBarUiModel.BottomBar.MEDIA_SOURCE import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.BottomBarUiModel.BottomBar.NONE import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.PopupMenuUiModel.PopupMenuItem +import org.wordpress.android.ui.posts.editor.media.CopyMediaToAppStorageUseCase import org.wordpress.android.ui.utils.UiString import org.wordpress.android.ui.utils.UiString.UiStringRes import org.wordpress.android.ui.utils.UiString.UiStringText @@ -58,8 +62,10 @@ import java.util.HashMap import javax.inject.Inject import javax.inject.Named -@Deprecated("This class is being refactored, if you implement any change, please also update " + - "{@link org.wordpress.android.ui.mediapicker.MediaPickerViewModel}") +@Deprecated( + "This class is being refactored, if you implement any change, please also update " + + "{@link org.wordpress.android.ui.mediapicker.MediaPickerViewModel}" +) class PhotoPickerViewModel @Inject constructor( @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher, @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, @@ -68,7 +74,8 @@ class PhotoPickerViewModel @Inject constructor( private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, private val permissionsHandler: PermissionsHandler, private val tenorFeatureConfig: TenorFeatureConfig, - private val resourceProvider: ResourceProvider + private val resourceProvider: ResourceProvider, + private val copyMediaToAppStorageUseCase: CopyMediaToAppStorageUseCase ) : ScopedViewModel(mainDispatcher) { private val _navigateToPreview = MutableLiveData>() private val _onInsert = MutableLiveData>>() @@ -78,6 +85,7 @@ class PhotoPickerViewModel @Inject constructor( private val _onIconClicked = MutableLiveData>() private val _onPermissionsRequested = MutableLiveData>() private val _softAskRequest = MutableLiveData() + private val _showProgressDialog = MutableLiveData() val onNavigateToPreview: LiveData> = _navigateToPreview val onInsert: LiveData>> = _onInsert @@ -91,8 +99,9 @@ class PhotoPickerViewModel @Inject constructor( val uiState: LiveData = merge( _photoPickerItems.distinct(), _selectedIds.distinct(), - _softAskRequest - ) { photoPickerItems, selectedIds, softAskRequest -> + _softAskRequest, + _showProgressDialog + ) { photoPickerItems, selectedIds, softAskRequest, progressDialogModel -> PhotoPickerUiState( buildPhotoPickerUiModel(photoPickerItems, selectedIds), buildBottomBar( @@ -104,7 +113,8 @@ class PhotoPickerViewModel @Inject constructor( FabUiModel(browserType.isWPStoriesPicker && selectedIds.isNullOrEmpty()) { clickIcon(WP_STORIES_CAPTURE) }, - buildActionModeUiModel(selectedIds) + buildActionModeUiModel(selectedIds), + progressDialogModel ?: ProgressDialogUiModel.Hidden ) } @@ -440,10 +450,12 @@ class PhotoPickerViewModel @Inject constructor( if (softAskRequest != null && softAskRequest.show) { val appName = "${resourceProvider.getString(R.string.app_name)}" val label = if (softAskRequest.isAlwaysDenied) { - val permissionName = ("${WPPermissionUtils.getPermissionName( - resourceProvider, - permission.WRITE_EXTERNAL_STORAGE - )}") + val permissionName = ("${ + WPPermissionUtils.getPermissionName( + resourceProvider, + permission.WRITE_EXTERNAL_STORAGE + ) + }") String.format( resourceProvider.getString(R.string.photo_picker_soft_ask_permissions_denied), appName, permissionName @@ -465,12 +477,26 @@ class PhotoPickerViewModel @Inject constructor( } } + fun urisSelectedFromSystemPicker(uris: List) { + launch { + _showProgressDialog.value = ProgressDialogUiModel.Visible(R.string.uploading_title) { + cancel() + } + val localUris = copyMediaToAppStorageUseCase.copyFilesToAppStorageIfNecessary(uris.map { it.uri }) + _showProgressDialog.value = ProgressDialogUiModel.Hidden + if (isActive) { + _onInsert.value = Event(localUris.permanentlyAccessibleUris.map { UriWrapper(it) }) + } + } + } + data class PhotoPickerUiState( val photoListUiModel: PhotoListUiModel, val bottomBarUiModel: BottomBarUiModel, val softAskViewUiModel: SoftAskViewUiModel, val fabUiModel: FabUiModel, - val actionModeUiModel: ActionModeUiModel + val actionModeUiModel: ActionModeUiModel, + val progressDialogUiModel: ProgressDialogUiModel ) sealed class PhotoListUiModel { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt index 2fe6c8246314..eeba1da6903f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt @@ -4,7 +4,7 @@ import android.net.Uri import dagger.Reusable import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext -import org.wordpress.android.modules.UI_THREAD +import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T.UTILS import org.wordpress.android.util.MediaUtilsWrapper @@ -14,7 +14,7 @@ import javax.inject.Named @Reusable class CopyMediaToAppStorageUseCase @Inject constructor( private val mediaUtilsWrapper: MediaUtilsWrapper, - @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher + @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher ) { /* * Some media providers (eg. Google Photos) give us a limited access to media files just so we can copy them and then @@ -22,7 +22,7 @@ class CopyMediaToAppStorageUseCase @Inject constructor( * revoked before the action completes. See https://github.com/wordpress-mobile/WordPress-Android/issues/5818 */ suspend fun copyFilesToAppStorageIfNecessary(uriList: List): CopyMediaResult { - return withContext(mainDispatcher) { + return withContext(bgDispatcher) { uriList .map { mediaUri -> if (!mediaUtilsWrapper.isInMediaStore(mediaUri)) { From d3502b93662d7fdea3e3c5d7254dde96024f4965 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 13:56:50 +0100 Subject: [PATCH 332/343] Fix tests --- .../ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt | 3 +++ .../android/ui/photopicker/PhotoPickerViewModelTest.kt | 5 ++++- .../posts/editor/media/CopyMediaToAppStorageUseCaseTest.kt | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt index eeba1da6903f..7b5de9fa8ac2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt @@ -20,6 +20,9 @@ class CopyMediaToAppStorageUseCase @Inject constructor( * Some media providers (eg. Google Photos) give us a limited access to media files just so we can copy them and then * they revoke the access. Copying these files must be performed on the UI thread, otherwise the access might be * revoked before the action completes. See https://github.com/wordpress-mobile/WordPress-Android/issues/5818 + * they revoke the access. Copying these files must be performed within the context (Activity) that requested the + * files, otherwise the access might be revoked before the action completes. + * See https://github.com/wordpress-mobile/WordPress-Android/issues/5818 */ suspend fun copyFilesToAppStorageIfNecessary(uriList: List): CopyMediaResult { return withContext(bgDispatcher) { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModelTest.kt index 3f8ee49751c4..1c1fdcec10a2 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModelTest.kt @@ -28,6 +28,7 @@ import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.BottomBarUiMode import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.PhotoListUiModel import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.PhotoPickerUiState import org.wordpress.android.ui.photopicker.PhotoPickerViewModel.SoftAskViewUiModel +import org.wordpress.android.ui.posts.editor.media.CopyMediaToAppStorageUseCase import org.wordpress.android.ui.utils.UiString import org.wordpress.android.ui.utils.UiString.UiStringRes import org.wordpress.android.ui.utils.UiString.UiStringText @@ -48,6 +49,7 @@ class PhotoPickerViewModelTest : BaseUnitTest() { @Mock lateinit var tenorFeatureConfig: TenorFeatureConfig @Mock lateinit var context: Context @Mock lateinit var resourceProvider: ResourceProvider + @Mock lateinit var copyMediaToAppStorageUseCase: CopyMediaToAppStorageUseCase private lateinit var viewModel: PhotoPickerViewModel private var uiStates = mutableListOf() private var navigateEvents = mutableListOf>() @@ -68,7 +70,8 @@ class PhotoPickerViewModelTest : BaseUnitTest() { analyticsTrackerWrapper, permissionsHandler, tenorFeatureConfig, - resourceProvider + resourceProvider, + copyMediaToAppStorageUseCase ) uiStates.clear() firstItem = PhotoPickerItem(1, uriWrapper1, false) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCaseTest.kt index 149c17f744c5..8623ca68fc51 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCaseTest.kt @@ -16,7 +16,7 @@ import org.wordpress.android.test import org.wordpress.android.util.MediaUtilsWrapper @RunWith(MockitoJUnitRunner::class) -@UseExperimental(InternalCoroutinesApi::class) +@InternalCoroutinesApi class CopyMediaToAppStorageUseCaseTest : BaseUnitTest() { @Test fun `do NOT copy files which are present in media store`() = test { @@ -88,6 +88,7 @@ class CopyMediaToAppStorageUseCaseTest : BaseUnitTest() { } private companion object Fixtures { + @InternalCoroutinesApi fun createCopyMediaToAppStorageUseCase(mediaUtilsWrapper: MediaUtilsWrapper = createMediaUtilsWrapper()) = CopyMediaToAppStorageUseCase(mediaUtilsWrapper, TEST_DISPATCHER) From 27796db0ccf70673a5c665f980e1e7860f253f92 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 14:28:10 +0100 Subject: [PATCH 333/343] Fix broken unit tests --- .../ui/reader/discover/ReaderDiscoverViewModelTest.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt index c3b07cb20a42..718fbe6f8412 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt @@ -41,12 +41,14 @@ import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderRecommen import org.wordpress.android.ui.reader.discover.ReaderCardUiState.ReaderWelcomeBannerCardUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.ContentUiState +import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.RequestFailedUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoFollowedTagsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.EmptyUiState.ShowNoPostsUiState import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel.DiscoverUiState.LoadingUiState import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.OpenEditorForReblog import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowBlogPreview import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowPostsByTag +import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowReaderSubs import org.wordpress.android.ui.reader.discover.ReaderNavigationEvents.ShowSitePickerForResult import org.wordpress.android.ui.reader.discover.ReaderPostCardAction.PrimaryAction import org.wordpress.android.ui.reader.discover.ReaderPostCardActionType.BOOKMARK @@ -357,7 +359,7 @@ class ReaderDiscoverViewModelTest { // Act fakeCommunicationChannel.postValue(Event(NetworkUnavailable(mock()))) // Assert - assertThat(uiStates.last().fullscreenErrorVisibility).isTrue() + assertThat(uiStates.last()).isInstanceOf(RequestFailedUiState::class.java) } @Test @@ -562,11 +564,13 @@ class ReaderDiscoverViewModelTest { } @Test - fun `OnEmptyActionClicked shows select interests screen`() = test { + fun `Action button on no tags empty screen opens reader interests screen`() = test { // Arrange + whenever(getFollowedTagsUseCase.get()).thenReturn(ReaderTagList()) init() + fakeDiscoverFeed.value = ReaderDiscoverCards(listOf()) // Act - viewModel.onEmptyActionClick() + (viewModel.uiState.value as ShowNoFollowedTagsUiState).action.invoke() // Assert verify(parentViewModel).onShowReaderInterests() } From c79368abf20c39a3eaf5f4f52096ddca534ca61b Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 14:28:44 +0100 Subject: [PATCH 334/343] Add unit tests for actions on empty screens --- .../discover/ReaderDiscoverViewModelTest.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt index 718fbe6f8412..a11c01b6d1a9 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModelTest.kt @@ -575,6 +575,29 @@ class ReaderDiscoverViewModelTest { verify(parentViewModel).onShowReaderInterests() } + @Test + fun `Action button on no posts empty screen opens subs screen`() = test { + // Arrange + val navigation = init().navigation + fakeDiscoverFeed.value = ReaderDiscoverCards(listOf()) + // Act + (viewModel.uiState.value as ShowNoPostsUiState).action.invoke() + // Assert + assertThat(navigation[0].peekContent()).isInstanceOf(ShowReaderSubs::class.java) + } + + @Test + fun `Action button on error empty screen invokes refresh cards`() = test { + // Arrange + val uiStates = init(autoUpdateFeed = false).uiStates + viewModel.start(parentViewModel) + fakeCommunicationChannel.postValue(Event(NetworkUnavailable(mock()))) + // Act + (viewModel.uiState.value as RequestFailedUiState).action.invoke() + // Assert + verify(readerDiscoverDataProvider).refreshCards() + } + private fun init(autoUpdateFeed: Boolean = true): Observers { val uiStates = mutableListOf() viewModel.uiState.observeForever { From 1a62b1d3c8f4274ac33921c05e2b83d8b7284219 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 14:31:41 +0100 Subject: [PATCH 335/343] Hide progress dialog correctly when coroutine is cancelled --- .../wordpress/android/ui/photopicker/PhotoPickerViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt index 3490e2a0087d..548ca7d91d30 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerViewModel.kt @@ -480,10 +480,11 @@ class PhotoPickerViewModel @Inject constructor( fun urisSelectedFromSystemPicker(uris: List) { launch { _showProgressDialog.value = ProgressDialogUiModel.Visible(R.string.uploading_title) { + _showProgressDialog.postValue(ProgressDialogUiModel.Hidden) cancel() } val localUris = copyMediaToAppStorageUseCase.copyFilesToAppStorageIfNecessary(uris.map { it.uri }) - _showProgressDialog.value = ProgressDialogUiModel.Hidden + _showProgressDialog.postValue(ProgressDialogUiModel.Hidden) if (isActive) { _onInsert.value = Event(localUris.permanentlyAccessibleUris.map { UriWrapper(it) }) } From 0942ff955aadd9358619b71b3c0d51da37c56fd8 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 14:38:34 +0100 Subject: [PATCH 336/343] Fix lint --- .../android/ui/reader/discover/ReaderDiscoverViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt index d1ebbbafd736..f3b9701f9b53 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/discover/ReaderDiscoverViewModel.kt @@ -241,7 +241,7 @@ class ReaderDiscoverViewModel @Inject constructor( when (uiState) { is LoadingUiState -> { // show fullscreen error - _uiState.value = RequestFailedUiState{ onRetryButtonClick() } + _uiState.value = RequestFailedUiState { onRetryButtonClick() } } is ContentUiState -> { _uiState.value = uiState.copy( From 551b2f9c0e03504b125bd3929f2a8c129c31222a Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 14:41:14 +0100 Subject: [PATCH 337/343] Make progress not cancellable by outside tap --- .../wordpress/android/ui/photopicker/PhotoPickerFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt index 74385ae58e48..00a764baa0f4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/photopicker/PhotoPickerFragment.kt @@ -325,8 +325,7 @@ class PhotoPickerFragment : Fragment() { builder.setNegativeButton( R.string.cancel ) { _, _ -> this.cancelAction() } - builder.setOnCancelListener { this.cancelAction() } - builder.setCancelable(true) + builder.setCancelable(false) progressDialog = builder.show() } } From 1e9844f57f8cffed79999cf635d4bfaeca8bcd12 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 15:27:56 +0100 Subject: [PATCH 338/343] Do not copy files to local storage --- .../ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt | 4 +++- .../main/java/org/wordpress/android/util/MediaUtilsWrapper.kt | 2 ++ .../src/main/java/org/wordpress/android/util/MediaUtils.java | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt index 7b5de9fa8ac2..68e36d7c7d75 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/CopyMediaToAppStorageUseCase.kt @@ -28,7 +28,9 @@ class CopyMediaToAppStorageUseCase @Inject constructor( return withContext(bgDispatcher) { uriList .map { mediaUri -> - if (!mediaUtilsWrapper.isInMediaStore(mediaUri)) { + if (!mediaUtilsWrapper.isInMediaStore(mediaUri) && + // don't copy existing local files + !mediaUtilsWrapper.isFile(mediaUri)) { copyToAppStorage(mediaUri) } else { mediaUri diff --git a/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt index ed5be3b2e539..c9687515590d 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt @@ -49,4 +49,6 @@ class MediaUtilsWrapper @Inject constructor(private val appContext: Context) { fun isLocalFile(uploadState: String): Boolean = MediaUtils.isLocalFile(uploadState) fun getExtensionForMimeType(mimeType: String?): String = MediaUtils.getExtensionForMimeType(mimeType) + + fun isFile(mediaUri: Uri): Boolean = MediaUtils.isFile(mediaUri) } diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java index c9bebda663f8..88ec0f53f755 100644 --- a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java +++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java @@ -169,6 +169,10 @@ public static boolean isInMediaStore(Uri mediaUri) { return mediaUri != null && mediaUri.toString().startsWith("content://media/"); } + public static boolean isFile(Uri mediaUri) { + return mediaUri != null && mediaUri.toString().startsWith("file://"); + } + public static @Nullable String getFilenameFromURI(Context context, Uri uri) { Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); From 326e025700c2d0246a05d3714e46abf1fbd2ed22 Mon Sep 17 00:00:00 2001 From: malinajirka Date: Fri, 13 Nov 2020 15:37:21 +0100 Subject: [PATCH 339/343] Remove didSucceed check from ReaderDiscoverDataProvider --- .../ui/reader/repository/ReaderDiscoverDataProvider.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt index 0d9963866ec6..f706afc42f7f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt @@ -210,10 +210,8 @@ class ReaderDiscoverDataProvider @Inject constructor( @Subscribe(threadMode = BACKGROUND) fun onFollowedTagsChanged(event: FollowedTagsChanged) { - if (event.didSucceed()) { - launch { - refreshCards() - } + launch { + refreshCards() } } } From 6714d1ce12c8d32b895e976d8825812a304767a5 Mon Sep 17 00:00:00 2001 From: vojtasmrcek Date: Fri, 13 Nov 2020 16:53:49 +0100 Subject: [PATCH 340/343] Update release notes for media picker project --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f2c9400719be..19e6eacb883b 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,6 +1,7 @@ 16.2 ----- * [*] Posts List: fixed bug that prevented showing the Featured Image of a post for pure self-hosted sites [https://github.com/wordpress-mobile/WordPress-Android/pull/13323] +* [**] Media picker: New media picker is now used in all the places where the user selects any kind of file (featured image, site icon, gravatar, in posts or media library) except stories [https://github.com/wordpress-mobile/WordPress-Android/pull/13368] 16.1 ----- From 580bb7546546a7a35d3d451108e923dc50fe6943 Mon Sep 17 00:00:00 2001 From: Cameron Voell Date: Fri, 13 Nov 2020 14:16:04 -0800 Subject: [PATCH 341/343] Reverted PR Mobile stories block #13130 --- .idea/codeStyles/Project.xml | 11 +- WordPress/build.gradle | 1 - .../android/modules/AppComponent.java | 3 - .../android/ui/ActivityLauncher.java | 68 +--- .../wordpress/android/ui/RequestCodes.java | 1 - .../android/ui/posts/EditPostActivity.java | 128 +------ .../ui/posts/editor/StoriesEventListener.kt | 317 ------------------ .../media/AddLocalMediaToPostUseCase.kt | 8 - .../posts/editor/media/EditorMediaListener.kt | 3 - .../stories/SaveStoryGutenbergBlockUseCase.kt | 178 ++-------- .../ui/stories/StoryComposerActivity.kt | 219 +----------- .../ui/stories/StoryComposerViewModel.kt | 8 +- .../ui/stories/StoryRepositoryWrapper.kt | 12 - .../media/StoryMediaSaveUploadBridge.kt | 183 +++------- .../android/ui/stories/prefs/StoriesPrefs.kt | 263 --------------- .../LoadStoryFromStoriesPrefsUseCase.kt | 146 -------- .../ui/uploads/MediaUploadReadyProcessor.java | 13 +- WordPress/src/main/res/values/strings.xml | 10 - .../TestContent.kt | 4 +- .../SaveStoryGutenbergBlockUseCaseTest.kt | 287 +--------------- .../ui/stories/StoryComposerViewModelTest.kt | 14 +- .../LoadStoryFromStoriesPrefsUseCaseTest.kt | 188 ----------- .../editor/EditorFragmentAbstract.java | 4 - .../gutenberg/GutenbergContainerFragment.java | 39 +-- .../gutenberg/GutenbergEditorFragment.java | 174 +--------- .../editor/gutenberg/GutenbergPropsBuilder.kt | 2 - .../gutenberg/StorySaveMediaListener.java | 10 - .../src/main/res/values/strings.xml | 4 - libs/gutenberg-mobile | 2 +- libs/stories-android | 2 +- 30 files changed, 145 insertions(+), 2157 deletions(-) delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/posts/editor/StoriesEventListener.kt delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/stories/prefs/StoriesPrefs.kt delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCase.kt delete mode 100644 WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/LoadStoryFromStoriesPrefsUseCaseTest.kt delete mode 100644 libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/gutenberg/StorySaveMediaListener.java diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 3bf04816d9bb..dd2e16ebd1ae 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -32,7 +32,16 @@ +