From 90001f822aad614f6cc0e199849d3b3cdee3b77d Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Tue, 24 Dec 2019 11:18:48 -0500 Subject: [PATCH 001/160] Squashed 'libs/utils/' changes from 4c6e8c9234..a15de7604a a15de7604a Merge pull request #25 from wordpress-mobile/deprecate-buggy-date-time-utils-methods 5026dc0904 Mark DateTimeUtils.localDateToUTC and DateTimeUtils.nowUTC as deprecated 58cd5bf239 Mark DateTimeUtils.localDateToUTC and DateTimeUtils.nowUTC as deprecated 4360e5d41f Merge pull request #23 from wordpress-mobile/update-bintray-config 48277981d8 Bump version to 1.23 81f224bd56 Fix Javadoc lint build error when releasing to Bintray 240f2492fa Update Bintray plugin to latest b6abdcae2a Merge pull request #22 from wordpress-mobile/merge-wpa 8260cdce57 Update style and lint configs 7c08103ff2 Update Gradle wrapper version 9666183e69 Merge commit 'a4a756b60bcbf144ed8527f693c4e974fa6e365d' into subtree-updates-v3 cdeb3eb7f1 Merge pull request #9215 from wordpress-mobile/feature/update-support-lib-28 e8dbd8f404 Revert update of targetSdkVersion f7014365ea Update supportLib version to 28.0.0 b3ad4c795e Fix low hanging deprecation warnings b028488305 Remove all instances of LOCATION bd194edb49 Merge pull request #9114 from wordpress-mobile/issue/8177-applog-testing e081d898e7 Merge pull request #9044 from wordpress-mobile/gradle-4-10 2ba6f52024 Move fake AppLog(the one for testing) from utils to the main project 3f74b509d0 Create copy of AppLog for unit testing purposes 78d9bfbf13 fixed merge conflict b3b6665bfd fixed merge conflict c2fa689153 Update to Gradle 4.10.3/Android Gradle plugin 3.2.1 62b03547dc Merge remote-tracking branch 'origin/develop' into feature/master-site-creation f55c4c33b1 Removed duplicated dots from image URL 818c1691c7 Rename NewSiteCreationService props ab051f9083 removed unused imports 0ea8d2aab7 moved unused getDate() method from MediaUtils to getFormattedDateForLastModified() in PostUtils 6e4cecb37b Make the progress of NewSiteCreationService indeterminate 4434057372 Merge branch 'develop' into feature/master-site-creation e7e89ad67e Fix SiteCreation segment icon color tint 2259033733 Merge branch 'develop' into feature/deep-link-main-activity 0671724495 Merge remote-tracking branch 'origin/develop' into feature/deep-linking-improvements 8f5238e5ef Reorder repositories in build.gradle to fix Gradle 4f18db4eb3 Reorder repositories in build.gradle to fix Gradle e695cb4a6b Refactor isDeepLinking to be outside MyProfileActivity d85b6dbb16 Remove unused WPImageGetter 44d63ede88 Add a new type of log tag for pages 5b4a4f09ae Move the logging wrapper class to FluxCUtils for convenience c7c1ff782a Add Crashlytics logging to make sure we're keeping track of problems uploading media d12f32a4a7 Remove coroutines and replace them with standard event driven architecture 5580689d26 Remove unused code - ImageUtils.getThumbnail git-subtree-dir: libs/utils git-subtree-split: a15de7604a34a9e989884fb84b8c9adfaeeb97b0 --- .idea/checkstyle-idea.xml | 4 +- .idea/codeStyles/Project.xml | 12 ++ WordPressUtils/build.gradle | 19 +- .../wordpress/android/util/ActivityUtils.java | 5 + .../org/wordpress/android/util/AppLog.java | 5 +- .../android/util/AutoForeground.java | 2 +- .../util/AutoForegroundNotification.java | 8 + .../wordpress/android/util/DateTimeUtils.java | 14 +- .../wordpress/android/util/DeviceUtils.java | 1 + .../wordpress/android/util/ImageUtils.java | 37 ---- .../wordpress/android/util/MediaUtils.java | 19 +- .../android/util/PermissionUtils.java | 14 -- .../android/util/helpers/WPImageGetter.java | 184 ------------------ .../android/util/DateTimeUtilsTest.java | 46 +++++ build.gradle | 6 + config/checkstyle.xml | 14 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 17 files changed, 116 insertions(+), 276 deletions(-) delete mode 100644 WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java create mode 100644 WordPressUtils/src/test/java/org/wordpress/android/util/DateTimeUtilsTest.java diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index 0d279f961609..0cfcc74ce7f4 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -3,12 +3,12 @@ \ No newline at end of file diff --git a/WordPressUtils/build.gradle b/WordPressUtils/build.gradle index 451281bdc928..68772d45373d 100644 --- a/WordPressUtils/build.gradle +++ b/WordPressUtils/build.gradle @@ -1,11 +1,11 @@ buildscript { repositories { - jcenter() google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.2' - classpath 'com.novoda:bintray-release:0.8.1' + classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.novoda:bintray-release:0.9' } } @@ -20,21 +20,24 @@ repositories { dependencies { implementation 'org.apache.commons:commons-text:1.1' implementation 'com.mcxiaoke.volley:library:1.0.18' - implementation 'com.android.support:design:27.1.1' - implementation 'com.android.support:recyclerview-v7:27.1.1' + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'org.greenrobot:eventbus:3.0.0' + testImplementation 'junit:junit:4.12' + testImplementation 'org.assertj:assertj-core:3.11.1' + lintChecks 'org.wordpress:lint:1.0.1' } android { useLibrary 'org.apache.http.legacy' - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { - versionName "1.22" + versionName "1.24" minSdkVersion 15 targetSdkVersion 26 } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/ActivityUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/ActivityUtils.java index 03a0dbf8582c..e9c4cb7b4e08 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/ActivityUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/ActivityUtils.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.support.annotation.Nullable; import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -46,4 +47,8 @@ public static void showKeyboard(@Nullable final View view) { (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } + + public static boolean isDeepLinking(Intent intent) { + return Intent.ACTION_VIEW.equals(intent.getAction()); + } } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java b/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java index 81b407aae58b..522ea218f75c 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/AppLog.java @@ -35,6 +35,7 @@ public enum T { NOTIFS, DB, POSTS, + PAGES, COMMENTS, THEMES, TESTS, @@ -48,7 +49,9 @@ public enum T { SHARING, PLUGINS, ACTIVITY_LOG, - SUPPORT + JETPACK_REMOTE_INSTALL, + SUPPORT, + SITE_CREATION } public static final String TAG = "WordPress"; diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java b/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java index 5c4ddedda08b..b141ae85cdbe 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java @@ -174,7 +174,7 @@ protected void setState(StateClass newState) { } } - private void track(ServiceState state) { + protected void track(ServiceState state) { Map props = new HashMap<>(); props.put("login_phase", state == null ? "null" : state.getStepName()); props.put("login_service_is_foreground", isForeground()); diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForegroundNotification.java b/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForegroundNotification.java index 2e3ce5dc5906..d7a130a050aa 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForegroundNotification.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForegroundNotification.java @@ -56,6 +56,14 @@ public static Notification progress(Context context, String channelId, int progr .build(); } + public static Notification progressIndeterminate(Context context, String channelId, @StringRes int title, + @StringRes int content, @DrawableRes int icon, + @ColorRes int accentColor) { + return getNotificationBuilder(context, channelId, NOTIFICATION_ID_PROGRESS, title, content, icon, accentColor) + .setProgress(0, 0, true) + .build(); + } + public static Notification success(Context context, String channelId, @StringRes int title, @StringRes int content, @DrawableRes int icon, @ColorRes int accentColor) { return getNotificationBuilder(context, channelId, NOTIFICATION_ID_SUCCESS, title, content, icon, accentColor) diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/DateTimeUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/DateTimeUtils.java index e7babbd76e4f..3f8f0fee74e4 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/DateTimeUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/DateTimeUtils.java @@ -112,13 +112,25 @@ public static String iso8601UTCFromDate(Date date) { } /** - * Returns the current UTC date + * Returns the current UTC date. + * + * @deprecated This method doesn't work as expected and shouldn't be used in production code. It doesn't take + * into account that `Date` class uses TimeZone.getDefault(). It substracts the currentOffsetFromUTC, but the + * final date still uses system default timezone. */ + @Deprecated public static Date nowUTC() { Date dateTimeNow = new Date(); return localDateToUTC(dateTimeNow); } + /** + * + * @deprecated This method doesn't work as expected and shouldn't be used in production code. It doesn't take + * into account that `Date` class uses TimeZone.getDefault(). It substracts the currentOffsetFromUTC, but the + * final date still uses system default timezone. + */ + @Deprecated public static Date localDateToUTC(Date dtLocal) { if (dtLocal == null) { return null; diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/DeviceUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/DeviceUtils.java index fdc1436461ef..fd8213a6baad 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/DeviceUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/DeviceUtils.java @@ -144,6 +144,7 @@ private static long availableSpaceAtFilePath(File path) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { return stat.getBlockSizeLong() * stat.getAvailableBlocksLong(); } else { + //noinspection deprecation - Deprecated calls properly handled bytesAvailable = (long) stat.getBlockSize() * (long) stat.getAvailableBlocks(); } return bytesAvailable; diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/ImageUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/ImageUtils.java index 3171adcf8cdd..33276e9f900c 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/ImageUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/ImageUtils.java @@ -847,43 +847,6 @@ public static String rotateImageIfNecessary(Context context, String path) { return null; } - /** - * This is a wrapper around MediaStore.Images.Thumbnails.getThumbnail that takes in consideration - * the orientation of the picture. - * - * @param contentResolver ContentResolver used to dispatch queries to MediaProvider. - * @param id Original image id associated with thumbnail of interest. - * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. - * - * @return A Bitmap instance. It could be null if the original image - * associated with origId doesn't exist or memory is not enough. - */ - public static Bitmap getThumbnail(ContentResolver contentResolver, long id, int kind) { - Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, - new String[]{MediaStore.Images.Media.DATA}, // Which columns to return - MediaStore.Images.Media._ID + "=?", // Which rows to return - new String[]{String.valueOf(id)}, // Selection arguments - null); // order - - if (cursor != null && cursor.getCount() > 0) { - cursor.moveToFirst(); - String filepath = cursor.getString(0); - cursor.close(); - int rotation = getExifOrientation(filepath); - Bitmap bitmap = MediaStore.Images.Thumbnails.getThumbnail(contentResolver, id, kind, null); - - if (rotation != 0 && bitmap != null) { - Matrix matrix = new Matrix(); - matrix.setRotate(rotation); - bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - - return bitmap; - } - - return null; - } - // determine correct scale value (should be power of 2) // http://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue/3549021#3549021 protected static int getScaleForResizing(int maxSize, BitmapFactory.Options optBounds) { 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 262a18e35fe5..9a28d305bc3c 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/MediaUtils.java @@ -30,10 +30,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Locale; -import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -97,19 +94,6 @@ public static boolean isGif(String url) { return "gif".equals(MimeTypeMap.getFileExtensionFromUrl(url)); } - /** - * E.g. Jul 2, 2013 @ 21:57 - */ - public static String getDate(long ms) { - Date date = new Date(ms); - SimpleDateFormat sdf = new SimpleDateFormat("MMM d, yyyy '@' HH:mm", Locale.ENGLISH); - - // The timezone on the website is at GMT - sdf.setTimeZone(TimeZone.getTimeZone("GMT")); - - return sdf.format(date); - } - public static boolean isLocalFile(String state) { if (state == null) { return false; @@ -208,7 +192,7 @@ public static Uri downloadExternalMedia(Context context, Uri imageUri) { if (context == null || imageUri == null) { return null; } - String mimeType = context.getContentResolver().getType(imageUri); + String mimeType = UrlUtils.getUrlMimeType(imageUri.toString()); File cacheDir = context.getCacheDir(); if (cacheDir != null && !cacheDir.exists()) { @@ -404,6 +388,7 @@ public static String getRealPathFromURI(final Context context, Uri uri) { } else { path = uri.toString(); } + return path; } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/PermissionUtils.java b/WordPressUtils/src/main/java/org/wordpress/android/util/PermissionUtils.java index b227aff3644d..daecb3e11015 100644 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/PermissionUtils.java +++ b/WordPressUtils/src/main/java/org/wordpress/android/util/PermissionUtils.java @@ -100,18 +100,4 @@ public static boolean checkAndRequestStoragePermission(Fragment fragment, int re permission.WRITE_EXTERNAL_STORAGE }); } - - public static boolean checkLocationPermissions(Activity activity, int requestCode) { - return checkAndRequestPermissions(activity, requestCode, new String[]{ - permission.ACCESS_FINE_LOCATION, - permission.ACCESS_COARSE_LOCATION - }); - } - - public static boolean checkLocationPermissions(Fragment fragment, int requestCode) { - return checkAndRequestPermissions(fragment, requestCode, new String[]{ - permission.ACCESS_FINE_LOCATION, - permission.ACCESS_COARSE_LOCATION - }); - } } diff --git a/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java b/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java deleted file mode 100644 index 9c563054d519..000000000000 --- a/WordPressUtils/src/main/java/org/wordpress/android/util/helpers/WPImageGetter.java +++ /dev/null @@ -1,184 +0,0 @@ -package org.wordpress.android.util.helpers; - -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.support.v4.view.ViewCompat; -import android.text.Html; -import android.text.TextUtils; -import android.widget.TextView; - -import com.android.volley.VolleyError; -import com.android.volley.toolbox.ImageLoader; - -import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.AppLog.T; -import org.wordpress.android.util.PhotonUtils; - -import java.lang.ref.WeakReference; - -/** - * ImageGetter for Html.fromHtml() - * adapted from existing ImageGetter code in NoteCommentFragment - * - * - * @deprecated Use org.wordpress.android.util.image.getters.WPCustomImageGetter instead. - * - */ -@Deprecated -public class WPImageGetter implements Html.ImageGetter { - private final WeakReference mWeakView; - private final int mMaxSize; - private ImageLoader mImageLoader; - private Drawable mLoadingDrawable; - private Drawable mFailedDrawable; - - public WPImageGetter(TextView view) { - this(view, 0); - } - - public WPImageGetter(TextView view, int maxSize) { - mWeakView = new WeakReference(view); - mMaxSize = maxSize; - } - - public WPImageGetter(TextView view, int maxSize, ImageLoader imageLoader, Drawable loadingDrawable, - Drawable failedDrawable) { - mWeakView = new WeakReference(view); - mMaxSize = maxSize; - mImageLoader = imageLoader; - mLoadingDrawable = loadingDrawable; - mFailedDrawable = failedDrawable; - } - - private TextView getView() { - return mWeakView.get(); - } - - @Override - public Drawable getDrawable(String source) { - if (mImageLoader == null || mLoadingDrawable == null || mFailedDrawable == null) { - throw new RuntimeException( - "Developer, you need to call setImageLoader, setLoadingDrawable and setFailedDrawable"); - } - - if (TextUtils.isEmpty(source)) { - return null; - } - - // images in reader comments may skip "http:" (no idea why) so make sure to add protocol here - if (source.startsWith("//")) { - source = "http:" + source; - } - - // use Photon if a max size is requested (otherwise the full-sized image will be downloaded - // and then resized) - if (mMaxSize > 0) { - source = PhotonUtils.getPhotonImageUrl(source, mMaxSize, 0); - } - - final RemoteDrawable remote = new RemoteDrawable(mLoadingDrawable, mFailedDrawable); - - mImageLoader.get(source, new ImageLoader.ImageListener() { - @Override - public void onErrorResponse(VolleyError error) { - remote.displayFailed(); - TextView view = getView(); - if (view != null) { - view.invalidate(); - } - } - - @Override - public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) { - if (response.getBitmap() == null) { - AppLog.w(T.UTILS, "WPImageGetter null bitmap"); - } - - TextView view = getView(); - if (view == null) { - AppLog.w(T.UTILS, "WPImageGetter view is invalid"); - return; - } - - int maxWidth = view.getWidth() - ViewCompat.getPaddingStart(view) - ViewCompat.getPaddingEnd(view); - if (mMaxSize > 0 && (maxWidth > mMaxSize || maxWidth == 0)) { - maxWidth = mMaxSize; - } - - Drawable drawable = new BitmapDrawable(view.getContext().getResources(), response.getBitmap()); - remote.setRemoteDrawable(drawable, maxWidth); - - // force textView to resize correctly if image isn't cached by resetting the content - // to itself - this way the textView will use the cached image, and resizing to - // accommodate the image isn't necessary - if (!isImmediate) { - view.setText(view.getText()); - } - } - }); - - return remote; - } - - public static class RemoteDrawable extends BitmapDrawable { - Drawable mRemoteDrawable; - final Drawable mLoadingDrawable; - final Drawable mFailedDrawable; - private boolean mDidFail = false; - - public RemoteDrawable(Drawable loadingDrawable, Drawable failedDrawable) { - mLoadingDrawable = loadingDrawable; - mFailedDrawable = failedDrawable; - setBounds(0, 0, mLoadingDrawable.getIntrinsicWidth(), mLoadingDrawable.getIntrinsicHeight()); - } - - public void displayFailed() { - mDidFail = true; - } - - public void setBounds(int x, int y, int width, int height) { - super.setBounds(x, y, width, height); - if (mRemoteDrawable != null) { - mRemoteDrawable.setBounds(x, y, width, height); - return; - } - if (mLoadingDrawable != null) { - mLoadingDrawable.setBounds(x, y, width, height); - mFailedDrawable.setBounds(x, y, width, height); - } - } - - public void setRemoteDrawable(Drawable remote, int maxWidth) { - // null sentinel for now - if (remote == null) { - // throw error - return; - } - mRemoteDrawable = remote; - // determine if we need to scale the image to fit in view - int imgWidth = remote.getIntrinsicWidth(); - int imgHeight = remote.getIntrinsicHeight(); - float xScale = (float) imgWidth / (float) maxWidth; - if (xScale > 1.0f) { - setBounds(0, 0, Math.round(imgWidth / xScale), Math.round(imgHeight / xScale)); - } else { - setBounds(0, 0, imgWidth, imgHeight); - } - } - - public boolean didFail() { - return mDidFail; - } - - public void draw(Canvas canvas) { - if (mRemoteDrawable != null) { - mRemoteDrawable.draw(canvas); - } else if (didFail()) { - mFailedDrawable.draw(canvas); - } else { - mLoadingDrawable.draw(canvas); - } - } - } -} diff --git a/WordPressUtils/src/test/java/org/wordpress/android/util/DateTimeUtilsTest.java b/WordPressUtils/src/test/java/org/wordpress/android/util/DateTimeUtilsTest.java new file mode 100644 index 000000000000..a6ae733607e5 --- /dev/null +++ b/WordPressUtils/src/test/java/org/wordpress/android/util/DateTimeUtilsTest.java @@ -0,0 +1,46 @@ +package org.wordpress.android.util; + +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Date; +import java.util.TimeZone; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DateTimeUtilsTest { + private final long mDefaultDate = 1564484058163L; // it's Tue Jul 30 2019 10:54:18 in UTC + + @Test + public void testIso8601UTCFromDate() { + // Arrange + TimeZone.setDefault(TimeZone.getTimeZone("GMT+2:00")); + Date date = new Date(mDefaultDate); + String expected = "2019-07-30T10:54:18+00:00"; + + // Act + String actual = DateTimeUtils.iso8601UTCFromDate(date); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + @Ignore(value = "This test is failing because `DateTimeUtils.localDateToUTC` doesn't work as expected. I've " + + "marked it as deprecated and this tests serves just as a documentation.") + public void testLocalDateToUTC() { + // Arrange + TimeZone.setDefault(TimeZone.getTimeZone("GMT+2:00")); + Date date = new Date(mDefaultDate); + // this succeeds + assertThat(DateTimeUtils.iso8601FromDate(date)).isEqualTo("2019-07-30T12:54:18+0200"); + + // Act + String actual = DateTimeUtils.iso8601FromDate(DateTimeUtils.localDateToUTC(date)); + + // Assert + + // fails because `localDateToUTC` doesn't work as expected. See DateTimeUtils.localDateToUTC for more info. + assertThat(actual).isEqualTo("2019-07-30T10:54:18+00:00"); + } +} diff --git a/build.gradle b/build.gradle index c758834bba8c..ebb5278a193c 100644 --- a/build.gradle +++ b/build.gradle @@ -32,4 +32,10 @@ allprojects { configFile file("${project.rootDir}/config/checkstyle.xml") } } + + // Suppress false Javadoc lint errors preventing Bintray release + // See https://stackoverflow.com/questions/34828426/disable-javadoc-check-for-bintray-upload + tasks.withType(Javadoc) { + options.addBooleanOption('Xdoclint:none', true) + } } diff --git a/config/checkstyle.xml b/config/checkstyle.xml index 5d6705740625..ae088941c51b 100644 --- a/config/checkstyle.xml +++ b/config/checkstyle.xml @@ -34,15 +34,6 @@ - - - - - - - - - @@ -154,7 +145,9 @@ - + + + @@ -181,6 +174,7 @@ + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19199d44dec1..5d5db35cfd1d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip From f2056b2f745276d84cedb54dff82a7e960943178 Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Tue, 24 Dec 2019 11:30:40 -0500 Subject: [PATCH 002/160] updated gradle wrapper of utils project. --- libs/utils/gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/gradle/wrapper/gradle-wrapper.properties b/libs/utils/gradle/wrapper/gradle-wrapper.properties index 5d5db35cfd1d..a4d1bcfaf935 100644 --- a/libs/utils/gradle/wrapper/gradle-wrapper.properties +++ b/libs/utils/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip From fc92d97f946df362e8914a4afe2c81b45b25fd3d Mon Sep 17 00:00:00 2001 From: Joel Dean Date: Tue, 24 Dec 2019 11:34:22 -0500 Subject: [PATCH 003/160] Fixed latitude missing column issue on api 29 --- .../src/main/java/org/wordpress/android/util/MediaUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 a8e6ef96cee2..a23ab7200819 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 @@ -170,7 +170,8 @@ public static boolean isInMediaStore(Uri mediaUri) { } public static @Nullable String getFilenameFromURI(Context context, Uri uri) { - Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, + null, null, null); try { String result = null; if (cursor != null && cursor.moveToFirst()) { From 93f7493cc628c16e2dbb2919b42b0fadeb745c30 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 7 Jan 2020 14:23:55 +0530 Subject: [PATCH 004/160] Fix signup bottom sheet navigation bar buttons not visible issue --- .../login/SignupBottomSheetDialog.java | 24 +++++++++++++++++++ .../signup_bottom_sheet_dialog.xml | 1 + .../res/layout/signup_bottom_sheet_dialog.xml | 1 + 3 files changed, 26 insertions(+) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java index 700670277c69..ac20e64dffdb 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java @@ -2,9 +2,11 @@ import android.content.Context; import android.content.DialogInterface; +import android.os.Build; import android.text.Html; import android.view.LayoutInflater; import android.view.View; +import android.view.Window; import android.widget.Button; import androidx.annotation.NonNull; @@ -66,6 +68,28 @@ public void onShow(DialogInterface dialog) { }); } + @Override + public void onStart() { + super.onStart(); + // Fix to show navigation bar buttons when the signup bottom sheet is shown + // on Android versions API 27 and above. They get shadowed or invisible otherwise. + if (getWindow() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + Window window = getWindow(); + + // You can find the bottom sheet coordinator layout container id here: https://git.io/JejbO + View containerView = window.findViewById(com.google.android.material.R.id.container); + if (containerView != null) { + containerView.setFitsSystemWindows(false); + + // dark navigation bar icons + View decorView = window.getDecorView(); + decorView.setSystemUiVisibility( + decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + ); + } + } + } + public interface SignupSheetListener { void onSignupSheetCanceled(); void onSignupSheetEmailClicked(); diff --git a/libs/login/WordPressLoginFlow/src/main/res/layout-land/signup_bottom_sheet_dialog.xml b/libs/login/WordPressLoginFlow/src/main/res/layout-land/signup_bottom_sheet_dialog.xml index 8d0605260a13..0f5a6453734b 100644 --- a/libs/login/WordPressLoginFlow/src/main/res/layout-land/signup_bottom_sheet_dialog.xml +++ b/libs/login/WordPressLoginFlow/src/main/res/layout-land/signup_bottom_sheet_dialog.xml @@ -8,6 +8,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" + android:fitsSystemWindows="true" android:paddingBottom="@dimen/margin_extra_large" android:paddingLeft="@dimen/margin_extra_large" android:paddingRight="@dimen/margin_extra_large" diff --git a/libs/login/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml b/libs/login/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml index d41a6be9e682..f0408261cdd7 100644 --- a/libs/login/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml +++ b/libs/login/WordPressLoginFlow/src/main/res/layout/signup_bottom_sheet_dialog.xml @@ -8,6 +8,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" + android:fitsSystemWindows="true" android:paddingBottom="@dimen/margin_extra_large" android:paddingLeft="@dimen/margin_extra_large" android:paddingRight="@dimen/margin_extra_large" From 98c445c28237a2f3c10e53e425f64e9e29d6eb8d Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 13 Jan 2020 09:38:25 +0530 Subject: [PATCH 005/160] Convert BottomSheetDialog to BottomSheetDialogFragment and update styles --- .../android/ui/accounts/LoginActivity.java | 23 ++-- .../login/SignupBottomSheetDialog.java | 99 ----------------- .../SignupBottomSheetDialogFragment.java | 100 ++++++++++++++++++ .../login/widgets/WPBottomSheetDialog.java | 34 ------ .../widgets/WPBottomSheetDialogFragment.java | 44 ++++++++ .../src/main/res/values-v27/styles.xml | 10 ++ .../src/main/res/values/styles.xml | 7 ++ 7 files changed, 168 insertions(+), 149 deletions(-) delete mode 100644 libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java create mode 100644 libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java delete mode 100644 libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialog.java create mode 100644 libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java create mode 100644 libs/login/WordPressLoginFlow/src/main/res/values-v27/styles.xml 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 c1eeb20d7019..fb4aab358425 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 @@ -40,8 +40,8 @@ import org.wordpress.android.login.LoginMode; import org.wordpress.android.login.LoginSiteAddressFragment; import org.wordpress.android.login.LoginUsernamePasswordFragment; -import org.wordpress.android.login.SignupBottomSheetDialog; -import org.wordpress.android.login.SignupBottomSheetDialog.SignupSheetListener; +import org.wordpress.android.login.SignupBottomSheetDialogFragment; +import org.wordpress.android.login.SignupBottomSheetDialogFragment.SignupSheetListener; import org.wordpress.android.login.SignupEmailFragment; import org.wordpress.android.login.SignupGoogleFragment; import org.wordpress.android.login.SignupMagicLinkFragment; @@ -105,7 +105,7 @@ private enum SmartLockHelperState { FINISHED } - private SignupBottomSheetDialog mSignupSheet; + private SignupBottomSheetDialogFragment mSignupSheet; private SmartLockHelper mSmartLockHelper; private SmartLockHelperState mSmartLockHelperState = SmartLockHelperState.NOT_TRIGGERED; private JetpackConnectionSource mJetpackConnectSource; @@ -165,8 +165,8 @@ protected void onCreate(Bundle savedInstanceState) { } if (mSignupSheetDisplayed) { - mSignupSheet = new SignupBottomSheetDialog(this, this); - mSignupSheet.show(); + mSignupSheet = new SignupBottomSheetDialogFragment(this); + mSignupSheet.show(getSupportFragmentManager(), SignupBottomSheetDialogFragment.TAG); } } } @@ -179,15 +179,6 @@ public void onSaveInstanceState(Bundle outState) { outState.putString(KEY_SMARTLOCK_HELPER_STATE, mSmartLockHelperState.name()); } - @Override - protected void onDestroy() { - if (mSignupSheetDisplayed && mSignupSheet != null) { - mSignupSheet.dismiss(); - } - - super.onDestroy(); - } - private void showFragment(Fragment fragment, String tag) { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.replace(R.id.fragment_container, fragment, tag); @@ -389,8 +380,8 @@ public void doStartSignup() { // This stat is part of a funnel that provides critical information. Before // making ANY modification to this stat please refer to: p4qSXL-35X-p2 AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_BUTTON_TAPPED); - mSignupSheet = new SignupBottomSheetDialog(this, this); - mSignupSheet.show(); + mSignupSheet = new SignupBottomSheetDialogFragment(this); + mSignupSheet.show(getSupportFragmentManager(), SignupBottomSheetDialogFragment.TAG); mSignupSheetDisplayed = true; } diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java deleted file mode 100644 index ac20e64dffdb..000000000000 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialog.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.wordpress.android.login; - -import android.content.Context; -import android.content.DialogInterface; -import android.os.Build; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.Window; -import android.widget.Button; - -import androidx.annotation.NonNull; - -import com.google.android.material.bottomsheet.BottomSheetBehavior; - -import org.wordpress.android.login.widgets.WPBottomSheetDialog; - -public class SignupBottomSheetDialog extends WPBottomSheetDialog { - public SignupBottomSheetDialog(@NonNull final Context context, - @NonNull final SignupSheetListener signupSheetListener) { - super(context); - //noinspection InflateParams - final View layout = LayoutInflater.from(context).inflate(R.layout.signup_bottom_sheet_dialog, null); - - Button termsOfServiceText = layout.findViewById(R.id.signup_tos); - termsOfServiceText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - signupSheetListener.onSignupSheetTermsOfServiceClicked(); - } - }); - termsOfServiceText.setText(Html.fromHtml(String.format( - context.getResources().getString(R.string.signup_terms_of_service_text), "", ""))); - - Button signupWithEmailButton = layout.findViewById(R.id.signup_email); - signupWithEmailButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - signupSheetListener.onSignupSheetEmailClicked(); - } - }); - - Button signupWithGoogleButton = layout.findViewById(R.id.signup_google); - signupWithGoogleButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - signupSheetListener.onSignupSheetGoogleClicked(); - } - }); - - setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - signupSheetListener.onSignupSheetCanceled(); - } - }); - - setContentView(layout); - - // Set peek height to full height of view to avoid signup buttons being off screen when - // bottom sheet is shown with small screen height (e.g. landscape orientation). - final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) layout.getParent()); - setOnShowListener(new OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - behavior.setPeekHeight(layout.getHeight()); - } - }); - } - - @Override - public void onStart() { - super.onStart(); - // Fix to show navigation bar buttons when the signup bottom sheet is shown - // on Android versions API 27 and above. They get shadowed or invisible otherwise. - if (getWindow() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - Window window = getWindow(); - - // You can find the bottom sheet coordinator layout container id here: https://git.io/JejbO - View containerView = window.findViewById(com.google.android.material.R.id.container); - if (containerView != null) { - containerView.setFitsSystemWindows(false); - - // dark navigation bar icons - View decorView = window.getDecorView(); - decorView.setSystemUiVisibility( - decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR - ); - } - } - } - - public interface SignupSheetListener { - void onSignupSheetCanceled(); - void onSignupSheetEmailClicked(); - void onSignupSheetGoogleClicked(); - void onSignupSheetTermsOfServiceClicked(); - } -} diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java new file mode 100644 index 000000000000..bdbec4a1fbd3 --- /dev/null +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java @@ -0,0 +1,100 @@ +package org.wordpress.android.login; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnShowListener; +import android.os.Bundle; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; + +import org.wordpress.android.login.widgets.WPBottomSheetDialogFragment; + +public class SignupBottomSheetDialogFragment extends WPBottomSheetDialogFragment { + public static final String TAG = SignupBottomSheetDialogFragment.class.getSimpleName(); + private SignupSheetListener mSignupSheetListener; + + public SignupBottomSheetDialogFragment(@NonNull final SignupSheetListener signupSheetListener) { + mSignupSheetListener = signupSheetListener; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + final View layout = inflater.inflate(R.layout.signup_bottom_sheet_dialog, container, false); + + Button termsOfServiceText = layout.findViewById(R.id.signup_tos); + termsOfServiceText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSignupSheetListener.onSignupSheetTermsOfServiceClicked(); + } + }); + termsOfServiceText.setText(Html.fromHtml(String.format( + getDialog() + .getContext() + .getResources() + .getString(R.string.signup_terms_of_service_text), "", ""))); + + Button signupWithEmailButton = layout.findViewById(R.id.signup_email); + signupWithEmailButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSignupSheetListener.onSignupSheetEmailClicked(); + } + }); + + Button signupWithGoogleButton = layout.findViewById(R.id.signup_google); + signupWithGoogleButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mSignupSheetListener.onSignupSheetGoogleClicked(); + } + }); + + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + mSignupSheetListener.onSignupSheetCanceled(); + } + }); + + dialog.setContentView(layout); + + // Set peek height to full height of view to avoid signup buttons being off screen when + // bottom sheet is shown with small screen height (e.g. landscape orientation). + final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) layout.getParent()); + dialog.setOnShowListener(new OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + behavior.setPeekHeight(layout.getHeight()); + } + }); + } + + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + mSignupSheetListener.onSignupSheetCanceled(); + } + + public interface SignupSheetListener { + void onSignupSheetCanceled(); + void onSignupSheetEmailClicked(); + void onSignupSheetGoogleClicked(); + void onSignupSheetTermsOfServiceClicked(); + } +} diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialog.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialog.java deleted file mode 100644 index 9f77f1e2b305..000000000000 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialog.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.wordpress.android.login.widgets; - -import android.content.Context; -import android.view.WindowManager; - -import androidx.annotation.NonNull; - -import com.google.android.material.bottomsheet.BottomSheetDialog; - -import org.wordpress.android.login.R; -import org.wordpress.android.util.DisplayUtils; - -public class WPBottomSheetDialog extends BottomSheetDialog { - public WPBottomSheetDialog(@NonNull Context context) { - super(context); - } - - @Override - public void show() { - super.show(); - - // Limit width of bottom sheet on wide screens; non-zero width defined only for sw600dp. - int dp = (int) getContext().getResources().getDimension(R.dimen.bottom_sheet_dialog_width); - - if (dp > 0) { - int px = DisplayUtils.dpToPx(getContext(), dp); - WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); - layoutParams.copyFrom(getWindow().getAttributes()); - layoutParams.width = px; - layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; - getWindow().setAttributes(layoutParams); - } - } -} diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java new file mode 100644 index 000000000000..07dbc85731d0 --- /dev/null +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java @@ -0,0 +1,44 @@ +package org.wordpress.android.login.widgets; + +import android.app.Dialog; +import android.os.Bundle; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import org.wordpress.android.login.R; + +public class WPBottomSheetDialogFragment extends BottomSheetDialogFragment { + @Override + public int getTheme() { + return R.style.LoginTheme_BottomSheetDialogStyle; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + setRetainInstance(true); + return new BottomSheetDialog(requireContext(), getTheme()); + } + + @Override + public void onResume() { + super.onResume(); + + if (getDialog() != null) { + // Limit width of bottom sheet on wide screens; non-zero width defined only for large qualifier. + int dp = (int) getDialog().getContext().getResources().getDimension(R.dimen.bottom_sheet_dialog_width); + + if (dp > 0) { + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.copyFrom(getDialog().getWindow() != null ? getDialog().getWindow().getAttributes() : null); + layoutParams.width = dp; + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + getDialog().getWindow().setAttributes(layoutParams); + } + } + } +} diff --git a/libs/login/WordPressLoginFlow/src/main/res/values-v27/styles.xml b/libs/login/WordPressLoginFlow/src/main/res/values-v27/styles.xml new file mode 100644 index 000000000000..03a359e2ae1f --- /dev/null +++ b/libs/login/WordPressLoginFlow/src/main/res/values-v27/styles.xml @@ -0,0 +1,10 @@ + + + + diff --git a/libs/login/WordPressLoginFlow/src/main/res/values/styles.xml b/libs/login/WordPressLoginFlow/src/main/res/values/styles.xml index 1902523ff4a3..c35b06ba84aa 100644 --- a/libs/login/WordPressLoginFlow/src/main/res/values/styles.xml +++ b/libs/login/WordPressLoginFlow/src/main/res/values/styles.xml @@ -70,4 +70,11 @@ + + From 4fd8960fe1eb574e941fae2af2080cb18aab4511 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 13 Jan 2020 16:58:28 +0530 Subject: [PATCH 006/160] Avoid using a parameter in Fragment's constructor If Android decides to recreate a Fragment later, it's going to call the no-argument constructor of the fragment. --- .../android/ui/accounts/LoginActivity.java | 4 ++-- .../SignupBottomSheetDialogFragment.java | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) 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 fb4aab358425..c6b6c08e30e1 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 @@ -165,7 +165,7 @@ protected void onCreate(Bundle savedInstanceState) { } if (mSignupSheetDisplayed) { - mSignupSheet = new SignupBottomSheetDialogFragment(this); + mSignupSheet = SignupBottomSheetDialogFragment.newInstance(); mSignupSheet.show(getSupportFragmentManager(), SignupBottomSheetDialogFragment.TAG); } } @@ -380,7 +380,7 @@ public void doStartSignup() { // This stat is part of a funnel that provides critical information. Before // making ANY modification to this stat please refer to: p4qSXL-35X-p2 AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_BUTTON_TAPPED); - mSignupSheet = new SignupBottomSheetDialogFragment(this); + mSignupSheet = SignupBottomSheetDialogFragment.newInstance(); mSignupSheet.show(getSupportFragmentManager(), SignupBottomSheetDialogFragment.TAG); mSignupSheetDisplayed = true; } diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java index bdbec4a1fbd3..0a7a27849c8f 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java @@ -1,6 +1,7 @@ package org.wordpress.android.login; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnShowListener; import android.os.Bundle; @@ -21,8 +22,17 @@ public class SignupBottomSheetDialogFragment extends WPBottomSheetDialogFragment public static final String TAG = SignupBottomSheetDialogFragment.class.getSimpleName(); private SignupSheetListener mSignupSheetListener; - public SignupBottomSheetDialogFragment(@NonNull final SignupSheetListener signupSheetListener) { - mSignupSheetListener = signupSheetListener; + public static SignupBottomSheetDialogFragment newInstance() { + return new SignupBottomSheetDialogFragment(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (!(context instanceof SignupSheetListener)) { + throw new IllegalStateException("Parent activity doesn't implement SignupSheetListener"); + } + mSignupSheetListener = (SignupSheetListener) context; } @Nullable @@ -91,6 +101,12 @@ public void onShow(DialogInterface dialog) { mSignupSheetListener.onSignupSheetCanceled(); } + @Override + public void onDetach() { + super.onDetach(); + mSignupSheetListener = null; + } + public interface SignupSheetListener { void onSignupSheetCanceled(); void onSignupSheetEmailClicked(); From d15dd92f5bdce35d7e403bd8f25f088e289b81f7 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 14 Jan 2020 12:39:14 +0530 Subject: [PATCH 007/160] Remove dialog.setOnDismissListener --- .../android/login/SignupBottomSheetDialogFragment.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java index 0a7a27849c8f..a94888bf7614 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java @@ -73,13 +73,6 @@ public void onClick(View view) { Dialog dialog = getDialog(); if (dialog != null) { - dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mSignupSheetListener.onSignupSheetCanceled(); - } - }); - dialog.setContentView(layout); // Set peek height to full height of view to avoid signup buttons being off screen when From 1d51b1ff3ab51373a4b14f906f98dbd3649047cb Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 14 Jan 2020 12:49:16 +0530 Subject: [PATCH 008/160] Eliminate setRetainInstance(true) --- .../android/login/SignupBottomSheetDialogFragment.java | 4 +++- .../android/login/widgets/WPBottomSheetDialogFragment.java | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java index a94888bf7614..3fbdb3db9487 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java @@ -91,7 +91,9 @@ public void onShow(DialogInterface dialog) { @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); - mSignupSheetListener.onSignupSheetCanceled(); + if (mSignupSheetListener != null) { + mSignupSheetListener.onSignupSheetCanceled(); + } } @Override diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java index 07dbc85731d0..1676689f5732 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java @@ -20,7 +20,6 @@ public int getTheme() { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - setRetainInstance(true); return new BottomSheetDialog(requireContext(), getTheme()); } From 8dcfbc712ed5201f1a227c61a5823255dc56aa07 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 14 Jan 2020 12:53:32 +0530 Subject: [PATCH 009/160] Eliminate logic to manually recreate the dialog(fragment) --- .../wordpress/android/ui/accounts/LoginActivity.java | 11 ----------- 1 file changed, 11 deletions(-) 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 c6b6c08e30e1..c0b9a53ea74e 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 @@ -110,7 +110,6 @@ private enum SmartLockHelperState { private SmartLockHelperState mSmartLockHelperState = SmartLockHelperState.NOT_TRIGGERED; private JetpackConnectionSource mJetpackConnectSource; private boolean mIsJetpackConnect; - private boolean mSignupSheetDisplayed; private LoginMode mLoginMode; @@ -155,7 +154,6 @@ protected void onCreate(Bundle savedInstanceState) { break; } } else { - mSignupSheetDisplayed = savedInstanceState.getBoolean(KEY_SIGNUP_SHEET_DISPLAYED); mSmartLockHelperState = SmartLockHelperState.valueOf( savedInstanceState.getString(KEY_SMARTLOCK_HELPER_STATE)); @@ -163,11 +161,6 @@ protected void onCreate(Bundle savedInstanceState) { // reconnect SmartLockHelper initSmartLockHelperConnection(); } - - if (mSignupSheetDisplayed) { - mSignupSheet = SignupBottomSheetDialogFragment.newInstance(); - mSignupSheet.show(getSupportFragmentManager(), SignupBottomSheetDialogFragment.TAG); - } } } @@ -175,7 +168,6 @@ protected void onCreate(Bundle savedInstanceState) { public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean(KEY_SIGNUP_SHEET_DISPLAYED, mSignupSheetDisplayed); outState.putString(KEY_SMARTLOCK_HELPER_STATE, mSmartLockHelperState.name()); } @@ -382,13 +374,11 @@ public void doStartSignup() { AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_BUTTON_TAPPED); mSignupSheet = SignupBottomSheetDialogFragment.newInstance(); mSignupSheet.show(getSupportFragmentManager(), SignupBottomSheetDialogFragment.TAG); - mSignupSheetDisplayed = true; } @Override public void onSignupSheetCanceled() { AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_CANCELED); - mSignupSheetDisplayed = false; } @Override @@ -817,7 +807,6 @@ public void onPositiveClicked(@NotNull String instanceTag) { private void dismissSignupSheet() { if (mSignupSheet != null) { mSignupSheet.dismiss(); - mSignupSheetDisplayed = false; } } From e0e832749045600d2c3928c464cce5cc47b2294e Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 14 Jan 2020 13:37:52 +0530 Subject: [PATCH 010/160] Extract parts of code from onCreateView to onViewCreated --- .../SignupBottomSheetDialogFragment.java | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java index 3fbdb3db9487..3e63466d5275 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java @@ -10,11 +10,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; import org.wordpress.android.login.widgets.WPBottomSheetDialogFragment; @@ -40,9 +42,14 @@ public void onAttach(Context context) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - final View layout = inflater.inflate(R.layout.signup_bottom_sheet_dialog, container, false); + return inflater.inflate(R.layout.signup_bottom_sheet_dialog, container); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - Button termsOfServiceText = layout.findViewById(R.id.signup_tos); + Button termsOfServiceText = view.findViewById(R.id.signup_tos); termsOfServiceText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -55,7 +62,7 @@ public void onClick(View view) { .getResources() .getString(R.string.signup_terms_of_service_text), "", ""))); - Button signupWithEmailButton = layout.findViewById(R.id.signup_email); + Button signupWithEmailButton = view.findViewById(R.id.signup_email); signupWithEmailButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -63,7 +70,7 @@ public void onClick(View view) { } }); - Button signupWithGoogleButton = layout.findViewById(R.id.signup_google); + Button signupWithGoogleButton = view.findViewById(R.id.signup_google); signupWithGoogleButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -73,20 +80,23 @@ public void onClick(View view) { Dialog dialog = getDialog(); if (dialog != null) { - dialog.setContentView(layout); // Set peek height to full height of view to avoid signup buttons being off screen when // bottom sheet is shown with small screen height (e.g. landscape orientation). - final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) layout.getParent()); dialog.setOnShowListener(new OnShowListener() { @Override - public void onShow(DialogInterface dialog) { - behavior.setPeekHeight(layout.getHeight()); + public void onShow(DialogInterface dialogInterface) { + BottomSheetDialog sheetDialog = (BottomSheetDialog) dialogInterface; + FrameLayout bottomSheet = sheetDialog + .findViewById(com.google.android.material.R.id.design_bottom_sheet); + + if (bottomSheet != null) { + BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } } }); } - - return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onDismiss(DialogInterface dialog) { From db32c52597911d7e7894a4580b75ad1856aa566c Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 14 Jan 2020 13:45:28 +0530 Subject: [PATCH 011/160] Remove empty line after brace --- .../wordpress/android/login/SignupBottomSheetDialogFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java index 3e63466d5275..93ba2342d141 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupBottomSheetDialogFragment.java @@ -80,7 +80,6 @@ public void onClick(View view) { Dialog dialog = getDialog(); if (dialog != null) { - // Set peek height to full height of view to avoid signup buttons being off screen when // bottom sheet is shown with small screen height (e.g. landscape orientation). dialog.setOnShowListener(new OnShowListener() { From 9816868ec61236a8bf495f53b1a7bb240bce632b Mon Sep 17 00:00:00 2001 From: ashiagr Date: Tue, 21 Jan 2020 16:33:08 +0530 Subject: [PATCH 012/160] Show full width navigation bar and restrict max width for large screens BottomSheetDialog's width always matches its parent, even if its content does not, root cause coming from BottomSheetDialog.onCreate() setting the window to MATCH_PARENT both ways: https://issuetracker.google.com/issues/37089648 We're explicitly setting the width of the bottom sheet and centering it inside it's parent coordinator layout. --- .../widgets/WPBottomSheetDialogFragment.java | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java index 1676689f5732..f25a7afc38ba 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java @@ -1,10 +1,13 @@ package org.wordpress.android.login.widgets; import android.app.Dialog; +import android.content.res.Resources; import android.os.Bundle; -import android.view.WindowManager; +import android.view.Gravity; +import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; @@ -27,17 +30,29 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { public void onResume() { super.onResume(); - if (getDialog() != null) { - // Limit width of bottom sheet on wide screens; non-zero width defined only for large qualifier. - int dp = (int) getDialog().getContext().getResources().getDimension(R.dimen.bottom_sheet_dialog_width); - - if (dp > 0) { - WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); - layoutParams.copyFrom(getDialog().getWindow() != null ? getDialog().getWindow().getAttributes() : null); - layoutParams.width = dp; - layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; - getDialog().getWindow().setAttributes(layoutParams); - } + Dialog dialog = getDialog(); + if (dialog != null) { + restrictMaxWidthForDialog(dialog); + } + } + + private void restrictMaxWidthForDialog(@NonNull Dialog dialog) { + Resources resources = dialog.getContext().getResources(); + int dp = (int) resources.getDimension(R.dimen.bottom_sheet_dialog_width); + // Limit width of bottom sheet on wide screens; non-zero width defined only for large qualifier. + if (dp > 0) { + FrameLayout bottomSheetLayout = + dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); + + CoordinatorLayout.LayoutParams coordinatorLayoutParams = + (CoordinatorLayout.LayoutParams) bottomSheetLayout.getLayoutParams(); + coordinatorLayoutParams.width = dp; + bottomSheetLayout.setLayoutParams(coordinatorLayoutParams); + + CoordinatorLayout coordinatorLayout = (CoordinatorLayout) bottomSheetLayout.getParent(); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) coordinatorLayout.getLayoutParams(); + layoutParams.gravity = Gravity.CENTER_HORIZONTAL; + coordinatorLayout.setLayoutParams(layoutParams); } } } From 07eb79802f38f5ca477416df79278a874a9ff6db Mon Sep 17 00:00:00 2001 From: ashiagr Date: Wed, 22 Jan 2020 13:05:01 +0530 Subject: [PATCH 013/160] Update libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java Co-Authored-By: Tyler Heck --- .../android/login/widgets/WPBottomSheetDialogFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java index f25a7afc38ba..e50b2872dbae 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java @@ -39,7 +39,7 @@ public void onResume() { private void restrictMaxWidthForDialog(@NonNull Dialog dialog) { Resources resources = dialog.getContext().getResources(); int dp = (int) resources.getDimension(R.dimen.bottom_sheet_dialog_width); - // Limit width of bottom sheet on wide screens; non-zero width defined only for large qualifier. + // Limit width of bottom sheet on wide screens; non-zero width defined only for sw600dp qualifier. if (dp > 0) { FrameLayout bottomSheetLayout = dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); From 042eb3b1960de3b8bf9fc281fa8c0555a0a868f5 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Wed, 22 Jan 2020 13:10:38 +0530 Subject: [PATCH 014/160] Add null check for design_bottom_sheet layout --- .../widgets/WPBottomSheetDialogFragment.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java index f25a7afc38ba..a17f19b0bf3d 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/widgets/WPBottomSheetDialogFragment.java @@ -4,6 +4,7 @@ import android.content.res.Resources; import android.os.Bundle; import android.view.Gravity; +import android.view.ViewParent; import android.widget.FrameLayout; import androidx.annotation.NonNull; @@ -44,15 +45,21 @@ private void restrictMaxWidthForDialog(@NonNull Dialog dialog) { FrameLayout bottomSheetLayout = dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); - CoordinatorLayout.LayoutParams coordinatorLayoutParams = - (CoordinatorLayout.LayoutParams) bottomSheetLayout.getLayoutParams(); - coordinatorLayoutParams.width = dp; - bottomSheetLayout.setLayoutParams(coordinatorLayoutParams); + if (bottomSheetLayout != null) { + ViewParent bottomSheetParent = bottomSheetLayout.getParent(); + if (bottomSheetParent instanceof CoordinatorLayout) { + CoordinatorLayout.LayoutParams coordinatorLayoutParams = + (CoordinatorLayout.LayoutParams) bottomSheetLayout.getLayoutParams(); + coordinatorLayoutParams.width = dp; + bottomSheetLayout.setLayoutParams(coordinatorLayoutParams); - CoordinatorLayout coordinatorLayout = (CoordinatorLayout) bottomSheetLayout.getParent(); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) coordinatorLayout.getLayoutParams(); - layoutParams.gravity = Gravity.CENTER_HORIZONTAL; - coordinatorLayout.setLayoutParams(layoutParams); + CoordinatorLayout coordinatorLayout = (CoordinatorLayout) bottomSheetParent; + FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) coordinatorLayout.getLayoutParams(); + layoutParams.gravity = Gravity.CENTER_HORIZONTAL; + coordinatorLayout.setLayoutParams(layoutParams); + } + } } } } From 65318dee45f92709400cd734cf07ac376c0da6d2 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Mon, 27 Jan 2020 09:40:50 +0530 Subject: [PATCH 015/160] Add max-width for bottom sheet in landscape mode --- WordPress/src/main/res/values-sw600dp-land/dimens.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/src/main/res/values-sw600dp-land/dimens.xml b/WordPress/src/main/res/values-sw600dp-land/dimens.xml index 7a8defd1cd22..4fd176a6882f 100644 --- a/WordPress/src/main/res/values-sw600dp-land/dimens.xml +++ b/WordPress/src/main/res/values-sw600dp-land/dimens.xml @@ -1,3 +1,5 @@ @dimen/reader_detail_margin_tablet_landscape + + 512dp From 0666345fed195c4f0c7413fac4d2dbab5fa4de24 Mon Sep 17 00:00:00 2001 From: ashiagr Date: Wed, 29 Jan 2020 12:01:33 +0530 Subject: [PATCH 016/160] Add max-width for bottom sheet in landscape mode for phones --- WordPress/src/main/res/values-w600dp-land/dimens.xml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 WordPress/src/main/res/values-w600dp-land/dimens.xml diff --git a/WordPress/src/main/res/values-w600dp-land/dimens.xml b/WordPress/src/main/res/values-w600dp-land/dimens.xml new file mode 100644 index 000000000000..76c02acc0a68 --- /dev/null +++ b/WordPress/src/main/res/values-w600dp-land/dimens.xml @@ -0,0 +1,4 @@ + + + 512dp + From 386a9117fa6232ec139c7da6e81e818be1920e92 Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Wed, 29 Jan 2020 22:33:25 -0300 Subject: [PATCH 017/160] Adding follow button to Reader's followed sites item --- .../main/res/layout/reader_listitem_blog.xml | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/res/layout/reader_listitem_blog.xml b/WordPress/src/main/res/layout/reader_listitem_blog.xml index aeaa25fb9bdc..e68c934da880 100644 --- a/WordPress/src/main/res/layout/reader_listitem_blog.xml +++ b/WordPress/src/main/res/layout/reader_listitem_blog.xml @@ -4,24 +4,26 @@ list item which shows a recommended or followed blog - see ReaderBlogAdapter --> + android:paddingBottom="@dimen/margin_large"> + android:layout_weight="0" + android:contentDescription="@null" + tools:src="@drawable/bg_rectangle_neutral_10_globe_32dp" /> + tools:text="text_title" /> + + From a872e8b6d375a0d338b017a082cbf701aebeef74 Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Wed, 29 Jan 2020 22:39:24 -0300 Subject: [PATCH 018/160] Implementing follow button in Reader's followed sites item --- .../ui/reader/adapters/ReaderBlogAdapter.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java index 5e7e0d2ae0d2..4467169779e8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java @@ -19,9 +19,13 @@ import org.wordpress.android.models.ReaderRecommendBlogList; import org.wordpress.android.models.ReaderRecommendedBlog; import org.wordpress.android.ui.reader.ReaderInterfaces; +import org.wordpress.android.ui.reader.actions.ReaderBlogActions; +import org.wordpress.android.ui.reader.views.ReaderFollowButton; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.StringUtils; +import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.UrlUtils; import org.wordpress.android.util.image.ImageManager; import org.wordpress.android.util.image.ImageType; @@ -151,6 +155,9 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { blogHolder.mTxtUrl.setText(""); } mImageManager.load(blogHolder.mImgBlog, ImageType.BLAVATAR, blogInfo.getImageUrl()); + blogHolder.mFollowButton.setIsFollowed(blogInfo.isFollowing); + blogHolder.mFollowButton.setOnClickListener( + v -> toggleFollow(blogHolder.itemView.getContext(), blogHolder.mFollowButton, blogInfo)); break; } @@ -181,6 +188,7 @@ class BlogViewHolder extends RecyclerView.ViewHolder { private final TextView mTxtDescription; private final TextView mTxtUrl; private final ImageView mImgBlog; + private final ReaderFollowButton mFollowButton; BlogViewHolder(View view) { super(view); @@ -189,14 +197,18 @@ class BlogViewHolder extends RecyclerView.ViewHolder { mTxtDescription = view.findViewById(R.id.text_description); mTxtUrl = view.findViewById(R.id.text_url); mImgBlog = view.findViewById(R.id.image_blog); + mFollowButton = view.findViewById(R.id.follow_button); // followed blogs don't have a description + // recommended blogs don't have a follow button switch (getBlogType()) { case FOLLOWED: mTxtDescription.setVisibility(View.GONE); + mFollowButton.setVisibility(View.VISIBLE); break; case RECOMMENDED: mTxtDescription.setVisibility(View.VISIBLE); + mFollowButton.setVisibility(View.GONE); break; } } @@ -204,6 +216,33 @@ class BlogViewHolder extends RecyclerView.ViewHolder { private boolean mIsTaskRunning = false; + private void toggleFollow(Context context, ReaderFollowButton followButton, ReaderBlog blog) { + if (!NetworkUtils.checkConnection(context)) { + return; + } + + final boolean isAskingToFollow = !blog.isFollowing; + + // disable follow button until API call returns + followButton.setEnabled(false); + + boolean result = ReaderBlogActions.followBlogById(blog.blogId, isAskingToFollow, succeeded -> { + followButton.setEnabled(true); + if (!succeeded) { + int errResId = isAskingToFollow ? R.string.reader_toast_err_follow_blog + : R.string.reader_toast_err_unfollow_blog; + ToastUtils.showToast(context, errResId); + followButton.setIsFollowed(!isAskingToFollow); + blog.isFollowing = !isAskingToFollow; + } + }); + + if (result) { + followButton.setIsFollowedAnimated(isAskingToFollow); + blog.isFollowing = isAskingToFollow; + } + } + private class LoadBlogsTask extends AsyncTask { private ReaderRecommendBlogList mTmpRecommendedBlogs; private ReaderBlogList mTmpFollowedBlogs; From c15a0fef8e7b64b00dcbb67b9c0f5d56c9c4e8fb Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Fri, 31 Jan 2020 23:02:47 -0300 Subject: [PATCH 019/160] Replacing hardcoded dimensions with @dimen resources --- WordPress/src/main/res/layout/reader_listitem_blog.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/res/layout/reader_listitem_blog.xml b/WordPress/src/main/res/layout/reader_listitem_blog.xml index e68c934da880..5aeba08485b0 100644 --- a/WordPress/src/main/res/layout/reader_listitem_blog.xml +++ b/WordPress/src/main/res/layout/reader_listitem_blog.xml @@ -20,7 +20,7 @@ android:id="@+id/image_blog" android:layout_width="@dimen/avatar_sz_medium" android:layout_height="@dimen/avatar_sz_medium" - android:layout_marginEnd="@dimen/margin_large" + android:layout_marginEnd="@dimen/margin_extra_large" android:layout_weight="0" android:contentDescription="@null" tools:src="@drawable/bg_rectangle_neutral_10_globe_32dp" /> @@ -49,6 +49,7 @@ android:id="@+id/text_url" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/margin_medium" android:ellipsize="end" android:gravity="start" android:maxLines="1" @@ -61,6 +62,7 @@ android:id="@+id/text_description" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/margin_medium" android:ellipsize="end" android:gravity="start" android:maxLines="2" @@ -75,7 +77,7 @@ android:id="@+id/follow_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:padding="@dimen/margin_medium" app:wpShowFollowButtonCaption="false" /> From 1d6381b2820e121d209332766e48135bd5870eed Mon Sep 17 00:00:00 2001 From: Renan Ferrari Date: Sat, 1 Feb 2020 00:55:00 -0300 Subject: [PATCH 020/160] Adding Architecture Infrastructure feature flags --- .../ui/reader/adapters/ReaderBlogAdapter.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java index 4467169779e8..6eb7e3935eae 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderBlogAdapter.java @@ -36,6 +36,10 @@ import javax.inject.Inject; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static org.wordpress.android.BuildConfig.INFORMATION_ARCHITECTURE_AVAILABLE; + /* * adapter which shows either recommended or followed blogs - used by ReaderBlogFragment */ @@ -155,9 +159,13 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { blogHolder.mTxtUrl.setText(""); } mImageManager.load(blogHolder.mImgBlog, ImageType.BLAVATAR, blogInfo.getImageUrl()); - blogHolder.mFollowButton.setIsFollowed(blogInfo.isFollowing); - blogHolder.mFollowButton.setOnClickListener( - v -> toggleFollow(blogHolder.itemView.getContext(), blogHolder.mFollowButton, blogInfo)); + if (INFORMATION_ARCHITECTURE_AVAILABLE) { + blogHolder.mFollowButton.setIsFollowed(blogInfo.isFollowing); + blogHolder.mFollowButton.setOnClickListener(v -> toggleFollow( + blogHolder.itemView.getContext(), + blogHolder.mFollowButton, + blogInfo)); + } break; } @@ -203,12 +211,12 @@ class BlogViewHolder extends RecyclerView.ViewHolder { // recommended blogs don't have a follow button switch (getBlogType()) { case FOLLOWED: - mTxtDescription.setVisibility(View.GONE); - mFollowButton.setVisibility(View.VISIBLE); + mTxtDescription.setVisibility(GONE); + mFollowButton.setVisibility(INFORMATION_ARCHITECTURE_AVAILABLE ? VISIBLE : GONE); break; case RECOMMENDED: - mTxtDescription.setVisibility(View.VISIBLE); - mFollowButton.setVisibility(View.GONE); + mTxtDescription.setVisibility(VISIBLE); + mFollowButton.setVisibility(GONE); break; } } From e2604e03837413872fb87562d81e725d848fdb7a Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Thu, 6 Feb 2020 14:32:47 +0100 Subject: [PATCH 021/160] Revert other files --- settings.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings.gradle b/settings.gradle index 3c013b9874e2..f96e02d86463 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,8 @@ include ':WordPressMocks' project(':WordPressMocks').projectDir = new File(rootProject.projectDir, properties.getOrDefault('wp.wordpress_mocks_path', 'libs/mocks') + '/WordPressMocks') if (properties.getOrDefault('wp.BUILD_GUTENBERG_FROM_SOURCE', false).toBoolean()) { + include ':react-native-linear-gradient' + project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, 'libs/gutenberg-mobile/node_modules/react-native-linear-gradient/android') include ':react-native-svg' project(':react-native-svg').projectDir = new File(rootProject.projectDir, 'libs/gutenberg-mobile/node_modules/react-native-svg/android') include ':react-native-aztec' From 87e5179c1e1ea4f385f48a84b16d3beea41274d4 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Thu, 6 Feb 2020 14:34:14 +0100 Subject: [PATCH 022/160] Update ref --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index b0f517b188df..cfadd2d15584 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit b0f517b188df7368e91318537d442c3344736200 +Subproject commit cfadd2d1558431a1668ba1364d4582878f79e035 From 121bb8d663f440da4fda3c1063e58ee472630fc0 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Thu, 6 Feb 2020 16:51:49 +0100 Subject: [PATCH 023/160] Update ref to point to gutenberg-mobile master --- libs/gutenberg-mobile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gutenberg-mobile b/libs/gutenberg-mobile index cfadd2d15584..3a22f087e454 160000 --- a/libs/gutenberg-mobile +++ b/libs/gutenberg-mobile @@ -1 +1 @@ -Subproject commit cfadd2d1558431a1668ba1364d4582878f79e035 +Subproject commit 3a22f087e454ff0d96e9d8a822ae44e92c4041ea From be3ad8b8fa896bfff67b2c7c1e5de271f7d66f68 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Thu, 6 Feb 2020 11:33:08 -0500 Subject: [PATCH 024/160] A rough introduction of AutoSavePostIfNotDraftUseCase --- .../AutosavePostOrUpdateDraftUseCase.kt | 85 +++++++++++++++++++ build.gradle | 2 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt new file mode 100644 index 000000000000..d717398c93e4 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt @@ -0,0 +1,85 @@ +package org.wordpress.android.ui.uploads + +import kotlinx.coroutines.suspendCancellableCoroutine +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode.BACKGROUND +import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.generated.PostActionBuilder +import org.wordpress.android.fluxc.model.CauseOfOnPostChanged.RemoteAutoSavePost +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +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.fluxc.store.PostStore.OnPostChanged +import org.wordpress.android.fluxc.store.PostStore.OnPostStatusFetched +import org.wordpress.android.fluxc.store.PostStore.RemotePostPayload +import org.wordpress.android.ui.uploads.AutoSavePostIfNotDraftResult.PostAutoSaved +import org.wordpress.android.ui.uploads.AutoSavePostIfNotDraftResult.PostIsDraftInRemote +import javax.inject.Inject +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +// TODO: Not be so verbose in naming + +private const val DRAFT_POST_STATUS = "draft" + +interface OnAutoSavePostIfNotDraftCallback { + fun handleAutoSavePostIfNotDraftResult(result: AutoSavePostIfNotDraftResult) +} + +sealed class AutoSavePostIfNotDraftResult { + data class PostIsDraftInRemote(val post: PostModel): AutoSavePostIfNotDraftResult() + data class PostAutoSaved(val post: PostModel): AutoSavePostIfNotDraftResult() +} + +// TODO: Add documentation +// TODO: Add unit tests +class AutoSavePostIfNotDraftUseCase @Inject constructor( + val dispatcher: Dispatcher, + val postStore: PostStore, + val callback: OnAutoSavePostIfNotDraftCallback +) { + private var postStatusPair: Pair>? = null + private var autoSavePair: Pair>? = null + + // TODO: Shouldn't be a suspend function + suspend fun autoSavePostOrUpdateDraft(site: SiteModel, post: PostModel) { + val remotePostPayload = RemotePostPayload(post, site) + val remotePostStatus: String = suspendCancellableCoroutine { cont -> + postStatusPair = Pair(LocalId(post.id), cont) + dispatcher.dispatch(PostActionBuilder.newFetchPostStatusAction(remotePostPayload)) + } + if (remotePostStatus != DRAFT_POST_STATUS) { + dispatcher.dispatch(PostActionBuilder.newRemoteAutoSavePostAction(remotePostPayload)) + } else { + callback.handleAutoSavePostIfNotDraftResult(PostIsDraftInRemote(post)) + } + } + + // TODO: Handle errors + // TODO: Do we need to observe the event in `MAIN` thread? (Probably not) + @Subscribe(threadMode = BACKGROUND) + @Suppress("unused") + fun onPostStatusFetched(event: OnPostStatusFetched) { + postStatusPair?.let { + if (event.post.id == it.first.value) { + it.second.resume(event.remotePostStatus) + postStatusPair = null + } + } + } + + // TODO: Handle errors + // TODO: Do we need to observe the event in `MAIN` thread? (Probably not) + @Subscribe(threadMode = BACKGROUND) + @Suppress("unused") + fun onPostChanged(event: OnPostChanged) { + autoSavePair?.let { + if (event.causeOfChange is RemoteAutoSavePost) { + val postLocalId = (event.causeOfChange as RemoteAutoSavePost).localPostId + val post: PostModel = postStore.getPostByLocalPostId(postLocalId) + callback.handleAutoSavePostIfNotDraftResult(PostAutoSaved(post)) + } + } + } +} diff --git a/build.gradle b/build.gradle index 40696e81a567..4172ba244006 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ buildScan { ext { daggerVersion = '2.22.1' - fluxCVersion = 'c5ea220f70c3a77681d376a17ce5c57e26c1c392' + fluxCVersion = 'bc810ea94e47a7f56184848fbb7e0f46a4de1d53' } // Onboarding and dev env setup tasks From 32da8c436600eaa69a02c69b606efaaebce80869 Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Thu, 6 Feb 2020 12:37:35 -0500 Subject: [PATCH 025/160] Rough integration of AutoSavePostIfNotDraftUseCase --- ...se.kt => AutoSavePostIfNotDraftUseCase.kt} | 48 ++++++++++++------- .../android/ui/uploads/PostUploadHandler.java | 39 ++++++++------- 2 files changed, 52 insertions(+), 35 deletions(-) rename WordPress/src/main/java/org/wordpress/android/ui/uploads/{AutosavePostOrUpdateDraftUseCase.kt => AutoSavePostIfNotDraftUseCase.kt} (63%) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt similarity index 63% rename from WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt rename to WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt index d717398c93e4..d911b98b87c1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutosavePostOrUpdateDraftUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt @@ -1,5 +1,7 @@ package org.wordpress.android.ui.uploads +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.BACKGROUND @@ -8,7 +10,6 @@ import org.wordpress.android.fluxc.generated.PostActionBuilder import org.wordpress.android.fluxc.model.CauseOfOnPostChanged.RemoteAutoSavePost import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId 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.fluxc.store.PostStore.OnPostChanged import org.wordpress.android.fluxc.store.PostStore.OnPostStatusFetched @@ -28,31 +29,41 @@ interface OnAutoSavePostIfNotDraftCallback { } sealed class AutoSavePostIfNotDraftResult { - data class PostIsDraftInRemote(val post: PostModel): AutoSavePostIfNotDraftResult() - data class PostAutoSaved(val post: PostModel): AutoSavePostIfNotDraftResult() + data class PostIsDraftInRemote(val post: PostModel) : AutoSavePostIfNotDraftResult() + data class PostAutoSaved(val post: PostModel) : AutoSavePostIfNotDraftResult() + // TODO: Add error result types } // TODO: Add documentation // TODO: Add unit tests class AutoSavePostIfNotDraftUseCase @Inject constructor( - val dispatcher: Dispatcher, - val postStore: PostStore, - val callback: OnAutoSavePostIfNotDraftCallback + private val dispatcher: Dispatcher, + private val postStore: PostStore ) { + // TODO: Make it reusable private var postStatusPair: Pair>? = null private var autoSavePair: Pair>? = null - // TODO: Shouldn't be a suspend function - suspend fun autoSavePostOrUpdateDraft(site: SiteModel, post: PostModel) { - val remotePostPayload = RemotePostPayload(post, site) - val remotePostStatus: String = suspendCancellableCoroutine { cont -> - postStatusPair = Pair(LocalId(post.id), cont) - dispatcher.dispatch(PostActionBuilder.newFetchPostStatusAction(remotePostPayload)) - } - if (remotePostStatus != DRAFT_POST_STATUS) { - dispatcher.dispatch(PostActionBuilder.newRemoteAutoSavePostAction(remotePostPayload)) - } else { - callback.handleAutoSavePostIfNotDraftResult(PostIsDraftInRemote(post)) + fun autoSavePostOrUpdateDraft( + remotePostPayload: RemotePostPayload, + callback: OnAutoSavePostIfNotDraftCallback + ) { + // TODO: What scope should we use for this? + GlobalScope.launch { + val remotePostStatus: String = suspendCancellableCoroutine { cont -> + postStatusPair = Pair(LocalId(remotePostPayload.post.id), cont) + dispatcher.dispatch(PostActionBuilder.newFetchPostStatusAction(remotePostPayload)) + } + + if (remotePostStatus == DRAFT_POST_STATUS) { + callback.handleAutoSavePostIfNotDraftResult(PostIsDraftInRemote(remotePostPayload.post)) + } else { + val updatedPost: PostModel = suspendCancellableCoroutine { cont -> + autoSavePair = Pair(LocalId(remotePostPayload.post.id), cont) + dispatcher.dispatch(PostActionBuilder.newRemoteAutoSavePostAction(remotePostPayload)) + } + callback.handleAutoSavePostIfNotDraftResult(PostAutoSaved(updatedPost)) + } } } @@ -78,7 +89,8 @@ class AutoSavePostIfNotDraftUseCase @Inject constructor( if (event.causeOfChange is RemoteAutoSavePost) { val postLocalId = (event.causeOfChange as RemoteAutoSavePost).localPostId val post: PostModel = postStore.getPostByLocalPostId(postLocalId) - callback.handleAutoSavePostIfNotDraftResult(PostAutoSaved(post)) + it.second.resume(post) + autoSavePair = null } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadHandler.java b/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadHandler.java index 1217be8a107c..e95e147f2658 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadHandler.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadHandler.java @@ -14,14 +14,13 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import org.jetbrains.annotations.NotNull; import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker.Stat; import org.wordpress.android.fluxc.Dispatcher; import org.wordpress.android.fluxc.generated.MediaActionBuilder; import org.wordpress.android.fluxc.generated.PostActionBuilder; -import org.wordpress.android.fluxc.model.CauseOfOnPostChanged; -import org.wordpress.android.fluxc.model.CauseOfOnPostChanged.RemoteAutoSavePost; import org.wordpress.android.fluxc.model.MediaModel; import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState; import org.wordpress.android.fluxc.model.PostImmutableModel; @@ -31,12 +30,12 @@ import org.wordpress.android.fluxc.store.MediaStore; import org.wordpress.android.fluxc.store.MediaStore.UploadMediaPayload; import org.wordpress.android.fluxc.store.PostStore; -import org.wordpress.android.fluxc.store.PostStore.OnPostChanged; import org.wordpress.android.fluxc.store.PostStore.OnPostUploaded; import org.wordpress.android.fluxc.store.PostStore.RemotePostPayload; import org.wordpress.android.fluxc.store.SiteStore; import org.wordpress.android.ui.posts.PostUtils; import org.wordpress.android.ui.prefs.AppPrefs; +import org.wordpress.android.ui.uploads.AutoSavePostIfNotDraftResult.PostAutoSaved; import org.wordpress.android.ui.uploads.PostEvents.PostUploadStarted; import org.wordpress.android.ui.utils.UiHelpers; import org.wordpress.android.util.AppLog; @@ -62,7 +61,7 @@ import javax.inject.Inject; -public class PostUploadHandler implements UploadHandler { +public class PostUploadHandler implements UploadHandler, OnAutoSavePostIfNotDraftCallback { private static ArrayList sQueuedPostsList = new ArrayList<>(); private static Set sFirstPublishPosts = new HashSet<>(); private static PostModel sCurrentUploadingPost = null; @@ -79,6 +78,7 @@ public class PostUploadHandler implements UploadHandler { @Inject MediaStore mMediaStore; @Inject UiHelpers mUiHelpers; @Inject UploadActionUseCase mUploadActionUseCase; + @Inject AutoSavePostIfNotDraftUseCase mAutoSavePostIfNotDraftUseCase; PostUploadHandler(PostUploadNotifier postUploadNotifier) { ((WordPress) WordPress.getContext().getApplicationContext()).component().inject(this); @@ -286,7 +286,7 @@ protected UploadPostTaskResult doInBackground(PostModel... posts) { break; case REMOTE_AUTO_SAVE: AppLog.d(T.POSTS, "PostUploadHandler - REMOTE_AUTO_SAVE. Post: " + mPost.getTitle()); - mDispatcher.dispatch(PostActionBuilder.newRemoteAutoSavePostAction(payload)); + mAutoSavePostIfNotDraftUseCase.autoSavePostOrUpdateDraft(payload, PostUploadHandler.this); break; case DO_NOTHING: AppLog.d(T.POSTS, "PostUploadHandler - DO_NOTHING. Post: " + mPost.getTitle()); @@ -604,6 +604,23 @@ private String uploadImageFile(MediaFile mediaFile, SiteModel site) { } } + // TODO: document? + @Override + public void handleAutoSavePostIfNotDraftResult(@NotNull AutoSavePostIfNotDraftResult result) { + // TODO: handle other cases + if (result instanceof AutoSavePostIfNotDraftResult.PostAutoSaved) { + mPostUploadNotifier + .incrementUploadedPostCountFromForegroundNotification(((PostAutoSaved) result).getPost()); + finishUpload(); + } else if (result instanceof AutoSavePostIfNotDraftResult.PostIsDraftInRemote) { + /** + * 1. If the post has a status that's not draft in local, remember that + * 2. Set the post status to draft and push post + * 3. Update the local copy of post with the previous post status + */ + } + } + /** * Has priority 9 on OnPostUploaded events, which ensures that PostUploadHandler is the first to receive * and process OnPostUploaded events, before they trickle down to other subscribers. @@ -660,16 +677,4 @@ public void onPostUploaded(OnPostUploaded event) { finishUpload(); } - - @SuppressWarnings("unused") - @Subscribe(threadMode = ThreadMode.MAIN, priority = 9) - public void onPostChanged(OnPostChanged event) { - if (event.causeOfChange instanceof CauseOfOnPostChanged.RemoteAutoSavePost) { - int postLocalId = ((RemoteAutoSavePost) event.causeOfChange).getLocalPostId(); - PostModel post = mPostStore.getPostByLocalPostId(postLocalId); - SiteModel site = mSiteStore.getSiteByLocalId(post.getLocalSiteId()); - mPostUploadNotifier.incrementUploadedPostCountFromForegroundNotification(post); - finishUpload(); - } - } } From ab808707a579b25c543e8d9bdf423a9cb8d38f2f Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Thu, 6 Feb 2020 15:19:35 -0500 Subject: [PATCH 026/160] Refactor AutoSavePostIfNotDraftUseCase so it's reusable --- .../uploads/AutoSavePostIfNotDraftUseCase.kt | 49 ++++++++++++------- build.gradle | 2 +- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt index d911b98b87c1..fbdd660763c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/AutoSavePostIfNotDraftUseCase.kt @@ -40,18 +40,28 @@ class AutoSavePostIfNotDraftUseCase @Inject constructor( private val dispatcher: Dispatcher, private val postStore: PostStore ) { - // TODO: Make it reusable - private var postStatusPair: Pair>? = null - private var autoSavePair: Pair>? = null + private val postStatusContinuations = HashMap>() + private val autoSaveContinuations = HashMap>() + + init { + dispatcher.register(this) + } fun autoSavePostOrUpdateDraft( remotePostPayload: RemotePostPayload, callback: OnAutoSavePostIfNotDraftCallback ) { + val localPostId = LocalId(remotePostPayload.post.id) + if (postStatusContinuations.containsKey(localPostId) || + autoSaveContinuations.containsKey(localPostId)) { + // TODO: Consider canceling the previous job instead + // We are already handling this post + return + } // TODO: What scope should we use for this? GlobalScope.launch { val remotePostStatus: String = suspendCancellableCoroutine { cont -> - postStatusPair = Pair(LocalId(remotePostPayload.post.id), cont) + postStatusContinuations[localPostId] = cont dispatcher.dispatch(PostActionBuilder.newFetchPostStatusAction(remotePostPayload)) } @@ -59,8 +69,12 @@ class AutoSavePostIfNotDraftUseCase @Inject constructor( callback.handleAutoSavePostIfNotDraftResult(PostIsDraftInRemote(remotePostPayload.post)) } else { val updatedPost: PostModel = suspendCancellableCoroutine { cont -> - autoSavePair = Pair(LocalId(remotePostPayload.post.id), cont) - dispatcher.dispatch(PostActionBuilder.newRemoteAutoSavePostAction(remotePostPayload)) + autoSaveContinuations[localPostId] = cont + dispatcher.dispatch( + PostActionBuilder.newRemoteAutoSavePostAction( + remotePostPayload + ) + ) } callback.handleAutoSavePostIfNotDraftResult(PostAutoSaved(updatedPost)) } @@ -68,29 +82,26 @@ class AutoSavePostIfNotDraftUseCase @Inject constructor( } // TODO: Handle errors - // TODO: Do we need to observe the event in `MAIN` thread? (Probably not) @Subscribe(threadMode = BACKGROUND) @Suppress("unused") fun onPostStatusFetched(event: OnPostStatusFetched) { - postStatusPair?.let { - if (event.post.id == it.first.value) { - it.second.resume(event.remotePostStatus) - postStatusPair = null - } + val localPostId = LocalId(event.post.id) + postStatusContinuations[localPostId]?.let { continuation -> + continuation.resume(event.remotePostStatus) + postStatusContinuations.remove(localPostId) } } // TODO: Handle errors - // TODO: Do we need to observe the event in `MAIN` thread? (Probably not) @Subscribe(threadMode = BACKGROUND) @Suppress("unused") fun onPostChanged(event: OnPostChanged) { - autoSavePair?.let { - if (event.causeOfChange is RemoteAutoSavePost) { - val postLocalId = (event.causeOfChange as RemoteAutoSavePost).localPostId - val post: PostModel = postStore.getPostByLocalPostId(postLocalId) - it.second.resume(post) - autoSavePair = null + if (event.causeOfChange is RemoteAutoSavePost) { + val localPostId = LocalId((event.causeOfChange as RemoteAutoSavePost).localPostId) + autoSaveContinuations[localPostId]?.let { continuation -> + val post: PostModel = postStore.getPostByLocalPostId(localPostId.value) + continuation.resume(post) + autoSaveContinuations.remove(localPostId) } } } diff --git a/build.gradle b/build.gradle index 4172ba244006..2593bd8965d9 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ buildScan { ext { daggerVersion = '2.22.1' - fluxCVersion = 'bc810ea94e47a7f56184848fbb7e0f46a4de1d53' + fluxCVersion = '1d2a933ea35ba0ca0286c805a12785b59d219d7f' } // Onboarding and dev env setup tasks From 4cf02b79d19bce3dd044c169d171cfad004c529a Mon Sep 17 00:00:00 2001 From: Oguz Kocer Date: Thu, 6 Feb 2020 17:51:54 -0500 Subject: [PATCH 027/160] Update FluxC hash which contains some fixes to fetching post status --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2593bd8965d9..2d3032213fa6 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ buildScan { ext { daggerVersion = '2.22.1' - fluxCVersion = '1d2a933ea35ba0ca0286c805a12785b59d219d7f' + fluxCVersion = '751696ad02d3446bbfe902c21b16747745616990' } // Onboarding and dev env setup tasks From a3c55d4f5fcfd676704a0821b1f78fcc03866c80 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Sun, 9 Feb 2020 15:34:28 -0500 Subject: [PATCH 028/160] Introduce new classes to manage the searching functionality --- .../ui/pages/PageParentSearchAdapter.kt | 57 ++++++++++ .../ui/pages/PageParentSearchFragment.kt | 104 ++++++++++++++++++ .../ui/pages/PageParentSearchViewModel.kt | 65 +++++++++++ 3 files changed, 226 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchAdapter.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchFragment.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchViewModel.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchAdapter.kt new file mode 100644 index 000000000000..24d53c297dfc --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchAdapter.kt @@ -0,0 +1,57 @@ +package org.wordpress.android.ui.pages + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView.Adapter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.wordpress.android.R.layout +import org.wordpress.android.ui.pages.PageItem.ParentPage +import org.wordpress.android.ui.pages.PageItemViewHolder.EmptyViewHolder +import org.wordpress.android.ui.pages.PageItemViewHolder.PageDividerViewHolder +import org.wordpress.android.ui.pages.PageItemViewHolder.PageParentViewHolder + +class PageParentSearchAdapter( + private val onParentSelected: (ParentPage) -> Unit, + private val uiScope: CoroutineScope +) : Adapter() { + private val items = mutableListOf() + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageItemViewHolder { + return when (viewType) { + PageItem.Type.PARENT.viewType -> PageParentViewHolder(parent, + this::selectParent, + layout.page_parent_list_item) + PageItem.Type.TOP_LEVEL_PARENT.viewType -> PageParentViewHolder(parent, + this::selectParent, + layout.page_parent_top_level_item) + PageItem.Type.DIVIDER.viewType -> PageDividerViewHolder(parent) + PageItem.Type.EMPTY.viewType -> EmptyViewHolder(parent) { } + else -> throw Throwable("Unexpected view type") + } + } + + private fun selectParent(parent: ParentPage) { + onParentSelected(parent) + uiScope.launch { + delay(200) // let the selection animation play out before refreshing the list + notifyDataSetChanged() + } + } + override fun getItemCount(): Int = items.size + + override fun getItemViewType(position: Int): Int { + return items[position].type.viewType + } + + override fun onBindViewHolder(holder: PageItemViewHolder, position: Int) { + holder.onBind(items[position]) + } + + fun update(result: List) { + val diffResult = DiffUtil.calculateDiff(PageItemDiffUtil(items.toList(), result)) + items.clear() + items.addAll(result) + diffResult.dispatchUpdatesTo(this) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchFragment.kt new file mode 100644 index 000000000000..8493d3896f34 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchFragment.kt @@ -0,0 +1,104 @@ +package org.wordpress.android.ui.pages + +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.pages_list_fragment.* +import kotlinx.coroutines.CoroutineScope +import org.wordpress.android.R +import org.wordpress.android.WordPress +import org.wordpress.android.modules.UI_SCOPE +import org.wordpress.android.util.DisplayUtils +import org.wordpress.android.viewmodel.pages.PageParentSearchViewModel +import org.wordpress.android.viewmodel.pages.PageParentViewModel +import org.wordpress.android.widgets.RecyclerItemDecoration +import javax.inject.Inject +import javax.inject.Named + +class PageParentSearchFragment : Fragment() { + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + private lateinit var viewModel: PageParentSearchViewModel + @field:[Inject Named(UI_SCOPE)] lateinit var uiScope: CoroutineScope + private var linearLayoutManager: LinearLayoutManager? = null + + private val listStateKey = "list_state" + + companion object { + fun newInstance(): PageParentSearchFragment { + return PageParentSearchFragment() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.pages_list_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val nonNullActivity = checkNotNull(activity) + (nonNullActivity.application as? WordPress)?.component()?.inject(this) + + initializeViews(savedInstanceState) + initializeViewModels(nonNullActivity) + } + + override fun onSaveInstanceState(outState: Bundle) { + linearLayoutManager?.let { + outState.putParcelable(listStateKey, it.onSaveInstanceState()) + } + super.onSaveInstanceState(outState) + } + + private fun initializeViewModels(activity: FragmentActivity) { + val pageParentViewModel = ViewModelProviders.of(activity, viewModelFactory) + .get(PageParentViewModel::class.java) + + viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(PageParentSearchViewModel::class.java) + viewModel.start(pageParentViewModel) + + setupObservers() + } + + private fun initializeViews(savedInstanceState: Bundle?) { + val layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false) + savedInstanceState?.getParcelable(listStateKey)?.let { + layoutManager.onRestoreInstanceState(it) + } + + linearLayoutManager = layoutManager + recyclerView.layoutManager = linearLayoutManager + recyclerView.addItemDecoration(RecyclerItemDecoration(0, DisplayUtils.dpToPx(activity, 1))) + } + + private fun setupObservers() { + viewModel.searchResult.observe(this, Observer { data -> + data?.let { setSearchResult(data) } + }) + } + + private fun setSearchResult(pages: List) { + val adapter: PageParentSearchAdapter + if (recyclerView.adapter == null) { + adapter = PageParentSearchAdapter( + { page -> viewModel.onParentSelected(page) }, uiScope ) + recyclerView.adapter = adapter + } else { + adapter = recyclerView.adapter as PageParentSearchAdapter + } + adapter.update(pages) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchViewModel.kt new file mode 100644 index 000000000000..efdbfb447301 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/pages/PageParentSearchViewModel.kt @@ -0,0 +1,65 @@ +package org.wordpress.android.viewmodel.pages + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.wordpress.android.R +import org.wordpress.android.modules.UI_SCOPE +import org.wordpress.android.ui.pages.PageItem +import org.wordpress.android.ui.pages.PageItem.Empty +import org.wordpress.android.ui.pages.PageItem.ParentPage +import javax.inject.Inject +import javax.inject.Named + +class PageParentSearchViewModel +@Inject constructor( + @Named(UI_SCOPE) private val uiScope: CoroutineScope +) : ViewModel() { + private val _searchResult: MutableLiveData> = MutableLiveData() + val searchResult: LiveData> = _searchResult + + private var isStarted: Boolean = false + private lateinit var pageParentViewModel: PageParentViewModel + + fun start(pageParentViewModel: PageParentViewModel) { + this.pageParentViewModel = pageParentViewModel + + if (!isStarted) { + isStarted = true + + /** TODO Implement the method in pageParentView Model + pageParentViewModel.searchPages.observeForever(searchObserver) + */ + } + } + + override fun onCleared() { + /** TODO Implement the method in pageParentView Model + pageParentViewModel.searchPages.removeObserver(searchObserver) + */ + } + + private val searchObserver = Observer> { pageItems -> + if (pageItems != null) { + loadFoundPages(pageItems) + } else { + _searchResult.value = listOf(Empty(R.string.pages_search_suggestion, true)) + } + } + + + fun onParentSelected(page: ParentPage) { + pageParentViewModel.onParentSelected(page) + } + + private fun loadFoundPages(pageItems: List) = uiScope.launch { + if (pageItems.isNotEmpty()) { + _searchResult.value = pageItems + } else { + _searchResult.value = listOf(Empty(R.string.pages_empty_search_result, true)) + } + } +} From 57cfc44c41af630375bb676f27ab546dec4187c4 Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Sun, 9 Feb 2020 15:36:06 -0500 Subject: [PATCH 029/160] Update AppComponent to inject PageParentSearchFragment --- .../main/java/org/wordpress/android/modules/AppComponent.java | 3 +++ 1 file changed, 3 insertions(+) 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 6c3498117247..8899bbbf8ac3 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java @@ -65,6 +65,7 @@ import org.wordpress.android.ui.notifications.receivers.NotificationsPendingDraftsReceiver; import org.wordpress.android.ui.pages.PageListFragment; import org.wordpress.android.ui.pages.PageParentFragment; +import org.wordpress.android.ui.pages.PageParentSearchFragment; import org.wordpress.android.ui.pages.PagesFragment; import org.wordpress.android.ui.pages.SearchListFragment; import org.wordpress.android.ui.people.PeopleInviteFragment; @@ -490,6 +491,8 @@ public interface AppComponent extends AndroidInjector { void inject(AddContentAdapter object); + void inject(PageParentSearchFragment object); + // Allows us to inject the application without having to instantiate any modules, and provides the Application // in the app graph @Component.Builder From 163de5425c14a1010d9fc7df9ad685fa0351489a Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Sun, 9 Feb 2020 15:37:32 -0500 Subject: [PATCH 030/160] Update ViewModelModule to for PageParentSearchViewModel --- .../java/org/wordpress/android/modules/ViewModelModule.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java index 31ffa0bb456d..0827e28d0e90 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java @@ -42,6 +42,7 @@ import org.wordpress.android.viewmodel.history.HistoryViewModel; import org.wordpress.android.viewmodel.main.WPMainActivityViewModel; import org.wordpress.android.viewmodel.pages.PageListViewModel; +import org.wordpress.android.viewmodel.pages.PageParentSearchViewModel; import org.wordpress.android.viewmodel.pages.PageParentViewModel; import org.wordpress.android.viewmodel.pages.PagesViewModel; import org.wordpress.android.viewmodel.pages.SearchListViewModel; @@ -276,6 +277,11 @@ abstract class ViewModelModule { @ViewModelKey(PostSignupInterstitialViewModel.class) abstract ViewModel postSignupInterstitialViewModel(PostSignupInterstitialViewModel viewModel); + @Binds + @IntoMap + @ViewModelKey(PageParentSearchViewModel.class) + abstract ViewModel pageParentSearchViewModel(PageParentSearchViewModel viewModel); + @Binds abstract ViewModelProvider.Factory provideViewModelFactory(ViewModelFactory viewModelFactory); } From 00f594b57c765162b37392858a54e71bebf6b2bc Mon Sep 17 00:00:00 2001 From: Annmarie Ziegler Date: Sun, 9 Feb 2020 15:43:37 -0500 Subject: [PATCH 031/160] Set up the search on the menu --- .idea/codeStyles/Project.xml | 67 ++++--------------- .../src/main/res/menu/page_parent_menu.xml | 7 ++ 2 files changed, 20 insertions(+), 54 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 3bf04816d9bb..f50f4769139a 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,8 @@ + +