From 1ca6e8e71812a39294e839f45392304f36db4ab1 Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 21 Feb 2024 10:12:00 +0900 Subject: [PATCH 1/3] Configure photoview-dialog module --- photoview-dialog/.gitignore | 1 + photoview-dialog/build.gradle.kts | 62 +++++++++++++++++++ photoview-dialog/src/main/AndroidManifest.xml | 18 ++++++ settings.gradle.kts | 1 + 4 files changed, 82 insertions(+) create mode 100644 photoview-dialog/.gitignore create mode 100644 photoview-dialog/build.gradle.kts create mode 100644 photoview-dialog/src/main/AndroidManifest.xml diff --git a/photoview-dialog/.gitignore b/photoview-dialog/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/photoview-dialog/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/photoview-dialog/build.gradle.kts b/photoview-dialog/build.gradle.kts new file mode 100644 index 00000000..b31c78d0 --- /dev/null +++ b/photoview-dialog/build.gradle.kts @@ -0,0 +1,62 @@ +import io.getstream.photoview.Configuration + +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.kotlin.android.get().pluginId) + id(libs.plugins.nexus.plugin.get().pluginId) + id(libs.plugins.baseline.profile.get().pluginId) +} + +apply(from = "${rootDir}/scripts/publish-module.gradle.kts") + +mavenPublishing { + val artifactId = "photoview-dialog" + coordinates( + Configuration.artifactGroup, + artifactId, + rootProject.extra.get("libVersion").toString() + ) + + pom { + name.set(artifactId) + description.set("PhotoView is an ImageView component for Android that enables zoom functionality through diverse touch gestures.") + } +} + +android { + compileSdk = Configuration.compileSdk + namespace = "io.getstream.photoview.dialog" + + defaultConfig { + minSdk = Configuration.minSdk + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +tasks.withType { + kotlinOptions.freeCompilerArgs += listOf( + "-Xexplicit-api=strict" + ) +} + +tasks.withType(JavaCompile::class.java).configureEach { + this.targetCompatibility = JavaVersion.VERSION_17.toString() + this.sourceCompatibility = JavaVersion.VERSION_17.toString() +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +dependencies { + implementation(libs.androidx.appcompat) + api(project(":photoview")) + + baselineProfile(project(":benchmark")) +} \ No newline at end of file diff --git a/photoview-dialog/src/main/AndroidManifest.xml b/photoview-dialog/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0390976b --- /dev/null +++ b/photoview-dialog/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 3f82a74d..57fa11c1 100755 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,4 +15,5 @@ dependencyResolutionManagement { include(":sample") include(":photoview") +include(":photoview-dialog") include(":benchmark") From 32ea297f110f8725062094ed9e3a9673f4a4b2fd Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 21 Feb 2024 14:19:15 +0900 Subject: [PATCH 2/3] Implement photoview-dialog features --- build.gradle.kts | 62 +-- gradle/libs.versions.toml | 3 +- .../photoview/dialog/PhotoViewDialog.java | 334 ++++++++++++++++ .../dialog/common/extensions/ImageView.kt | 29 ++ .../dialog/common/extensions/PhotoView.kt | 24 ++ .../dialog/common/extensions/SparseArray.kt | 29 ++ .../dialog/common/extensions/Transition.kt | 49 +++ .../dialog/common/extensions/View.kt | 124 ++++++ .../dialog/common/extensions/ViewPager.kt | 37 ++ .../common/extensions/ViewPropertyAnimator.kt | 37 ++ .../detector/SimpleOnGestureListener.kt | 32 ++ .../gestures/direction/SwipeDirection.kt | 38 ++ .../direction/SwipeDirectionDetector.kt | 94 +++++ .../gestures/dismiss/SwipeToDismissHandler.kt | 109 ++++++ .../common/pager/MultiTouchViewPager.kt | 89 +++++ .../common/pager/RecyclingPagerAdapter.kt | 147 +++++++ .../dialog/listeners/OnDismissListener.kt | 26 ++ .../dialog/listeners/OnImageChangeListener.kt | 25 ++ .../photoview/dialog/loader/ImageLoader.kt | 32 ++ .../viewer/adapter/ImagesPagerAdapter.kt | 75 ++++ .../dialog/viewer/builder/BuilderData.kt | 41 ++ .../dialog/viewer/dialog/ImageViewerDialog.kt | 115 ++++++ .../dialog/viewer/view/ImageViewerView.kt | 362 ++++++++++++++++++ .../viewer/view/TransitionImageAnimator.kt | 161 ++++++++ .../src/main/res/layout/view_image_viewer.xml | 58 +++ .../src/main/res/values/styles.xml | 13 + 26 files changed, 2113 insertions(+), 32 deletions(-) create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.java create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/ImageView.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/PhotoView.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/SparseArray.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/Transition.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/View.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/ViewPager.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/extensions/ViewPropertyAnimator.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/gestures/detector/SimpleOnGestureListener.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/gestures/direction/SwipeDirection.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/gestures/direction/SwipeDirectionDetector.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/gestures/dismiss/SwipeToDismissHandler.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/pager/MultiTouchViewPager.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/common/pager/RecyclingPagerAdapter.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/listeners/OnDismissListener.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/listeners/OnImageChangeListener.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/loader/ImageLoader.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/adapter/ImagesPagerAdapter.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/builder/BuilderData.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/dialog/ImageViewerDialog.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/view/ImageViewerView.kt create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/view/TransitionImageAnimator.kt create mode 100644 photoview-dialog/src/main/res/layout/view_image_viewer.xml create mode 100644 photoview-dialog/src/main/res/values/styles.xml diff --git a/build.gradle.kts b/build.gradle.kts index 810382f4..5dbf725d 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,39 +1,41 @@ @Suppress("DSL_SCOPE_VIOLATION") plugins { - alias(libs.plugins.android.application) apply false - alias(libs.plugins.android.library) apply false - alias(libs.plugins.kotlin.android) apply false - alias(libs.plugins.baseline.profile) apply false - alias(libs.plugins.nexus.plugin) - alias(libs.plugins.spotless) - alias(libs.plugins.dokka) - alias(libs.plugins.kotlin.binary.compatibility) + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.baseline.profile) apply false + alias(libs.plugins.nexus.plugin) + alias(libs.plugins.spotless) + alias(libs.plugins.dokka) + alias(libs.plugins.kotlin.binary.compatibility) } subprojects { - apply(plugin = rootProject.libs.plugins.spotless.get().pluginId) + apply(plugin = rootProject.libs.plugins.spotless.get().pluginId) + if (!this.name.contains("dialog")) { configure { - kotlin { - target("**/*.kt") - targetExclude("$buildDir/**/*.kt") - ktlint().editorConfigOverride( - mapOf( - "indent_size" to "2", - "continuation_indent_size" to "2" - ) - ) - licenseHeaderFile(rootProject.file("spotless/copyright.kt")) - trimTrailingWhitespace() - endWithNewline() - } - format("xml") { - target("**/*.xml") - targetExclude("**/build/**/*.xml") - // Look for the first XML tag that isn't a comment ( + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/photoview-dialog/src/main/res/values/styles.xml b/photoview-dialog/src/main/res/values/styles.xml new file mode 100644 index 00000000..7c63c62a --- /dev/null +++ b/photoview-dialog/src/main/res/values/styles.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file From 049d44f2570b6271a26ee589c402ec9dcd638a12 Mon Sep 17 00:00:00 2001 From: skydoves Date: Wed, 21 Feb 2024 14:50:39 +0900 Subject: [PATCH 3/3] Implement photoview dialog samples --- photoview-dialog/api/photoview-dialog.api | 79 +++++ .../photoview/dialog/PhotoViewDialog.java | 334 ------------------ .../photoview/dialog/PhotoViewDialog.kt | 328 +++++++++++++++++ .../photoview/dialog/loader/ImageLoader.kt | 2 +- .../dialog/viewer/builder/BuilderData.kt | 28 +- .../dialog/viewer/dialog/ImageViewerDialog.kt | 2 +- .../src/main/res/values/styles.xml | 13 +- sample/api/sample.api | 14 + sample/build.gradle.kts | 1 + sample/src/main/AndroidManifest.xml | 71 ++-- .../photoview/sample/LauncherActivity.kt | 2 + .../sample/PhotoViewDialogActivity.kt | 50 +++ .../res/layout/activity_photoview_dialog.xml | 34 ++ 13 files changed, 571 insertions(+), 387 deletions(-) create mode 100644 photoview-dialog/api/photoview-dialog.api delete mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.java create mode 100644 photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.kt create mode 100644 sample/src/main/kotlin/io/getstream/photoview/sample/PhotoViewDialogActivity.kt create mode 100755 sample/src/main/res/layout/activity_photoview_dialog.xml diff --git a/photoview-dialog/api/photoview-dialog.api b/photoview-dialog/api/photoview-dialog.api new file mode 100644 index 00000000..68d41cb1 --- /dev/null +++ b/photoview-dialog/api/photoview-dialog.api @@ -0,0 +1,79 @@ +public final class io/getstream/photoview/dialog/PhotoViewDialog { + public fun (Landroid/content/Context;Lio/getstream/photoview/dialog/viewer/builder/BuilderData;)V + public final fun close ()V + public final fun currentPosition ()I + public final fun dismiss ()V + public final fun setCurrentPosition (I)I + public final fun show ()V + public final fun show (Z)V + public static synthetic fun show$default (Lio/getstream/photoview/dialog/PhotoViewDialog;ZILjava/lang/Object;)V + public final fun updateImages (Ljava/util/List;)V + public final fun updateImages ([Ljava/lang/Object;)V + public final fun updateTransitionImage (Landroid/widget/ImageView;)V +} + +public final class io/getstream/photoview/dialog/PhotoViewDialog$Builder { + public fun (Landroid/content/Context;Ljava/util/List;Lio/getstream/photoview/dialog/loader/ImageLoader;)V + public fun (Landroid/content/Context;[Ljava/lang/Object;Lio/getstream/photoview/dialog/loader/ImageLoader;)V + public final fun allowSwipeToDismiss (Z)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun allowZooming (Z)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun build ()Lio/getstream/photoview/dialog/PhotoViewDialog; + public final fun show ()Lio/getstream/photoview/dialog/PhotoViewDialog; + public final fun show (Z)Lio/getstream/photoview/dialog/PhotoViewDialog; + public static synthetic fun show$default (Lio/getstream/photoview/dialog/PhotoViewDialog$Builder;ZILjava/lang/Object;)Lio/getstream/photoview/dialog/PhotoViewDialog; + public final fun withBackgroundColor (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withBackgroundColorResource (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withContainerPadding (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withContainerPadding (IIII)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withContainerPaddingPixels (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withContainerPaddingPixels (IIII)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withDismissListener (Lio/getstream/photoview/dialog/listeners/OnDismissListener;)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withHiddenStatusBar (Z)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withImageChangeListener (Lio/getstream/photoview/dialog/listeners/OnImageChangeListener;)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withImageMarginPixels (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withImagesMargin (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withOverlayView (Landroid/view/View;)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withStartPosition (I)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; + public final fun withTransitionFrom (Landroid/widget/ImageView;)Lio/getstream/photoview/dialog/PhotoViewDialog$Builder; +} + +public abstract interface class io/getstream/photoview/dialog/listeners/OnDismissListener { + public abstract fun onDismiss ()V +} + +public abstract interface class io/getstream/photoview/dialog/listeners/OnImageChangeListener { + public abstract fun onImageChange (I)V +} + +public abstract interface class io/getstream/photoview/dialog/loader/ImageLoader { + public abstract fun loadImage (Landroid/widget/ImageView;Ljava/lang/Object;)V +} + +public final class io/getstream/photoview/dialog/viewer/builder/BuilderData { + public fun (Ljava/util/List;Lio/getstream/photoview/dialog/loader/ImageLoader;)V + public final fun getBackgroundColor ()I + public final fun getContainerPaddingPixels ()[I + public final fun getImageChangeListener ()Lio/getstream/photoview/dialog/listeners/OnImageChangeListener; + public final fun getImageLoader ()Lio/getstream/photoview/dialog/loader/ImageLoader; + public final fun getImageMarginPixels ()I + public final fun getImages ()Ljava/util/List; + public final fun getOnDismissListener ()Lio/getstream/photoview/dialog/listeners/OnDismissListener; + public final fun getOverlayView ()Landroid/view/View; + public final fun getShouldStatusBarHide ()Z + public final fun getStartPosition ()I + public final fun getTransitionView ()Landroid/widget/ImageView; + public final fun isSwipeToDismissAllowed ()Z + public final fun isZoomingAllowed ()Z + public final fun setBackgroundColor (I)V + public final fun setContainerPaddingPixels ([I)V + public final fun setImageChangeListener (Lio/getstream/photoview/dialog/listeners/OnImageChangeListener;)V + public final fun setImageMarginPixels (I)V + public final fun setOnDismissListener (Lio/getstream/photoview/dialog/listeners/OnDismissListener;)V + public final fun setOverlayView (Landroid/view/View;)V + public final fun setShouldStatusBarHide (Z)V + public final fun setStartPosition (I)V + public final fun setSwipeToDismissAllowed (Z)V + public final fun setTransitionView (Landroid/widget/ImageView;)V + public final fun setZoomingAllowed (Z)V +} + diff --git a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.java b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.java deleted file mode 100644 index 85d4342f..00000000 --- a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright 2018 stfalcon.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.photoview.dialog; - -import android.content.Context; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DimenRes; -import androidx.annotation.NonNull; -import androidx.annotation.Px; -import androidx.core.content.ContextCompat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import io.getstream.photoview.dialog.listeners.OnDismissListener; -import io.getstream.photoview.dialog.listeners.OnImageChangeListener; -import io.getstream.photoview.dialog.loader.ImageLoader; -import io.getstream.photoview.dialog.viewer.builder.BuilderData; -import io.getstream.photoview.dialog.viewer.dialog.ImageViewerDialog; - -//N.B.! This class is written in Java for convenient use of lambdas due to languages compatibility issues. -@SuppressWarnings({"unused", "WeakerAccess"}) -public class PhotoViewDialog { - - private final BuilderData builderData; - private final ImageViewerDialog dialog; - - protected PhotoViewDialog(@NonNull Context context, @NonNull BuilderData builderData) { - this.builderData = builderData; - this.dialog = new ImageViewerDialog<>(context, builderData); - } - - /** - * Displays the built viewer if passed list of images is not empty - */ - public void show() { - show(true); - } - - /** - * Displays the built viewer if passed list of images is not empty - * - * @param animate whether the passed transition view should be animated on open. Useful for screen rotation handling. - */ - public void show(boolean animate) { - if (!builderData.getImages().isEmpty()) { - dialog.show(animate); - } else { - Log.w("PhotoView", "Images list cannot be empty! Viewer ignored."); - } - } - - /** - * Closes the viewer with suitable close animation - */ - public void close() { - dialog.close(); - } - - /** - * Dismisses the dialog with no animation - */ - public void dismiss() { - dialog.dismiss(); - } - - /** - * Updates an existing images list if a new list is not empty, otherwise closes the viewer - */ - public void updateImages(T[] images) { - updateImages(new ArrayList<>(Arrays.asList(images))); - } - - /** - * Updates an existing images list if a new list is not empty, otherwise closes the viewer - */ - public void updateImages(List images) { - if (!images.isEmpty()) { - dialog.updateImages(images); - } else { - dialog.close(); - } - } - - public int currentPosition() { - return dialog.getCurrentPosition(); - } - - public int setCurrentPosition(int position) { - return dialog.setCurrentPosition(position); - } - - /** - * Updates transition image view. - * Useful for a case when image position has changed and you want to update the transition animation target. - */ - public void updateTransitionImage(ImageView imageView) { - dialog.updateTransitionImage(imageView); - } - - /** - * Builder class for {@link PhotoViewDialog} - */ - public static class Builder { - - private final Context context; - private final BuilderData data; - - public Builder(Context context, T[] images, ImageLoader imageLoader) { - this(context, new ArrayList<>(Arrays.asList(images)), imageLoader); - } - - public Builder(Context context, List images, ImageLoader imageLoader) { - this.context = context; - this.data = new BuilderData<>(images, imageLoader); - } - - /** - * Sets a position to start viewer from. - * - * @return This Builder object to allow calls chaining - */ - public Builder withStartPosition(int position) { - this.data.setStartPosition(position); - return this; - } - - /** - * Sets a background color value for the viewer - * - * @return This Builder object to allow calls chaining - */ - public Builder withBackgroundColor(@ColorInt int color) { - this.data.setBackgroundColor(color); - return this; - } - - /** - * Sets a background color resource for the viewer - * - * @return This Builder object to allow calls chaining - */ - public Builder withBackgroundColorResource(@ColorRes int color) { - return this.withBackgroundColor(ContextCompat.getColor(context, color)); - } - - /** - * Sets custom overlay view to be shown over the viewer. - * Commonly used for image description or counter displaying. - * - * @return This Builder object to allow calls chaining - */ - public Builder withOverlayView(View view) { - this.data.setOverlayView(view); - return this; - } - - /** - * Sets space between the images using dimension. - * - * @return This Builder object to allow calls chaining - */ - public Builder withImagesMargin(@DimenRes int dimen) { - this.data.setImageMarginPixels(Math.round(context.getResources().getDimension(dimen))); - return this; - } - - /** - * Sets space between the images in pixels. - * - * @return This Builder object to allow calls chaining - */ - public Builder withImageMarginPixels(int marginPixels) { - this.data.setImageMarginPixels(marginPixels); - return this; - } - - /** - * Sets overall padding for zooming and scrolling area using dimension. - * - * @return This Builder object to allow calls chaining - */ - public Builder withContainerPadding(@DimenRes int padding) { - int paddingPx = Math.round(context.getResources().getDimension(padding)); - return withContainerPaddingPixels(paddingPx, paddingPx, paddingPx, paddingPx); - } - - /** - * Sets `start`, `top`, `end` and `bottom` padding for zooming and scrolling area using dimension. - * - * @return This Builder object to allow calls chaining - */ - public Builder withContainerPadding(@DimenRes int start, @DimenRes int top, - @DimenRes int end, @DimenRes int bottom - ) { - withContainerPaddingPixels( - Math.round(context.getResources().getDimension(start)), - Math.round(context.getResources().getDimension(top)), - Math.round(context.getResources().getDimension(end)), - Math.round(context.getResources().getDimension(bottom))); - return this; - } - - /** - * Sets overall padding for zooming and scrolling area in pixels. - * - * @return This Builder object to allow calls chaining - */ - public Builder withContainerPaddingPixels(@Px int padding) { - this.data.setContainerPaddingPixels(new int[]{padding, padding, padding, padding}); - return this; - } - - /** - * Sets `start`, `top`, `end` and `bottom` padding for zooming and scrolling area in pixels. - * - * @return This Builder object to allow calls chaining - */ - public Builder withContainerPaddingPixels(int start, int top, int end, int bottom) { - this.data.setContainerPaddingPixels(new int[]{start, top, end, bottom}); - return this; - } - - /** - * Sets status bar visibility. True by default. - * - * @return This Builder object to allow calls chaining - */ - public Builder withHiddenStatusBar(boolean value) { - this.data.setShouldStatusBarHide(value); - return this; - } - - /** - * Enables or disables zooming. True by default. - * - * @return This Builder object to allow calls chaining - */ - public Builder allowZooming(boolean value) { - this.data.setZoomingAllowed(value); - return this; - } - - /** - * Enables or disables the "Swipe to Dismiss" gesture. True by default. - * - * @return This Builder object to allow calls chaining - */ - public Builder allowSwipeToDismiss(boolean value) { - this.data.setSwipeToDismissAllowed(value); - return this; - } - - /** - * Sets a target {@link ImageView} to be part of transition when opening or closing the viewer/ - * - * @return This Builder object to allow calls chaining - */ - public Builder withTransitionFrom(ImageView imageView) { - this.data.setTransitionView(imageView); - return this; - } - - /** - * Sets {@link OnImageChangeListener} for the viewer. - * - * @return This Builder object to allow calls chaining - */ - public Builder withImageChangeListener(OnImageChangeListener imageChangeListener) { - this.data.setImageChangeListener(imageChangeListener); - return this; - } - - /** - * Sets {@link OnDismissListener} for viewer. - * - * @return This Builder object to allow calls chaining - */ - public Builder withDismissListener(OnDismissListener onDismissListener) { - this.data.setOnDismissListener(onDismissListener); - return this; - } - - /** - * Creates a {@link PhotoViewDialog} with the arguments supplied to this builder. It does not - * show the dialog. This allows the user to do any extra processing - * before displaying the dialog. Use {@link #show()} if you don't have any other processing - * to do and want this to be created and displayed. - */ - public PhotoViewDialog build() { - return new PhotoViewDialog<>(context, data); - } - - /** - * Creates the {@link PhotoViewDialog} with the arguments supplied to this builder and - * shows the dialog. - */ - public PhotoViewDialog show() { - return show(true); - } - - /** - * Creates the {@link PhotoViewDialog} with the arguments supplied to this builder and - * shows the dialog. - * - * @param animate whether the passed transition view should be animated on open. Useful for screen rotation handling. - */ - public PhotoViewDialog show(boolean animate) { - PhotoViewDialog viewer = build(); - viewer.show(animate); - return viewer; - } - } -} diff --git a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.kt b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.kt new file mode 100644 index 00000000..ebf07e4b --- /dev/null +++ b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/PhotoViewDialog.kt @@ -0,0 +1,328 @@ +/* + * Copyright 2018 stfalcon.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getstream.photoview.dialog + +import android.content.Context +import android.util.Log +import android.view.View +import android.widget.ImageView +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.Px +import androidx.core.content.ContextCompat +import io.getstream.photoview.dialog.listeners.OnDismissListener +import io.getstream.photoview.dialog.listeners.OnImageChangeListener +import io.getstream.photoview.dialog.loader.ImageLoader +import io.getstream.photoview.dialog.viewer.builder.BuilderData +import io.getstream.photoview.dialog.viewer.dialog.ImageViewerDialog +import kotlin.math.roundToInt + +//N.B.! This class is written in Java for convenient use of lambdas due to languages compatibility issues. +@Suppress("unused") +public class PhotoViewDialog( + context: Context, + private val builderData: BuilderData +) { + private val dialog: ImageViewerDialog + + init { + dialog = ImageViewerDialog(context, builderData) + } + /** + * Displays the built viewer if passed list of images is not empty + * + * @param animate whether the passed transition view should be animated on open. Useful for screen rotation handling. + */ + /** + * Displays the built viewer if passed list of images is not empty + */ + @JvmOverloads + public fun show(animate: Boolean = true) { + if (builderData.images.isNotEmpty()) { + dialog.show(animate) + } else { + Log.w("PhotoView", "Images list cannot be empty! Viewer ignored.") + } + } + + /** + * Closes the viewer with suitable close animation + */ + public fun close() { + dialog.close() + } + + /** + * Dismisses the dialog with no animation + */ + public fun dismiss() { + dialog.dismiss() + } + + /** + * Updates an existing images list if a new list is not empty, otherwise closes the viewer + */ + public fun updateImages(images: Array) { + updateImages(ArrayList(listOf(*images))) + } + + /** + * Updates an existing images list if a new list is not empty, otherwise closes the viewer + */ + public fun updateImages(images: List) { + if (images.isNotEmpty()) { + dialog.updateImages(images) + } else { + dialog.close() + } + } + + public fun currentPosition(): Int { + return dialog.getCurrentPosition() + } + + public fun setCurrentPosition(position: Int): Int { + return dialog.setCurrentPosition(position) + } + + /** + * Updates transition image view. + * Useful for a case when image position has changed and you want to update the transition animation target. + */ + public fun updateTransitionImage(imageView: ImageView?) { + dialog.updateTransitionImage(imageView) + } + + /** + * Builder class for [PhotoViewDialog] + */ + public class Builder( + private val context: Context, + images: List, + imageLoader: ImageLoader + ) { + private val data: BuilderData + + public constructor( + context: Context, + images: Array, + imageLoader: ImageLoader + ) : this( + context, ArrayList( + listOf(*images) + ), imageLoader + ) + + init { + data = BuilderData(images, imageLoader) + } + + /** + * Sets a position to start viewer from. + * + * @return This Builder object to allow calls chaining + */ + public fun withStartPosition(position: Int): Builder { + data.startPosition = position + return this + } + + /** + * Sets a background color value for the viewer + * + * @return This Builder object to allow calls chaining + */ + public fun withBackgroundColor(@ColorInt color: Int): Builder { + data.backgroundColor = color + return this + } + + /** + * Sets a background color resource for the viewer + * + * @return This Builder object to allow calls chaining + */ + public fun withBackgroundColorResource(@ColorRes color: Int): Builder { + return withBackgroundColor(ContextCompat.getColor(context, color)) + } + + /** + * Sets custom overlay view to be shown over the viewer. + * Commonly used for image description or counter displaying. + * + * @return This Builder object to allow calls chaining + */ + public fun withOverlayView(view: View): Builder { + data.overlayView = view + return this + } + + /** + * Sets space between the images using dimension. + * + * @return This Builder object to allow calls chaining + */ + public fun withImagesMargin(@DimenRes dimen: Int): Builder { + data.imageMarginPixels = context.resources.getDimension(dimen).roundToInt() + return this + } + + /** + * Sets space between the images in pixels. + * + * @return This Builder object to allow calls chaining + */ + public fun withImageMarginPixels(marginPixels: Int): Builder { + data.imageMarginPixels = marginPixels + return this + } + + /** + * Sets overall padding for zooming and scrolling area using dimension. + * + * @return This Builder object to allow calls chaining + */ + public fun withContainerPadding(@DimenRes padding: Int): Builder { + val paddingPx = context.resources.getDimension(padding).roundToInt() + return withContainerPaddingPixels(paddingPx, paddingPx, paddingPx, paddingPx) + } + + /** + * Sets `start`, `top`, `end` and `bottom` padding for zooming and scrolling area using dimension. + * + * @return This Builder object to allow calls chaining + */ + public fun withContainerPadding( + @DimenRes start: Int, @DimenRes top: Int, + @DimenRes end: Int, @DimenRes bottom: Int + ): Builder { + withContainerPaddingPixels( + context.resources.getDimension(start).roundToInt(), + context.resources.getDimension(top).roundToInt(), + context.resources.getDimension(end).roundToInt(), + context.resources.getDimension(bottom).roundToInt() + ) + return this + } + + /** + * Sets overall padding for zooming and scrolling area in pixels. + * + * @return This Builder object to allow calls chaining + */ + public fun withContainerPaddingPixels(@Px padding: Int): Builder { + data.containerPaddingPixels = intArrayOf(padding, padding, padding, padding) + return this + } + + /** + * Sets `start`, `top`, `end` and `bottom` padding for zooming and scrolling area in pixels. + * + * @return This Builder object to allow calls chaining + */ + public fun withContainerPaddingPixels(start: Int, top: Int, end: Int, bottom: Int): Builder { + data.containerPaddingPixels = intArrayOf(start, top, end, bottom) + return this + } + + /** + * Sets status bar visibility. True by default. + * + * @return This Builder object to allow calls chaining + */ + public fun withHiddenStatusBar(value: Boolean): Builder { + data.shouldStatusBarHide = value + return this + } + + /** + * Enables or disables zooming. True by default. + * + * @return This Builder object to allow calls chaining + */ + public fun allowZooming(value: Boolean): Builder { + data.isZoomingAllowed = value + return this + } + + /** + * Enables or disables the "Swipe to Dismiss" gesture. True by default. + * + * @return This Builder object to allow calls chaining + */ + public fun allowSwipeToDismiss(value: Boolean): Builder { + data.isSwipeToDismissAllowed = value + return this + } + + /** + * Sets a target [ImageView] to be part of transition when opening or closing the viewer/ + * + * @return This Builder object to allow calls chaining + */ + public fun withTransitionFrom(imageView: ImageView?): Builder { + data.transitionView = imageView + return this + } + + /** + * Sets [OnImageChangeListener] for the viewer. + * + * @return This Builder object to allow calls chaining + */ + public fun withImageChangeListener(imageChangeListener: OnImageChangeListener?): Builder { + data.imageChangeListener = imageChangeListener + return this + } + + /** + * Sets [OnDismissListener] for viewer. + * + * @return This Builder object to allow calls chaining + */ + public fun withDismissListener(onDismissListener: OnDismissListener?): Builder { + data.onDismissListener = onDismissListener + return this + } + + /** + * Creates a [PhotoViewDialog] with the arguments supplied to this builder. It does not + * show the dialog. This allows the user to do any extra processing + * before displaying the dialog. Use [.show] if you don't have any other processing + * to do and want this to be created and displayed. + */ + public fun build(): PhotoViewDialog { + return PhotoViewDialog(context, data) + } + /** + * Creates the [PhotoViewDialog] with the arguments supplied to this builder and + * shows the dialog. + * + * @param animate whether the passed transition view should be animated on open. Useful for screen rotation handling. + */ + /** + * Creates the [PhotoViewDialog] with the arguments supplied to this builder and + * shows the dialog. + */ + @JvmOverloads + public fun show(animate: Boolean = true): PhotoViewDialog { + val viewer = build() + viewer.show(animate) + return viewer + } + } +} diff --git a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/loader/ImageLoader.kt b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/loader/ImageLoader.kt index 313194a5..926573a6 100644 --- a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/loader/ImageLoader.kt +++ b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/loader/ImageLoader.kt @@ -28,5 +28,5 @@ public fun interface ImageLoader { * @param imageView an [ImageView] object where the image should be loaded * @param image image data from which image should be loaded */ - public fun loadImage(imageView: ImageView?, image: T) + public fun loadImage(imageView: ImageView, image: T) } diff --git a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/builder/BuilderData.kt b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/builder/BuilderData.kt index 9227bb65..2e3dae60 100644 --- a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/builder/BuilderData.kt +++ b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/builder/BuilderData.kt @@ -23,19 +23,19 @@ import io.getstream.photoview.dialog.listeners.OnDismissListener import io.getstream.photoview.dialog.listeners.OnImageChangeListener import io.getstream.photoview.dialog.loader.ImageLoader -internal class BuilderData( - val images: List, - val imageLoader: ImageLoader +public class BuilderData( + public val images: List, + public val imageLoader: ImageLoader ) { - var backgroundColor = Color.BLACK - var startPosition: Int = 0 - var imageChangeListener: OnImageChangeListener? = null - var onDismissListener: OnDismissListener? = null - var overlayView: View? = null - var imageMarginPixels: Int = 0 - var containerPaddingPixels = IntArray(4) - var shouldStatusBarHide = true - var isZoomingAllowed = true - var isSwipeToDismissAllowed = true - var transitionView: ImageView? = null + public var backgroundColor: Int = Color.BLACK + public var startPosition: Int = 0 + public var imageChangeListener: OnImageChangeListener? = null + public var onDismissListener: OnDismissListener? = null + public var overlayView: View? = null + public var imageMarginPixels: Int = 0 + public var containerPaddingPixels: IntArray = IntArray(4) + public var shouldStatusBarHide: Boolean = true + public var isZoomingAllowed: Boolean = true + public var isSwipeToDismissAllowed: Boolean = true + public var transitionView: ImageView? = null } \ No newline at end of file diff --git a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/dialog/ImageViewerDialog.kt b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/dialog/ImageViewerDialog.kt index 4086bd39..342ef615 100644 --- a/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/dialog/ImageViewerDialog.kt +++ b/photoview-dialog/src/main/kotlin/io/getstream/photoview/dialog/viewer/dialog/ImageViewerDialog.kt @@ -35,7 +35,7 @@ internal class ImageViewerDialog( private val dialogStyle: Int get() = if (builderData.shouldStatusBarHide) - R.style.ImageViewerqwDialog_NoStatusBar + R.style.ImageViewerDialog_NoStatusBar else R.style.ImageViewerDialog_Default diff --git a/photoview-dialog/src/main/res/values/styles.xml b/photoview-dialog/src/main/res/values/styles.xml index 7c63c62a..5c467c7d 100644 --- a/photoview-dialog/src/main/res/values/styles.xml +++ b/photoview-dialog/src/main/res/values/styles.xml @@ -1,13 +1,20 @@ - - - \ No newline at end of file diff --git a/sample/api/sample.api b/sample/api/sample.api index 759c39ba..cafb2bac 100644 --- a/sample/api/sample.api +++ b/sample/api/sample.api @@ -70,6 +70,10 @@ public final class io/getstream/photoview/sample/LauncherActivity$Companion { public final fun getOptions ()[Ljava/lang/String; } +public final class io/getstream/photoview/sample/PhotoViewDialogActivity : androidx/appcompat/app/AppCompatActivity { + public fun ()V +} + public final class io/getstream/photoview/sample/RotationSampleActivity : androidx/appcompat/app/AppCompatActivity { public fun ()V public fun onCreate (Landroid/os/Bundle;)V @@ -112,6 +116,16 @@ public final class io/getstream/photoview/sample/databinding/ActivityLauncherBin public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lio/getstream/photoview/sample/databinding/ActivityLauncherBinding; } +public final class io/getstream/photoview/sample/databinding/ActivityPhotoviewDialogBinding : androidx/viewbinding/ViewBinding { + public final field button Landroid/widget/Button; + public final field ivPhoto Landroidx/constraintlayout/widget/ConstraintLayout; + public static fun bind (Landroid/view/View;)Lio/getstream/photoview/sample/databinding/ActivityPhotoviewDialogBinding; + public synthetic fun getRoot ()Landroid/view/View; + public fun getRoot ()Landroidx/constraintlayout/widget/ConstraintLayout; + public static fun inflate (Landroid/view/LayoutInflater;)Lio/getstream/photoview/sample/databinding/ActivityPhotoviewDialogBinding; + public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lio/getstream/photoview/sample/databinding/ActivityPhotoviewDialogBinding; +} + public final class io/getstream/photoview/sample/databinding/ActivityRotationSampleBinding : androidx/viewbinding/ViewBinding { public final field appbar Lcom/google/android/material/appbar/AppBarLayout; public final field ivPhoto Lio/getstream/photoview/PhotoView; diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index f50f6399..8f2393a8 100755 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { implementation(libs.glide) implementation(project(":photoview")) + implementation(project(":photoview-dialog")) baselineProfile(project(":benchmark")) } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 8dc6716d..c7f671b8 100755 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -16,39 +16,42 @@ limitations under the License. --> - - - - - - - - - - - - - - - - - - - - - - + xmlns:tools="http://schemas.android.com/tools" + package="io.getstream.photoview.sample"> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/kotlin/io/getstream/photoview/sample/LauncherActivity.kt b/sample/src/main/kotlin/io/getstream/photoview/sample/LauncherActivity.kt index ca527107..cce5cdf1 100755 --- a/sample/src/main/kotlin/io/getstream/photoview/sample/LauncherActivity.kt +++ b/sample/src/main/kotlin/io/getstream/photoview/sample/LauncherActivity.kt @@ -69,6 +69,7 @@ class LauncherActivity : AppCompatActivity() { 4 -> GlideSampleActivity::class.java 5 -> ActivityTransitionActivity::class.java 6 -> ImmersiveActivity::class.java + 7 -> PhotoViewDialogActivity::class.java else -> SimpleSampleActivity::class.java } val context = holder.itemView.context @@ -115,6 +116,7 @@ class LauncherActivity : AppCompatActivity() { "Glide Sample", "Activity Transition Sample", "Immersive Sample", + "PhotoView Dialog", ) } } diff --git a/sample/src/main/kotlin/io/getstream/photoview/sample/PhotoViewDialogActivity.kt b/sample/src/main/kotlin/io/getstream/photoview/sample/PhotoViewDialogActivity.kt new file mode 100644 index 00000000..7d225d41 --- /dev/null +++ b/sample/src/main/kotlin/io/getstream/photoview/sample/PhotoViewDialogActivity.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Stream.IO, Inc. + * Copyright 2011, 2012 Chris Banes. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.getstream.photoview.sample + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.bumptech.glide.Glide +import io.getstream.photoview.dialog.PhotoViewDialog +import io.getstream.photoview.sample.databinding.ActivityPhotoviewDialogBinding + +class PhotoViewDialogActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityPhotoviewDialogBinding.inflate(layoutInflater) + setContentView(binding.root) + + val urls = listOf( + "https://images.unsplash.com/photo-1577643816920-65b43ba99fba?ixlib=rb-1.2.1", + "https://images.unsplash.com/photo-1577643816920-65b43ba99fba?ixlib=rb-1.2.1", + "https://images.unsplash.com/photo-1577643816920-65b43ba99fba?ixlib=rb-1.2.1", + "https://images.unsplash.com/photo-1577643816920-65b43ba99fba?ixlib=rb-1.2.1", + "https://images.unsplash.com/photo-1577643816920-65b43ba99fba?ixlib=rb-1.2.1", + "https://images.unsplash.com/photo-1577643816920-65b43ba99fba?ixlib=rb-1.2.1", + ) + + val button = binding.button + button.setOnClickListener { + PhotoViewDialog.Builder(context = this, images = urls) { imageView, url -> + Glide.with(this) + .load(url) + .into(imageView) + }.build().show() + } + } +} diff --git a/sample/src/main/res/layout/activity_photoview_dialog.xml b/sample/src/main/res/layout/activity_photoview_dialog.xml new file mode 100755 index 00000000..e9adeac3 --- /dev/null +++ b/sample/src/main/res/layout/activity_photoview_dialog.xml @@ -0,0 +1,34 @@ + + + + +