diff --git a/app/build.gradle b/app/build.gradle index c1e0787a..913d8f47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,9 +61,6 @@ dependencies { //noinspection GradleDependency implementation 'com.squareup.picasso:picasso:2.8' implementation 'com.github.QuadFlask:colorpicker:0.0.15' - implementation 'com.github.nuclearfog:ZoomView:1.0.4' - implementation 'com.github.nuclearfog:Tagger:2.4' - implementation 'com.github.nuclearfog:LinkAndScrollMovement:1.4.1' implementation 'com.github.kyleduo:SwitchButton:2.0.3-SNAPSHOT' implementation 'com.github.UnifiedPush:android-connector:2.1.1' implementation 'com.google.android.material:material:1.9.0' diff --git a/app/src/main/assets/licenses.html b/app/src/main/assets/licenses.html index d35ed425..bcf59354 100644 --- a/app/src/main/assets/licenses.html +++ b/app/src/main/assets/licenses.html @@ -10,7 +10,7 @@
Copyright 2018 nuclearfog diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/utils/LinkAndScrollMovement.java b/app/src/main/java/org/nuclearfog/twidda/backend/utils/LinkAndScrollMovement.java new file mode 100644 index 00000000..31157b14 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/backend/utils/LinkAndScrollMovement.java @@ -0,0 +1,94 @@ +package org.nuclearfog.twidda.backend.utils; + +import android.text.Layout; +import android.text.Spannable; +import android.text.method.ScrollingMovementMethod; +import android.text.style.ClickableSpan; +import android.view.MotionEvent; +import android.view.ViewParent; +import android.widget.TextView; + +/** + * @author nuclearfog + */ +public class LinkAndScrollMovement extends ScrollingMovementMethod { + + private static final LinkAndScrollMovement instance = new LinkAndScrollMovement(); + + /** + * setup the x axis threshold to disable click events. + */ + private static final int THRESHOLD_WIDTH_DIVIDER = 6; + + /** + * setup the y axis threshold to disable click events. + */ + private static final int THRESHOLD_HEIGHT_DIVIDER = 3; + + private int xScroll = 0; + private int yScroll = 0; + + /** + */ + private LinkAndScrollMovement() { + super(); + } + + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + switch(event.getAction()) { + case MotionEvent.ACTION_DOWN: + lockParentScrolling(widget, true); + xScroll = widget.getScrollX(); + yScroll = widget.getScrollY(); + break; + + case MotionEvent.ACTION_UP: + lockParentScrolling(widget, false); + int deltaX = Math.abs(widget.getScrollX() - xScroll); + int deltaY = Math.abs(widget.getScrollY() - yScroll); + if (deltaY <= widget.getTextSize() / THRESHOLD_HEIGHT_DIVIDER && deltaX <= widget.getWidth() / THRESHOLD_WIDTH_DIVIDER) { + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + x += widget.getScrollX(); + y += widget.getScrollY(); + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + if (link.length > 0) { + link[0].onClick(widget); + return true; + } + } + break; + } + return super.onTouchEvent(widget, buffer, event); + } + + /** + * lock parent view scrolling + * + * @param widget interacting TextView + * @param lock true if parent views scrolling should be locked + */ + private void lockParentScrolling(TextView widget, boolean lock) { + ViewParent parent = widget.getParent(); + int lineCount = widget.getLineCount(); + int maxLines = widget.getMaxLines(); + if ( parent != null && maxLines > 0 && lineCount > maxLines ) { + parent.requestDisallowInterceptTouchEvent(lock); + } + } + + /** + * Get singleton instance of the movement method + * + * @return LinkAndScrollingMovementMethod object + */ + public static LinkAndScrollMovement getInstance() { + return instance; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/utils/Tagger.java b/app/src/main/java/org/nuclearfog/twidda/backend/utils/Tagger.java new file mode 100644 index 00000000..5a2df8d5 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/backend/utils/Tagger.java @@ -0,0 +1,212 @@ +package org.nuclearfog.twidda.backend.utils; + +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.util.Patterns; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author nuclearfog + */ +public class Tagger { + + /** + * regex patterns used to get @usernames and #hashtags + */ + private static final Pattern[] PATTERNS = { + Pattern.compile("@[^#\"“”‘’«»„"⹂‟`*'~,;‚‛:<>|^!/§%&()=?´°{}+\\-\\[\\]\\s]+"), + Pattern.compile("#[^@#\"“”‘’«»„"⹂‟`*'~,;‚.‛:<>|^!/§%&()=?´°{}+\\-\\[\\]\\s]+") + }; + + /** + * default span type + */ + private static final int SPAN_TYPE = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE; + + /** + * maximum link url length before truncating + */ + private static final int MAX_LINK_LENGTH = 30; + + /** + * Make a spannable colored String with click listener + * + * @param text String that should be spannable + * @param color Text Color + * @param l click listener + * @return Spannable String + */ + public static Spannable makeText(@Nullable String text, final int color, @NonNull final OnTagClickListener l) { + SpannableStringBuilder spannable = new SpannableStringBuilder(); + /// Add '@' & '#' highlighting + listener + if (text != null && text.length() > 0) { + spannable.append(text); + for (Pattern pattern : PATTERNS) { + Matcher m = pattern.matcher(spannable); + while (m.find()) { + int end = m.end(); + int start = m.start(); + final String tag = spannable.subSequence(start, end).toString(); + spannable.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + l.onTagClick(tag); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setColor(color); + ds.setUnderlineText(false); + } + }, start, end, SPAN_TYPE); + } + } + } + return spannable; + } + + /** + * Make a spannable colored String with click listener + * http(s) links included + * + * @param text String that should be spannable + * @param color Text Color + * @param l click listener + * @return Spannable String + */ + public static Spannable makeTextWithLinks(@Nullable String text, final int color, @NonNull final OnTagClickListener l) { + SpannableStringBuilder spannable = new SpannableStringBuilder(makeText(text, color, l)); + // Add link highlight + listener + if (spannable.length() > 0) { + StackindexStack = new Stack<>(); + Matcher m = Patterns.WEB_URL.matcher(spannable.toString()); + while (m.find()) { + indexStack.push(m.start()); + indexStack.push(m.end()); + } + while (!indexStack.empty()) { + int end = indexStack.pop(); + int start = indexStack.pop(); + final String link = spannable.subSequence(start, end).toString(); + if (link.startsWith("https://")) { + spannable = spannable.delete(start, start + 8); + end -= 8; + } else if (link.startsWith("http://")) { + spannable = spannable.delete(start, start + 7); + end -= 7; + } + if (start + MAX_LINK_LENGTH < end) { + spannable.replace(start + MAX_LINK_LENGTH, end, "..."); + end = start + MAX_LINK_LENGTH + 3; + } + spannable.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + l.onLinkClick(link); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setColor(color); + ds.setUnderlineText(false); + } + }, start, end, SPAN_TYPE); + } + } + return spannable; + } + + /** + * Make a spannable String without listener + * + * @param text String that should be spannable + * @param color Text Color + * @return Spannable String + */ + public static Spannable makeText(@Nullable String text, int color) { + SpannableStringBuilder spannable = new SpannableStringBuilder(); + // Add '@' & '#' highlighting + if (text != null && text.length() > 0) { + spannable.append(text); + for (Pattern pattern : PATTERNS) { + Matcher m = pattern.matcher(spannable.toString()); + while (m.find()) { + int end = m.end(); + int start = m.start(); + ForegroundColorSpan colorSpan = new ForegroundColorSpan(color); + spannable.setSpan(colorSpan, start, end, SPAN_TYPE); + } + } + } + return spannable; + } + + /** + * Make a spannable String without listener + * http(s) links included will be shorted + * + * @param text String that should be spannable + * @param color Text Color + * @return Spannable String + */ + public static Spannable makeTextWithLinks(@Nullable String text, int color) { + SpannableStringBuilder spannable = new SpannableStringBuilder(makeText(text, color)); + // Add link highlighting + if (spannable.length() > 0) { + Stack indexStack = new Stack<>(); + Matcher m = Patterns.WEB_URL.matcher(spannable.toString()); + while (m.find()) { + indexStack.push(m.start()); + indexStack.push(m.end()); + } + while (!indexStack.empty()) { + int end = indexStack.pop(); + int start = indexStack.pop(); + final String link = spannable.subSequence(start, end).toString(); + if (link.startsWith("https://")) { + spannable = spannable.delete(start, start + 8); + end -= 8; + } else if (link.startsWith("http://")) { + spannable = spannable.delete(start, start + 7); + end -= 7; + } + if (start + MAX_LINK_LENGTH < end) { + spannable.replace(start + MAX_LINK_LENGTH, end, "..."); + end = start + MAX_LINK_LENGTH + 3; + } + ForegroundColorSpan colorSpan = new ForegroundColorSpan(color); + spannable.setSpan(colorSpan, start, end, SPAN_TYPE); + } + } + return spannable; + } + + /** + * Listener for clickable spans + */ + public interface OnTagClickListener { + /** + * Called when user clicks on a tag + * + * @param tag Tag string (starting with '@', '#') + */ + void onTagClick(String tag); + + /** + * Called when user clicks on link + * + * @param link http(s) link + */ + void onLinkClick(String link); + } +} diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/ImageViewer.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/ImageViewer.java index d4628720..6682baf9 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/ImageViewer.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/ImageViewer.java @@ -30,7 +30,7 @@ import org.nuclearfog.twidda.ui.dialogs.DescriptionDialog.DescriptionCallback; import org.nuclearfog.twidda.ui.dialogs.MetaDialog; import org.nuclearfog.twidda.ui.views.AnimatedImageView; import org.nuclearfog.twidda.ui.views.DescriptionView; -import org.nuclearfog.zoomview.ZoomView; +import org.nuclearfog.twidda.ui.views.ZoomView; import java.io.File; import java.io.Serializable; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/ProfileActivity.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/ProfileActivity.java index 773dfc7a..29aa9f11 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/ProfileActivity.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/ProfileActivity.java @@ -31,9 +31,6 @@ import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import com.squareup.picasso.Transformation; -import org.nuclearfog.tag.Tagger; -import org.nuclearfog.tag.Tagger.OnTagClickListener; -import org.nuclearfog.textviewtool.LinkAndScrollMovement; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.api.ConnectionException; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; @@ -45,8 +42,11 @@ import org.nuclearfog.twidda.backend.image.PicassoBuilder; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.EmojiUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils; +import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement; import org.nuclearfog.twidda.backend.utils.LinkUtils; import org.nuclearfog.twidda.backend.utils.StringUtils; +import org.nuclearfog.twidda.backend.utils.Tagger; +import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener; import org.nuclearfog.twidda.backend.utils.ToolbarUpdater; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.Relation; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java index 62879397..bc66991e 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/StatusActivity.java @@ -38,9 +38,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.squareup.picasso.Picasso; import com.squareup.picasso.Transformation; -import org.nuclearfog.tag.Tagger; -import org.nuclearfog.tag.Tagger.OnTagClickListener; -import org.nuclearfog.textviewtool.LinkAndScrollMovement; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.api.ConnectionException; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; @@ -53,8 +50,11 @@ import org.nuclearfog.twidda.backend.image.PicassoBuilder; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.EmojiUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils; +import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement; import org.nuclearfog.twidda.backend.utils.LinkUtils; import org.nuclearfog.twidda.backend.utils.StringUtils; +import org.nuclearfog.twidda.backend.utils.Tagger; +import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.Card; import org.nuclearfog.twidda.model.Location; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/FieldAdapter.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/FieldAdapter.java index d5e7fed4..dc7ac248 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/FieldAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/FieldAdapter.java @@ -5,7 +5,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView.Adapter; -import org.nuclearfog.tag.Tagger.OnTagClickListener; +import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener; import org.nuclearfog.twidda.model.lists.Fields; import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.FieldHolder; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/EditHistoryHolder.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/EditHistoryHolder.java index cd3e20b1..39ff9f71 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/EditHistoryHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/EditHistoryHolder.java @@ -20,15 +20,15 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.squareup.picasso.Picasso; import com.squareup.picasso.Transformation; -import org.nuclearfog.tag.Tagger; -import org.nuclearfog.textviewtool.LinkAndScrollMovement; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.async.AsyncExecutor; import org.nuclearfog.twidda.backend.async.TextEmojiLoader; import org.nuclearfog.twidda.backend.image.PicassoBuilder; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.EmojiUtils; +import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement; import org.nuclearfog.twidda.backend.utils.StringUtils; +import org.nuclearfog.twidda.backend.utils.Tagger; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.EditedStatus; import org.nuclearfog.twidda.model.User; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/FieldHolder.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/FieldHolder.java index 3ddc9b35..edf8fb59 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/FieldHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/FieldHolder.java @@ -10,11 +10,11 @@ import android.widget.TextView; import androidx.cardview.widget.CardView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import org.nuclearfog.tag.Tagger; -import org.nuclearfog.tag.Tagger.OnTagClickListener; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.StringUtils; +import org.nuclearfog.twidda.backend.utils.Tagger; +import org.nuclearfog.twidda.backend.utils.Tagger.OnTagClickListener; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.User.Field; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java index 77069ca6..83a6da27 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java @@ -12,10 +12,10 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; -import org.nuclearfog.tag.Tagger; -import org.nuclearfog.textviewtool.LinkAndScrollMovement; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.utils.AppStyles; +import org.nuclearfog.twidda.backend.utils.LinkAndScrollMovement; +import org.nuclearfog.twidda.backend.utils.Tagger; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.ScheduledStatus; import org.nuclearfog.twidda.model.Status; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/StatusHolder.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/StatusHolder.java index a7d29918..5dcaf940 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/StatusHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/StatusHolder.java @@ -23,7 +23,6 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.squareup.picasso.Picasso; import com.squareup.picasso.Transformation; -import org.nuclearfog.tag.Tagger; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; import org.nuclearfog.twidda.backend.async.TextEmojiLoader; @@ -33,6 +32,7 @@ import org.nuclearfog.twidda.backend.image.PicassoBuilder; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.EmojiUtils; import org.nuclearfog.twidda.backend.utils.StringUtils; +import org.nuclearfog.twidda.backend.utils.Tagger; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.Notification; import org.nuclearfog.twidda.model.Status; diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/views/ZoomView.java b/app/src/main/java/org/nuclearfog/twidda/ui/views/ZoomView.java new file mode 100644 index 00000000..eeca48d2 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/ui/views/ZoomView.java @@ -0,0 +1,219 @@ +package org.nuclearfog.twidda.ui.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.RemoteViews.RemoteView; + +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; + +import static android.view.MotionEvent.*; + +import org.nuclearfog.twidda.R; + +/** + * Zoomable image view + * + * @author nuclearfog + */ +@RemoteView +public class ZoomView extends AppCompatImageView { + + // Default values + private static final float DEF_MAX_ZOOM_IN = 3.0f; + private static final float DEF_MAX_ZOOM_OUT = 0.5f; + private static final boolean DEF_ENABLE_MOVE = true; + private static final ScaleType DEF_SCALE_TYPE = ScaleType.FIT_CENTER; + + // Layout Attributes + private float max_zoom_in = DEF_MAX_ZOOM_IN; + private float max_zoom_out = DEF_MAX_ZOOM_OUT; + private boolean enableMove = DEF_ENABLE_MOVE; + private ScaleType scaleType = DEF_SCALE_TYPE; + + // intern flags + private final PointF pos = new PointF(0.0f, 0.0f); + private final PointF dist = new PointF(0.0f, 0.0f); + private boolean moveLock = false; + + /** + * + */ + public ZoomView(Context context) { + super(context); + } + + /** + * + */ + public ZoomView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * + */ + public ZoomView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + scaleType = getScaleType(); + if (attrs != null) { + TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.ZoomView); + setMaxZoomIn(attrArray.getFloat(R.styleable.ZoomView_max_zoom_in, DEF_MAX_ZOOM_IN)); + setMaxZoomOut(attrArray.getFloat(R.styleable.ZoomView_max_zoom_out, DEF_MAX_ZOOM_OUT)); + setMovable(attrArray.getBoolean(R.styleable.ZoomView_enable_move, DEF_ENABLE_MOVE)); + attrArray.recycle(); + } + } + + + @Override + public boolean performClick() { + return super.performClick(); + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (getScaleType() != ScaleType.MATRIX) + setScaleType(ScaleType.MATRIX); + if (event.getPointerCount() == 1) { + + switch (event.getAction()) { + case ACTION_UP: + pos.set(event.getX(), event.getY()); + moveLock = false; + break; + + case ACTION_DOWN: + pos.set(event.getX(), event.getY()); + break; + + case ACTION_MOVE: + if (moveLock || !enableMove) + return super.performClick(); + float posX = event.getX() - pos.x; + float posY = event.getY() - pos.y; + pos.set(event.getX(), event.getY()); + Matrix m = new Matrix(getImageMatrix()); + m.postTranslate(posX, posY); + apply(m); + break; + } + } else if (event.getPointerCount() == 2) { + float distX, distY, scale; + switch (event.getActionMasked()) { + case ACTION_POINTER_UP: + case ACTION_POINTER_DOWN: + distX = event.getX(0) - event.getX(1); + distY = event.getY(0) - event.getY(1); + dist.set(distX, distY); // Distance vector + moveLock = true; + break; + + case ACTION_MOVE: + distX = event.getX(0) - event.getX(1); + distY = event.getY(0) - event.getY(1); + PointF current = new PointF(distX, distY); + scale = current.length() / dist.length(); + Matrix m = new Matrix(getImageMatrix()); + m.postScale(scale, scale, getWidth() / 2.0f, getHeight() / 2.0f); + dist.set(distX, distY); + apply(m); + break; + } + } + return true; + } + + /** + * Reset Image position/zoom to default + */ + public void reset() { + setScaleType(scaleType); + } + + /** + * set Image movable + * + * @param enableMove set image movable + */ + public void setMovable(boolean enableMove) { + this.enableMove = enableMove; + } + + /** + * set maximum zoom in + * + * @param max_zoom_in maximum zoom value + */ + public void setMaxZoomIn(float max_zoom_in) { + if (max_zoom_in < 1.0f) + throw new AssertionError("value should be more 1.0!"); + this.max_zoom_in = max_zoom_in; + } + + /** + * set maximum zoom in + * + * @param max_zoom_out maximum zoom value + */ + public void setMaxZoomOut(float max_zoom_out) { + if (max_zoom_out > 1.0f) + throw new AssertionError("value should be less 1.0!"); + this.max_zoom_out = max_zoom_out; + } + + /** + * + */ + private void apply(Matrix m) { + Drawable d = getDrawable(); + if (d == null) return; + + float[] val = new float[9]; + m.getValues(val); + float scale = (val[Matrix.MSCALE_X] + val[Matrix.MSCALE_Y]) / 2; // Scale factor + float width = d.getIntrinsicWidth() * scale; // image width + float height = d.getIntrinsicHeight() * scale; // image height + float leftBorder = val[Matrix.MTRANS_X]; // distance to left border + float rightBorder = -(val[Matrix.MTRANS_X] + width - getWidth()); // distance to right border + float bottomBorder = val[Matrix.MTRANS_Y]; // distance to bottom border + float topBorder = -(val[Matrix.MTRANS_Y] + height - getHeight()); // distance to top border + + if (width > getWidth()) { // is image width bigger than screen width? + if (rightBorder > 0) // is image on the right border? + m.postTranslate(rightBorder, 0); // clamp to right border + else if (leftBorder > 0) + m.postTranslate(-leftBorder, 0); // clamp to left order + } else if (leftBorder < 0 ^ rightBorder < 0) { // does image clash with one border? + if (rightBorder < 0) + m.postTranslate(rightBorder, 0); // clamp to right border + else + m.postTranslate(-leftBorder, 0); // clamp to left border + } + if (height > getHeight()) { // is image height bigger than screen height? + if (bottomBorder > 0) // is image on the bottom border? + m.postTranslate(0, -bottomBorder); // clamp to bottom border + else if (topBorder > 0) // is image on the top border? + m.postTranslate(0, topBorder); // clamp to top border + } else if (topBorder < 0 ^ bottomBorder < 0) { // does image clash with one border? + if (bottomBorder < 0) + m.postTranslate(0, -bottomBorder); // clamp to bottom border + else + m.postTranslate(0, topBorder); // clamp to top border + } + if (scale > max_zoom_in) { // scale limit exceeded? + float undoScale = max_zoom_in / scale; // undo scale setting + m.postScale(undoScale, undoScale, getWidth() / 2.0f, getHeight() / 2.0f); + } else if (scale < max_zoom_out) { // scale limit exceeded? + float undoScale = max_zoom_out / scale; // undo scale setting + m.postScale(undoScale, undoScale, getWidth() / 2.0f, getHeight() / 2.0f); + } + setImageMatrix(m); // set Image matrix + } +} \ No newline at end of file diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attr.xml index 20ae9449..c2798535 100644 --- a/app/src/main/res/values/attr.xml +++ b/app/src/main/res/values/attr.xml @@ -9,4 +9,10 @@ + + + \ No newline at end of file+ + +