Reduce flakiness of our dependencies.
This commit is contained in:
parent
fb8b230442
commit
1007b4d635
48 changed files with 3761 additions and 402 deletions
|
@ -16,9 +16,6 @@ repositories {
|
|||
includeGroupByRegex "org\\.signal.*"
|
||||
}
|
||||
}
|
||||
maven {
|
||||
url "https://www.jitpack.io"
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -29,10 +26,6 @@ repositories {
|
|||
jcenter {
|
||||
content {
|
||||
includeVersion "mobi.upod", "time-duration-picker", "1.1.3"
|
||||
includeVersion "cn.carbswang.android", "NumberPickerView", "1.0.9"
|
||||
includeVersion "com.takisoft.fix", "colorpicker", "0.9.1"
|
||||
includeVersion "com.codewaves.stickyheadergrid", "stickyheadergrid", "0.9.4"
|
||||
includeVersion "com.google.android", "flexbox", "0.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -474,6 +467,8 @@ dependencies {
|
|||
implementation project(':contacts')
|
||||
implementation project(':qr')
|
||||
implementation project(':sms-exporter')
|
||||
implementation project(':sticky-header-grid')
|
||||
implementation project(':photoview')
|
||||
|
||||
implementation libs.libsignal.android
|
||||
implementation libs.google.protobuf.javalite
|
||||
|
@ -494,7 +489,6 @@ dependencies {
|
|||
implementation libs.emilsjolander.stickylistheaders
|
||||
implementation libs.jpardogo.materialtabstrip
|
||||
implementation libs.apache.httpclient.android
|
||||
implementation libs.photoview
|
||||
implementation libs.glide.glide
|
||||
implementation libs.roundedimageview
|
||||
implementation libs.materialish.progress
|
||||
|
@ -503,12 +497,10 @@ dependencies {
|
|||
implementation libs.google.zxing.android.integration
|
||||
implementation libs.time.duration.picker
|
||||
implementation libs.google.zxing.core
|
||||
implementation libs.google.flexbox
|
||||
implementation (libs.subsampling.scale.image.view) {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
implementation (libs.numberpickerview) {
|
||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
}
|
||||
implementation (libs.android.tooltips) {
|
||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
}
|
||||
|
@ -517,15 +509,9 @@ dependencies {
|
|||
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
||||
}
|
||||
implementation libs.stream
|
||||
implementation (libs.colorpicker) {
|
||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
exclude group: 'com.android.support', module: 'recyclerview-v7'
|
||||
}
|
||||
|
||||
implementation libs.lottie
|
||||
|
||||
implementation libs.stickyheadergrid
|
||||
|
||||
implementation libs.signal.android.database.sqlcipher
|
||||
implementation libs.androidx.sqlite
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
|
|||
getLabel().setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin);
|
||||
break;
|
||||
case PIN_DOES_NOT_MATCH:
|
||||
getLabel().setText(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.red),
|
||||
getLabel().setText(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.ConfirmKbsPinFragment__pins_dont_match)));
|
||||
getInput().getText().clear();
|
||||
break;
|
||||
|
|
|
@ -54,7 +54,7 @@ public class CreateKbsPinFragment extends BaseKbsPinFragment<CreateKbsPinViewMod
|
|||
viewModel.getNavigationEvents().observe(getViewLifecycleOwner(), e -> onConfirmPin(e.getUserEntry(), e.getKeyboard(), args.getIsPinChange()));
|
||||
viewModel.getErrorEvents().observe(getViewLifecycleOwner(), e -> {
|
||||
if (e == CreateKbsPinViewModel.PinErrorEvent.WEAK_PIN) {
|
||||
getLabel().setText(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.red),
|
||||
getLabel().setText(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.red_500),
|
||||
getString(R.string.CreateKbsPinFragment__choose_a_stronger_pin)));
|
||||
shake(getInput(), () -> getInput().getText().clear());
|
||||
} else {
|
||||
|
|
|
@ -18,8 +18,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreferenceDialogFragmentCompat;
|
||||
|
||||
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat {
|
||||
|
||||
|
@ -40,9 +38,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
|
|||
public void onDisplayPreferenceDialog(Preference preference) {
|
||||
DialogFragment dialogFragment = null;
|
||||
|
||||
if (preference instanceof ColorPickerPreference) {
|
||||
dialogFragment = ColorPickerPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
} else if (preference instanceof CustomDefaultPreference) {
|
||||
if (preference instanceof CustomDefaultPreference) {
|
||||
dialogFragment = CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,253 +0,0 @@
|
|||
package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.res.TypedArrayUtils;
|
||||
import androidx.preference.DialogPreference;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.takisoft.colorpicker.ColorPickerDialog;
|
||||
import com.takisoft.colorpicker.ColorPickerDialog.Size;
|
||||
import com.takisoft.colorpicker.ColorStateDrawable;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class ColorPickerPreference extends DialogPreference {
|
||||
|
||||
private static final String TAG = Log.tag(ColorPickerPreference.class);
|
||||
|
||||
private int[] colors;
|
||||
private CharSequence[] colorDescriptions;
|
||||
private int color;
|
||||
private int columns;
|
||||
private int size;
|
||||
private boolean sortColors;
|
||||
|
||||
private ImageView colorWidget;
|
||||
private OnPreferenceChangeListener listener;
|
||||
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorPickerPreference, defStyleAttr, 0);
|
||||
|
||||
int colorsId = a.getResourceId(R.styleable.ColorPickerPreference_colors, R.array.color_picker_default_colors);
|
||||
|
||||
if (colorsId != 0) {
|
||||
colors = context.getResources().getIntArray(colorsId);
|
||||
}
|
||||
|
||||
colorDescriptions = a.getTextArray(R.styleable.ColorPickerPreference_colorDescriptions);
|
||||
color = a.getColor(R.styleable.ColorPickerPreference_currentColor, 0);
|
||||
columns = a.getInt(R.styleable.ColorPickerPreference_columns, 3);
|
||||
size = a.getInt(R.styleable.ColorPickerPreference_colorSize, 2);
|
||||
sortColors = a.getBoolean(R.styleable.ColorPickerPreference_sortColors, false);
|
||||
|
||||
a.recycle();
|
||||
|
||||
setWidgetLayoutResource(R.layout.preference_widget_color_swatch);
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
public ColorPickerPreference(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
|
||||
android.R.attr.dialogPreferenceStyle));
|
||||
}
|
||||
|
||||
public ColorPickerPreference(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnPreferenceChangeListener(OnPreferenceChangeListener listener) {
|
||||
super.setOnPreferenceChangeListener(listener);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
|
||||
colorWidget = (ImageView) holder.findViewById(R.id.color_picker_widget);
|
||||
setColorOnWidget(color);
|
||||
}
|
||||
|
||||
private void setColorOnWidget(int color) {
|
||||
if (colorWidget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Drawable[] colorDrawable = new Drawable[]
|
||||
{ContextCompat.getDrawable(getContext(), R.drawable.colorpickerpreference_pref_swatch)};
|
||||
colorWidget.setImageDrawable(new ColorStateDrawable(colorDrawable, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current color.
|
||||
*
|
||||
* @return The current color.
|
||||
*/
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current color.
|
||||
*
|
||||
* @param color The current color.
|
||||
*/
|
||||
public void setColor(int color) {
|
||||
setInternalColor(color, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the available colors.
|
||||
*
|
||||
* @return The available colors.
|
||||
*/
|
||||
public int[] getColors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available colors.
|
||||
*
|
||||
* @param colors The available colors.
|
||||
*/
|
||||
public void setColors(int[] colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the available colors should be sorted automatically based on their HSV
|
||||
* values.
|
||||
*
|
||||
* @return Whether the available colors should be sorted automatically based on their HSV
|
||||
* values.
|
||||
*/
|
||||
public boolean isSortColors() {
|
||||
return sortColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the available colors should be sorted automatically based on their HSV
|
||||
* values. The sorting does not modify the order of the original colors supplied via
|
||||
* {@link #setColors(int[])} or the XML attribute {@code app:colors}.
|
||||
*
|
||||
* @param sortColors Whether the available colors should be sorted automatically based on their
|
||||
* HSV values.
|
||||
*/
|
||||
public void setSortColors(boolean sortColors) {
|
||||
this.sortColors = sortColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available colors' descriptions that can be used by accessibility services.
|
||||
*
|
||||
* @return The available colors' descriptions.
|
||||
*/
|
||||
public CharSequence[] getColorDescriptions() {
|
||||
return colorDescriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available colors' descriptions that can be used by accessibility services.
|
||||
*
|
||||
* @param colorDescriptions The available colors' descriptions.
|
||||
*/
|
||||
public void setColorDescriptions(CharSequence[] colorDescriptions) {
|
||||
this.colorDescriptions = colorDescriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of columns to be used in the picker dialog for displaying the available
|
||||
* colors. If the value is less than or equals to 0, the number of columns will be determined
|
||||
* automatically by the system using FlexboxLayoutManager.
|
||||
*
|
||||
* @return The number of columns to be used in the picker dialog.
|
||||
* @see com.google.android.flexbox.FlexboxLayoutManager
|
||||
*/
|
||||
public int getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of columns to be used in the picker dialog for displaying the available
|
||||
* colors. If the value is less than or equals to 0, the number of columns will be determined
|
||||
* automatically by the system using FlexboxLayoutManager.
|
||||
*
|
||||
* @param columns The number of columns to be used in the picker dialog. Use 0 to set it to
|
||||
* 'auto' mode.
|
||||
* @see com.google.android.flexbox.FlexboxLayoutManager
|
||||
*/
|
||||
public void setColumns(int columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the color swatches in the dialog. It can be either
|
||||
* {@link ColorPickerDialog#SIZE_SMALL} or {@link ColorPickerDialog#SIZE_LARGE}.
|
||||
*
|
||||
* @return The size of the color swatches in the dialog.
|
||||
* @see ColorPickerDialog#SIZE_SMALL
|
||||
* @see ColorPickerDialog#SIZE_LARGE
|
||||
*/
|
||||
@Size
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the color swatches in the dialog. It can be either
|
||||
* {@link ColorPickerDialog#SIZE_SMALL} or {@link ColorPickerDialog#SIZE_LARGE}.
|
||||
*
|
||||
* @param size The size of the color swatches in the dialog. It can be either
|
||||
* {@link ColorPickerDialog#SIZE_SMALL} or {@link ColorPickerDialog#SIZE_LARGE}.
|
||||
* @see ColorPickerDialog#SIZE_SMALL
|
||||
* @see ColorPickerDialog#SIZE_LARGE
|
||||
*/
|
||||
public void setSize(@Size int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
private void setInternalColor(int color, boolean force) {
|
||||
int oldColor = getPersistedInt(0);
|
||||
|
||||
boolean changed = oldColor != color;
|
||||
|
||||
if (changed || force) {
|
||||
this.color = color;
|
||||
|
||||
persistInt(color);
|
||||
|
||||
setColorOnWidget(color);
|
||||
|
||||
if (listener != null) listener.onPreferenceChange(this, color);
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object onGetDefaultValue(TypedArray a, int index) {
|
||||
return a.getString(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSetInitialValue(boolean restoreValue, Object defaultValueObj) {
|
||||
final String defaultValue = (String) defaultValueObj;
|
||||
setInternalColor(restoreValue ? getPersistedInt(0) : (!TextUtils.isEmpty(defaultValue) ? Color.parseColor(defaultValue) : 0), true);
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package org.thoughtcrime.securesms.preferences.widgets;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceDialogFragmentCompat;
|
||||
|
||||
import com.takisoft.colorpicker.ColorPickerDialog;
|
||||
import com.takisoft.colorpicker.OnColorSelectedListener;
|
||||
|
||||
public class ColorPickerPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements OnColorSelectedListener {
|
||||
|
||||
private int pickedColor;
|
||||
|
||||
public static ColorPickerPreferenceDialogFragmentCompat newInstance(String key) {
|
||||
ColorPickerPreferenceDialogFragmentCompat fragment = new ColorPickerPreferenceDialogFragmentCompat();
|
||||
Bundle b = new Bundle(1);
|
||||
b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key);
|
||||
fragment.setArguments(b);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ColorPickerPreference pref = getColorPickerPreference();
|
||||
|
||||
ColorPickerDialog.Params params = new ColorPickerDialog.Params.Builder(getContext())
|
||||
.setSelectedColor(pref.getColor())
|
||||
.setColors(pref.getColors())
|
||||
.setColorContentDescriptions(pref.getColorDescriptions())
|
||||
.setSize(pref.getSize())
|
||||
.setSortColors(pref.isSortColors())
|
||||
.setColumns(pref.getColumns())
|
||||
.build();
|
||||
|
||||
ColorPickerDialog dialog = new ColorPickerDialog(getActivity(), this, params);
|
||||
dialog.setTitle(pref.getDialogTitle());
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogClosed(boolean positiveResult) {
|
||||
ColorPickerPreference preference = getColorPickerPreference();
|
||||
|
||||
if (positiveResult) {
|
||||
preference.setColor(pickedColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorSelected(int color) {
|
||||
this.pickedColor = color;
|
||||
|
||||
super.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
|
||||
}
|
||||
|
||||
ColorPickerPreference getColorPickerPreference() {
|
||||
return (ColorPickerPreference) getPreference();
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|||
import org.thoughtcrime.securesms.net.ContentProxySelector
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver
|
||||
import org.thoughtcrime.securesms.util.DeviceProperties
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ripple android:color="@color/green_700" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/green" />
|
||||
<solid android:color="@color/green_500" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ripple android:color="@color/red_700" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/red" />
|
||||
<solid android:color="@color/red_500" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</item>
|
||||
<item android:state_pressed="false">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/green" />
|
||||
<solid android:color="@color/green_500" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</item>
|
||||
<item android:state_pressed="false">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/red" />
|
||||
<solid android:color="@color/red_500" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:background="@color/green"
|
||||
tools:background="@color/green_500"
|
||||
tools:layout_width="wrap_content"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|end"
|
||||
android:orientation="horizontal"
|
||||
tools:background="@color/green"
|
||||
tools:background="@color/green_500"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center">
|
||||
|
||||
<cn.carbswang.android.numberpickerview.library.NumberPickerView
|
||||
android:id="@+id/expiration_number_picker"
|
||||
android:layout_alignParentTop="true"
|
||||
app:npv_WrapSelectorWheel="false"
|
||||
app:npv_DividerColor="#cbc8ea"
|
||||
app:npv_TextColorNormal="@color/signal_text_primary_dialog"
|
||||
app:npv_TextColorSelected="@color/signal_text_selected"
|
||||
app:npv_ItemPaddingVertical="20dp"
|
||||
app:npv_TextColorHint="@color/grey_600"
|
||||
app:npv_TextSizeNormal="16sp"
|
||||
app:npv_TextSizeSelected="16sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView android:id="@+id/expiration_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/expiration_number_picker"
|
||||
android:minLines="3"
|
||||
android:padding="20dp"
|
||||
tools:text="Your messages will not expire."/>
|
||||
|
||||
|
||||
</RelativeLayout>
|
|
@ -18,6 +18,6 @@
|
|||
android:layout_height="16dp"
|
||||
android:src="@drawable/circle_white"
|
||||
android:layout_gravity="center"
|
||||
tools:tint="@color/red"/>
|
||||
tools:tint="@color/red_500"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -42,7 +42,7 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@color/red" />
|
||||
tools:background="@color/red_500" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_switch"
|
||||
|
@ -61,7 +61,7 @@
|
|||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@id/camera_switch"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@color/green" />
|
||||
tools:background="@color/green_500" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
|
@ -33,7 +33,7 @@
|
|||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@color/red" />
|
||||
tools:background="@color/red_500" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_switch"
|
||||
|
@ -48,6 +48,6 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:background="@color/green" />
|
||||
tools:background="@color/green_500" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
android:background="@drawable/circle_tintable"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="?android:windowBackground"
|
||||
tools:backgroundTint="@color/red"
|
||||
tools:backgroundTint="@color/red_500"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
|
|
|
@ -108,7 +108,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="@id/story"
|
||||
app:layout_constraintEnd_toEndOf="@id/story"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Signal.Story.Preview"
|
||||
tools:background="@color/green"
|
||||
tools:background="@color/green_500"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
android:scaleType="centerCrop"
|
||||
android:translationX="-28dp"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
||||
tools:background="@color/red" />
|
||||
tools:background="@color/red_500" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/preview_media_1"
|
||||
|
@ -60,7 +60,7 @@
|
|||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
||||
app:strokeColor="@color/signal_colorBackground"
|
||||
app:strokeWidth="3dp"
|
||||
tools:background="@color/green" />
|
||||
tools:background="@color/green_500" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/color_bar"
|
||||
app:layout_constraintStart_toStartOf="@id/color_bar"
|
||||
tools:alpha="1"
|
||||
tools:tint="@color/red" />
|
||||
tools:tint="@color/red_500" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/color_bar"
|
||||
|
|
|
@ -192,7 +192,7 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||
app:layout_constraintStart_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||
tools:alpha="1"
|
||||
tools:tint="@color/red" />
|
||||
tools:tint="@color/red_500" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/image_editor_hud_draw_color_bar"
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||
app:layout_constraintStart_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||
tools:alpha="1"
|
||||
tools:tint="@color/red" />
|
||||
tools:tint="@color/red_500" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSeekBar
|
||||
android:id="@+id/image_editor_hud_draw_color_bar"
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
tools:viewBindingIgnore="true"
|
||||
android:layout_width="54dp"
|
||||
android:layout_height="72dp"
|
||||
tools:background="@color/red"
|
||||
tools:background="@color/red_500"
|
||||
tools:visibility="visible" />
|
|
@ -9,7 +9,7 @@
|
|||
android:background="@null"
|
||||
android:clipChildren="true"
|
||||
app:cardCornerRadius="8dp"
|
||||
tools:background="@color/red"
|
||||
tools:background="@color/red_500"
|
||||
tools:visibility="visible">
|
||||
|
||||
<include
|
||||
|
|
|
@ -74,6 +74,7 @@ dependencyResolutionManagement {
|
|||
alias('google-ez-vcard').to('com.googlecode.ez-vcard:ez-vcard:0.9.11')
|
||||
alias('google-jsr305').to('com.google.code.findbugs:jsr305:3.0.2')
|
||||
alias('google-guava-android').to('com.google.guava:guava:30.0-android')
|
||||
alias('google-flexbox').to('com.google.android.flexbox:flexbox:3.0.0')
|
||||
|
||||
// Exoplayer
|
||||
alias('exoplayer-core').to('com.google.android.exoplayer', 'exoplayer-core').versionRef('exoplayer')
|
||||
|
@ -111,19 +112,15 @@ dependencyResolutionManagement {
|
|||
alias('apache-httpclient-android').to('org.apache.httpcomponents:httpclient-android:4.3.5')
|
||||
alias('glide-glide').to('com.github.bumptech.glide', 'glide').versionRef('glide')
|
||||
alias('glide-compiler').to('com.github.bumptech.glide', 'compiler').versionRef('glide')
|
||||
alias('photoview').to('com.github.chrisbanes:PhotoView:2.3.0')
|
||||
alias('roundedimageview').to('com.makeramen:roundedimageview:2.1.0')
|
||||
alias('materialish-progress').to('com.pnikosis:materialish-progress:1.5')
|
||||
alias('waitingdots').to('pl.tajchert:waitingdots:0.1.0')
|
||||
alias('time-duration-picker').to('mobi.upod:time-duration-picker:1.1.3')
|
||||
alias('subsampling-scale-image-view').to('com.davemorrissey.labs:subsampling-scale-image-view:3.10.0')
|
||||
alias('numberpickerview').to('cn.carbswang.android:NumberPickerView:1.0.9')
|
||||
alias('android-tooltips').to('com.tomergoldst.android:tooltips:1.0.6')
|
||||
alias('android-smsmms').to('com.klinkerapps:android-smsmms:4.0.1')
|
||||
alias('stream').to('com.annimon:stream:1.1.8')
|
||||
alias('colorpicker').to('com.takisoft.fix:colorpicker:0.9.1')
|
||||
alias('lottie').to('com.airbnb.android:lottie:3.6.0')
|
||||
alias('stickyheadergrid').to('com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4')
|
||||
alias('dnsjava').to('dnsjava:dnsjava:2.1.9')
|
||||
alias('nanohttpd-webserver').to('org.nanohttpd:nanohttpd-webserver:2.3.1')
|
||||
|
||||
|
|
|
@ -20,6 +20,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
</trusted-artifacts>
|
||||
</configuration>
|
||||
<components>
|
||||
<component group="androidx.activity" name="activity" version="1.2.4">
|
||||
<artifact name="activity-1.2.4.aar">
|
||||
<sha256 value="ae8e9c7de57e387d2ad90e73f3a5a5dfd502bd4f034c1dccfdb3506d1d2df81a" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="activity-1.2.4.module">
|
||||
<sha256 value="20f5634f76717910e5b4299909d6998a8078f106bdb9e15b2dd75fcfc1bd42b5" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.activity" name="activity" version="1.5.1">
|
||||
<artifact name="activity-1.5.1.aar">
|
||||
<sha256 value="4b04b42d2c1f81c02faf0f7b6e9cc9fede10fdee8f66136cd4b99f88e8f48c0f" origin="Generated by Gradle"/>
|
||||
|
@ -78,6 +86,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="0361d1526a4d7501255e19779e09e93cdbd07fee0e2f5c50b7a137432d510119" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.appcompat" name="appcompat" version="1.4.1">
|
||||
<artifact name="appcompat-1.4.1.aar">
|
||||
<sha256 value="9f1e6a958f7b02f57f9069362978baae3e73dbde9e15e9d2f119a28e0b48f799" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="appcompat-1.4.1.module">
|
||||
<sha256 value="0ec2f0dff5a05cb960a114c35619d8c9a1edbc518407f59b030ab38611fec017" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.appcompat" name="appcompat" version="1.5.1">
|
||||
<artifact name="appcompat-1.5.1.aar">
|
||||
<sha256 value="50f2c7756e28a7da648bd4551ee3b31e0b71863a6bf591f0ca978428219c5eab" origin="Generated by Gradle"/>
|
||||
|
@ -86,6 +102,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="8c8e88ccafb55c142ea2ac0669fd4db8e7190a853d73fcc429a5c012ceacddf2" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.appcompat" name="appcompat-resources" version="1.4.1">
|
||||
<artifact name="appcompat-resources-1.4.1.aar">
|
||||
<sha256 value="c324f0a157e3cf2decd8448107de487420cb4f4fb4853cd634d71ec1f2226224" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="appcompat-resources-1.4.1.module">
|
||||
<sha256 value="e4a0fcabaa5f44a32e28de2a5623424b8898563cf0b40674e441149b50281ec5" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.appcompat" name="appcompat-resources" version="1.5.1">
|
||||
<artifact name="appcompat-resources-1.5.1.aar">
|
||||
<sha256 value="6cfa9caae1869ffe03da9ec3ebf47ad84d70a7185ee86ec63aa8289cbb457e67" origin="Generated by Gradle"/>
|
||||
|
@ -232,6 +256,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="e3877fa529fe29177f34a26e0790ed35544848b0c7503bfed30b2539f1686d65" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.core" name="core" version="1.7.0">
|
||||
<artifact name="core-1.7.0.aar">
|
||||
<sha256 value="aaf6734226fff923784f92f65d78a2984dbf17534138855c5ce2038f18656e0b" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="core-1.7.0.module">
|
||||
<sha256 value="988f820899d5a4982e5c878ca1cd417970ace332ea2ff72f5be19b233fa0e788" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.core" name="core" version="1.8.0">
|
||||
<artifact name="core-1.8.0.aar">
|
||||
<sha256 value="48c64a15ec3eb11bfb33339e5ceb70ec7f821bd2dfa2eb8675ebd5353317e792" origin="Generated by Gradle"/>
|
||||
|
@ -319,6 +351,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="ce005162c229bf308d2d5b12fb6cad0874069cbbeaccee63a8193bd08d40de04" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.emoji2" name="emoji2" version="1.0.0">
|
||||
<artifact name="emoji2-1.0.0.aar">
|
||||
<sha256 value="af075731a4d17beaa29e69194a6baaf5f3512814c19aa1b8965d1920b0b7fbfa" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="emoji2-1.0.0.module">
|
||||
<sha256 value="9b16729827de022d1e0b68b8f86d146752f24c0ec0a55266672afd243088c57c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.emoji2" name="emoji2" version="1.2.0">
|
||||
<artifact name="emoji2-1.2.0.aar">
|
||||
<sha256 value="f31a06c150ecb03073f55a6f7b0b74a240a6a8d727c14ce76726d020570dfa8c" origin="Generated by Gradle"/>
|
||||
|
@ -327,6 +367,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="9d1996cca03777baa1f27cd15531db983a633dae37b90f85bd53501acb56699d" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.emoji2" name="emoji2-views-helper" version="1.0.0">
|
||||
<artifact name="emoji2-views-helper-1.0.0.aar">
|
||||
<sha256 value="39616a3d66551fc18585112ce1d12a14ac0fb588a89a6528cd97842da6221ec2" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="emoji2-views-helper-1.0.0.module">
|
||||
<sha256 value="a6a2a613c35a982f2b015fa22288dab65a7ab6940805ef8da0b7ffefb8c4c92b" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.emoji2" name="emoji2-views-helper" version="1.2.0">
|
||||
<artifact name="emoji2-views-helper-1.2.0.aar">
|
||||
<sha256 value="7ffa4d464d9db259fca0cdb50fbd4ab63d6872bcda59468b9f7555504c7d5ac4" origin="Generated by Gradle"/>
|
||||
|
@ -504,6 +552,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="1cedc0f971979cdec31f1542f263a8d41df6cfffc4857ea73a7d85643b73486d" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.lifecycle" name="lifecycle-process" version="2.4.0">
|
||||
<artifact name="lifecycle-process-2.4.0.aar">
|
||||
<sha256 value="32f175588bd62df5672448516d9e4f3d4935d0020c0d9517958c9ffbd7c207e5" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="lifecycle-process-2.4.0.module">
|
||||
<sha256 value="0bc4154e94dcb50f34afc8a6ea76b0ca03792ac2d800abae9dcbe905a6745a2c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.lifecycle" name="lifecycle-process" version="2.4.1">
|
||||
<artifact name="lifecycle-process-2.4.1.aar">
|
||||
<sha256 value="db649b3efa24e31052145310b002db91da346b3f89c093ec38c3046db45e794e" origin="Generated by Gradle"/>
|
||||
|
@ -549,6 +605,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="2a7b90e5049b674b36bccfd68677b3a0b3178b3f7c2ef7ddf618d3895598c4ce" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.4.0">
|
||||
<artifact name="lifecycle-runtime-2.4.0.aar">
|
||||
<sha256 value="fba2ea76a2c0a8d2804e611367d9703c5ab2ecaafebca4932b00fd672c593588" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="lifecycle-runtime-2.4.0.module">
|
||||
<sha256 value="c089d36989257144e571fef9b5229e62ff56eab91832b1196eccb90d44b8038c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.5.1">
|
||||
<artifact name="lifecycle-runtime-2.5.1.aar">
|
||||
<sha256 value="33b0d73dc2f028fceb3599bacabe563c3db6d26f3513d889595863536a4ac8c0" origin="Generated by Gradle"/>
|
||||
|
@ -767,6 +831,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="23542c8c85cc58fafe0ae8cba201e6c9e01b4c6799223340a2d6a51d7784828c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.resourceinspection" name="resourceinspection-annotation" version="1.0.0">
|
||||
<artifact name="resourceinspection-annotation-1.0.0.jar">
|
||||
<sha256 value="8cff870ec6fb31db48a52f4a792335b4bf8de07e03bd37823181526433ccd5cb" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="resourceinspection-annotation-1.0.0.module">
|
||||
<sha256 value="16cf0dbbbd97081b86cf0654e9d2ef684e083a672c8ac8590345831bd24e5746" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.resourceinspection" name="resourceinspection-annotation" version="1.0.1">
|
||||
<artifact name="resourceinspection-annotation-1.0.1.jar">
|
||||
<sha256 value="8cff870ec6fb31db48a52f4a792335b4bf8de07e03bd37823181526433ccd5cb" origin="Generated by Gradle"/>
|
||||
|
@ -775,6 +847,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="352a11a8d8a4c1bd6cd2c2fefff9c94ca954d7b5202a0656959db95297f6a2b7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.savedstate" name="savedstate" version="1.1.0">
|
||||
<artifact name="savedstate-1.1.0.aar">
|
||||
<sha256 value="d60bbe44c2c08083a17c5dc678a6d6b4d0a2d664858016ab5c049cbea90a63b7" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="savedstate-1.1.0.module">
|
||||
<sha256 value="6eea2bc150828c8fcba777e93037038e2ee3ec441c43d6acecf2c5cd9ddc5374" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.savedstate" name="savedstate" version="1.2.0">
|
||||
<artifact name="savedstate-1.2.0.aar">
|
||||
<sha256 value="2de528d6898e95ef020d22d9ffdf9d1f77cbdd93f92d39dfaa5d5c43b0c311c8" origin="Generated by Gradle"/>
|
||||
|
@ -826,6 +906,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.startup" name="startup-runtime" version="1.0.0">
|
||||
<artifact name="startup-runtime-1.0.0.aar">
|
||||
<sha256 value="ff081d2db7dd28aec59f74934c514fbaf4ae5aac5258495fe10d612a3622f876" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="startup-runtime-1.0.0.module">
|
||||
<sha256 value="40effca0d6ee1fde32bc296897e54ebbcc4cf4aa29b0c531036cbd2a824a3c24" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
|
@ -1649,6 +1732,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
|||
<sha256 value="313157e19cc69358139899fb582d110e361e54ab80bf681b60d7247b5b69a822" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.android.flexbox" name="flexbox" version="3.0.0">
|
||||
<artifact name="flexbox-3.0.0.aar">
|
||||
<sha256 value="5e19500486fd7c8e2e8c7aad6bbba9c8d0ada7057c6b32b9b5c61439814e7574" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="flexbox-3.0.0.module">
|
||||
<sha256 value="96f6e6d9f5e5db840ee3da20449e16828ed4ba2c07783dde3397d13aaf814e19" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.android.gms" name="play-services-auth" version="16.0.1">
|
||||
<artifact name="play-services-auth-16.0.1.aar">
|
||||
<sha256 value="aec9e1c584d442cb9f59481a50b2c66dc191872607c04d97ecb82dd0eb5149ec" origin="Generated by Gradle"/>
|
||||
|
|
62
photoview/build.gradle
Normal file
62
photoview/build.gradle
Normal file
|
@ -0,0 +1,62 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'com.google.protobuf'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
buildToolsVersion BUILD_TOOL_VERSION
|
||||
compileSdkVersion COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion MINIMUM_SDK
|
||||
targetSdkVersion TARGET_SDK
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JAVA_VERSION
|
||||
targetCompatibility JAVA_VERSION
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = 'com.google.protobuf:protoc:3.18.0'
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
task.builtins {
|
||||
java {
|
||||
option "lite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
lintChecks project(':lintchecks')
|
||||
|
||||
coreLibraryDesugaring libs.android.tools.desugar
|
||||
|
||||
api libs.androidx.annotation
|
||||
|
||||
implementation libs.androidx.core.ktx
|
||||
implementation libs.androidx.lifecycle.common.java8
|
||||
implementation libs.google.protobuf.javalite
|
||||
implementation libs.androidx.sqlite
|
||||
implementation libs.rxjava3.rxjava
|
||||
|
||||
testImplementation testLibs.junit.junit
|
||||
testImplementation testLibs.mockito.core
|
||||
testImplementation (testLibs.robolectric.robolectric) {
|
||||
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
||||
}
|
||||
}
|
6
photoview/src/main/AndroidManifest.xml
Normal file
6
photoview/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.chrisbanes.photoview">
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
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 com.github.chrisbanes.photoview;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.view.View;
|
||||
|
||||
class Compat {
|
||||
|
||||
private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
|
||||
|
||||
public static void postOnAnimation(View view, Runnable runnable) {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||
postOnAnimationJellyBean(view, runnable);
|
||||
} else {
|
||||
view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(16)
|
||||
private static void postOnAnimationJellyBean(View view, Runnable runnable) {
|
||||
view.postOnAnimation(runnable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
Copyright 2011, 2012 Chris Banes.
|
||||
<p/>
|
||||
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
|
||||
<p/>
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
<p/>
|
||||
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 com.github.chrisbanes.photoview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
/**
|
||||
* Does a whole lot of gesture detecting.
|
||||
*/
|
||||
class CustomGestureDetector {
|
||||
|
||||
private static final int INVALID_POINTER_ID = -1;
|
||||
|
||||
private int mActivePointerId = INVALID_POINTER_ID;
|
||||
private int mActivePointerIndex = 0;
|
||||
private final ScaleGestureDetector mDetector;
|
||||
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private boolean mIsDragging;
|
||||
private float mLastTouchX;
|
||||
private float mLastTouchY;
|
||||
private final float mTouchSlop;
|
||||
private final float mMinimumVelocity;
|
||||
private OnGestureListener mListener;
|
||||
|
||||
CustomGestureDetector(Context context, OnGestureListener listener) {
|
||||
final ViewConfiguration configuration = ViewConfiguration
|
||||
.get(context);
|
||||
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||
mTouchSlop = configuration.getScaledTouchSlop();
|
||||
|
||||
mListener = listener;
|
||||
ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
|
||||
private float lastFocusX, lastFocusY = 0;
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
float scaleFactor = detector.getScaleFactor();
|
||||
|
||||
if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
|
||||
return false;
|
||||
|
||||
if (scaleFactor >= 0) {
|
||||
mListener.onScale(scaleFactor,
|
||||
detector.getFocusX(),
|
||||
detector.getFocusY(),
|
||||
detector.getFocusX() - lastFocusX,
|
||||
detector.getFocusY() - lastFocusY
|
||||
);
|
||||
lastFocusX = detector.getFocusX();
|
||||
lastFocusY = detector.getFocusY();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
lastFocusX = detector.getFocusX();
|
||||
lastFocusY = detector.getFocusY();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
// NO-OP
|
||||
}
|
||||
};
|
||||
mDetector = new ScaleGestureDetector(context, mScaleListener);
|
||||
}
|
||||
|
||||
private float getActiveX(MotionEvent ev) {
|
||||
try {
|
||||
return ev.getX(mActivePointerIndex);
|
||||
} catch (Exception e) {
|
||||
return ev.getX();
|
||||
}
|
||||
}
|
||||
|
||||
private float getActiveY(MotionEvent ev) {
|
||||
try {
|
||||
return ev.getY(mActivePointerIndex);
|
||||
} catch (Exception e) {
|
||||
return ev.getY();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isScaling() {
|
||||
return mDetector.isInProgress();
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
try {
|
||||
mDetector.onTouchEvent(ev);
|
||||
return processTouchEvent(ev);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Fix for support lib bug, happening when onDestroy is called
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processTouchEvent(MotionEvent ev) {
|
||||
final int action = ev.getAction();
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
if (null != mVelocityTracker) {
|
||||
mVelocityTracker.addMovement(ev);
|
||||
}
|
||||
|
||||
mLastTouchX = getActiveX(ev);
|
||||
mLastTouchY = getActiveY(ev);
|
||||
mIsDragging = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float x = getActiveX(ev);
|
||||
final float y = getActiveY(ev);
|
||||
final float dx = x - mLastTouchX, dy = y - mLastTouchY;
|
||||
|
||||
if (!mIsDragging) {
|
||||
// Use Pythagoras to see if drag length is larger than
|
||||
// touch slop
|
||||
mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
|
||||
}
|
||||
|
||||
if (mIsDragging) {
|
||||
mListener.onDrag(dx, dy);
|
||||
mLastTouchX = x;
|
||||
mLastTouchY = y;
|
||||
|
||||
if (null != mVelocityTracker) {
|
||||
mVelocityTracker.addMovement(ev);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mActivePointerId = INVALID_POINTER_ID;
|
||||
// Recycle Velocity Tracker
|
||||
if (null != mVelocityTracker) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mActivePointerId = INVALID_POINTER_ID;
|
||||
if (mIsDragging) {
|
||||
if (null != mVelocityTracker) {
|
||||
mLastTouchX = getActiveX(ev);
|
||||
mLastTouchY = getActiveY(ev);
|
||||
|
||||
// Compute velocity within the last 1000ms
|
||||
mVelocityTracker.addMovement(ev);
|
||||
mVelocityTracker.computeCurrentVelocity(1000);
|
||||
|
||||
final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
|
||||
.getYVelocity();
|
||||
|
||||
// If the velocity is greater than minVelocity, call
|
||||
// listener
|
||||
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
|
||||
mListener.onFling(mLastTouchX, mLastTouchY, -vX,
|
||||
-vY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recycle Velocity Tracker
|
||||
if (null != mVelocityTracker) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
final int pointerIndex = Util.getPointerIndex(ev.getAction());
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
// This was our active pointer going up. Choose a new
|
||||
// active pointer and adjust accordingly.
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
mLastTouchX = ev.getX(newPointerIndex);
|
||||
mLastTouchY = ev.getY(newPointerIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
mActivePointerIndex = ev
|
||||
.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
|
||||
: 0);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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 com.github.chrisbanes.photoview;
|
||||
|
||||
interface OnGestureListener {
|
||||
|
||||
void onDrag(float dx, float dy);
|
||||
|
||||
void onFling(float startX, float startY, float velocityX,
|
||||
float velocityY);
|
||||
|
||||
void onScale(float scaleFactor, float focusX, float focusY);
|
||||
|
||||
void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the internal Matrix has changed for
|
||||
* this View.
|
||||
*/
|
||||
public interface OnMatrixChangedListener {
|
||||
|
||||
/**
|
||||
* Callback for when the Matrix displaying the Drawable has changed. This could be because
|
||||
* the View's bounds have changed, or the user has zoomed.
|
||||
*
|
||||
* @param rect - Rectangle displaying the Drawable's new bounds.
|
||||
*/
|
||||
void onMatrixChanged(RectF rect);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* Callback when the user tapped outside of the photo
|
||||
*/
|
||||
public interface OnOutsidePhotoTapListener {
|
||||
|
||||
/**
|
||||
* The outside of the photo has been tapped
|
||||
*/
|
||||
void onOutsidePhotoTap(ImageView imageView);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
import android.widget.ImageView;
|
||||
|
||||
/**
|
||||
* A callback to be invoked when the Photo is tapped with a single
|
||||
* tap.
|
||||
*/
|
||||
public interface OnPhotoTapListener {
|
||||
|
||||
/**
|
||||
* A callback to receive where the user taps on a photo. You will only receive a callback if
|
||||
* the user taps on the actual photo, tapping on 'whitespace' will be ignored.
|
||||
*
|
||||
* @param view ImageView the user tapped.
|
||||
* @param x where the user tapped from the of the Drawable, as percentage of the
|
||||
* Drawable width.
|
||||
* @param y where the user tapped from the top of the Drawable, as percentage of the
|
||||
* Drawable height.
|
||||
*/
|
||||
void onPhotoTap(ImageView view, float x, float y);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
|
||||
/**
|
||||
* Interface definition for callback to be invoked when attached ImageView scale changes
|
||||
*/
|
||||
public interface OnScaleChangedListener {
|
||||
|
||||
/**
|
||||
* Callback for when the scale changes
|
||||
*
|
||||
* @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in)
|
||||
* @param focusX focal point X position
|
||||
* @param focusY focal point Y position
|
||||
*/
|
||||
void onScaleChange(float scaleFactor, float focusX, float focusY);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
/**
|
||||
* A callback to be invoked when the ImageView is flung with a single
|
||||
* touch
|
||||
*/
|
||||
public interface OnSingleFlingListener {
|
||||
|
||||
/**
|
||||
* A callback to receive where the user flings on a ImageView. You will receive a callback if
|
||||
* the user flings anywhere on the view.
|
||||
*
|
||||
* @param e1 MotionEvent the user first touch.
|
||||
* @param e2 MotionEvent the user last touch.
|
||||
* @param velocityX distance of user's horizontal fling.
|
||||
* @param velocityY distance of user's vertical fling.
|
||||
*/
|
||||
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the photo is experiencing a drag event
|
||||
*/
|
||||
public interface OnViewDragListener {
|
||||
|
||||
/**
|
||||
* Callback for when the photo is experiencing a drag event. This cannot be invoked when the
|
||||
* user is scaling.
|
||||
*
|
||||
* @param dx The change of the coordinates in the x-direction
|
||||
* @param dy The change of the coordinates in the y-direction
|
||||
*/
|
||||
void onDrag(float dx, float dy);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
public interface OnViewTapListener {
|
||||
|
||||
/**
|
||||
* A callback to receive where the user taps on a ImageView. You will receive a callback if
|
||||
* the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.
|
||||
*
|
||||
* @param view - View the user tapped.
|
||||
* @param x - where the user tapped from the left of the View.
|
||||
* @param y - where the user tapped from the top of the View.
|
||||
*/
|
||||
void onViewTap(View view, float x, float y);
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
Copyright 2011, 2012 Chris Banes.
|
||||
<p>
|
||||
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
|
||||
<p>
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
<p>
|
||||
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 com.github.chrisbanes.photoview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
|
||||
/**
|
||||
* A zoomable ImageView. See {@link PhotoViewAttacher} for most of the details on how the zooming
|
||||
* is accomplished
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class PhotoView extends AppCompatImageView {
|
||||
|
||||
private PhotoViewAttacher attacher;
|
||||
private ScaleType pendingScaleType;
|
||||
|
||||
public PhotoView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PhotoView(Context context, AttributeSet attr) {
|
||||
this(context, attr, 0);
|
||||
}
|
||||
|
||||
public PhotoView(Context context, AttributeSet attr, int defStyle) {
|
||||
super(context, attr, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
attacher = new PhotoViewAttacher(this);
|
||||
//We always pose as a Matrix scale type, though we can change to another scale type
|
||||
//via the attacher
|
||||
super.setScaleType(ScaleType.MATRIX);
|
||||
//apply the previously applied scale type
|
||||
if (pendingScaleType != null) {
|
||||
setScaleType(pendingScaleType);
|
||||
pendingScaleType = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current {@link PhotoViewAttacher} for this view. Be wary of holding on to references
|
||||
* to this attacher, as it has a reference to this view, which, if a reference is held in the
|
||||
* wrong place, can cause memory leaks.
|
||||
*
|
||||
* @return the attacher.
|
||||
*/
|
||||
public PhotoViewAttacher getAttacher() {
|
||||
return attacher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleType getScaleType() {
|
||||
return attacher.getScaleType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix getImageMatrix() {
|
||||
return attacher.getImageMatrix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnLongClickListener(OnLongClickListener l) {
|
||||
attacher.setOnLongClickListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
attacher.setOnClickListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleType(ScaleType scaleType) {
|
||||
if (attacher == null) {
|
||||
pendingScaleType = scaleType;
|
||||
} else {
|
||||
attacher.setScaleType(scaleType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
super.setImageDrawable(drawable);
|
||||
// setImageBitmap calls through to this method
|
||||
if (attacher != null) {
|
||||
attacher.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageResource(int resId) {
|
||||
super.setImageResource(resId);
|
||||
if (attacher != null) {
|
||||
attacher.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageURI(Uri uri) {
|
||||
super.setImageURI(uri);
|
||||
if (attacher != null) {
|
||||
attacher.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean setFrame(int l, int t, int r, int b) {
|
||||
boolean changed = super.setFrame(l, t, r, b);
|
||||
if (changed) {
|
||||
attacher.update();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void setRotationTo(float rotationDegree) {
|
||||
attacher.setRotationTo(rotationDegree);
|
||||
}
|
||||
|
||||
public void setRotationBy(float rotationDegree) {
|
||||
attacher.setRotationBy(rotationDegree);
|
||||
}
|
||||
|
||||
public boolean isZoomable() {
|
||||
return attacher.isZoomable();
|
||||
}
|
||||
|
||||
public void setZoomable(boolean zoomable) {
|
||||
attacher.setZoomable(zoomable);
|
||||
}
|
||||
|
||||
public RectF getDisplayRect() {
|
||||
return attacher.getDisplayRect();
|
||||
}
|
||||
|
||||
public void getDisplayMatrix(Matrix matrix) {
|
||||
attacher.getDisplayMatrix(matrix);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue") public boolean setDisplayMatrix(Matrix finalRectangle) {
|
||||
return attacher.setDisplayMatrix(finalRectangle);
|
||||
}
|
||||
|
||||
public void getSuppMatrix(Matrix matrix) {
|
||||
attacher.getSuppMatrix(matrix);
|
||||
}
|
||||
|
||||
public boolean setSuppMatrix(Matrix matrix) {
|
||||
return attacher.setDisplayMatrix(matrix);
|
||||
}
|
||||
|
||||
public float getMinimumScale() {
|
||||
return attacher.getMinimumScale();
|
||||
}
|
||||
|
||||
public float getMediumScale() {
|
||||
return attacher.getMediumScale();
|
||||
}
|
||||
|
||||
public float getMaximumScale() {
|
||||
return attacher.getMaximumScale();
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return attacher.getScale();
|
||||
}
|
||||
|
||||
public void setAllowParentInterceptOnEdge(boolean allow) {
|
||||
attacher.setAllowParentInterceptOnEdge(allow);
|
||||
}
|
||||
|
||||
public void setMinimumScale(float minimumScale) {
|
||||
attacher.setMinimumScale(minimumScale);
|
||||
}
|
||||
|
||||
public void setMediumScale(float mediumScale) {
|
||||
attacher.setMediumScale(mediumScale);
|
||||
}
|
||||
|
||||
public void setMaximumScale(float maximumScale) {
|
||||
attacher.setMaximumScale(maximumScale);
|
||||
}
|
||||
|
||||
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
|
||||
attacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
|
||||
}
|
||||
|
||||
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
|
||||
attacher.setOnMatrixChangeListener(listener);
|
||||
}
|
||||
|
||||
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
|
||||
attacher.setOnPhotoTapListener(listener);
|
||||
}
|
||||
|
||||
public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener listener) {
|
||||
attacher.setOnOutsidePhotoTapListener(listener);
|
||||
}
|
||||
|
||||
public void setOnViewTapListener(OnViewTapListener listener) {
|
||||
attacher.setOnViewTapListener(listener);
|
||||
}
|
||||
|
||||
public void setOnViewDragListener(OnViewDragListener listener) {
|
||||
attacher.setOnViewDragListener(listener);
|
||||
}
|
||||
|
||||
public void setScale(float scale) {
|
||||
attacher.setScale(scale);
|
||||
}
|
||||
|
||||
public void setScale(float scale, boolean animate) {
|
||||
attacher.setScale(scale, animate);
|
||||
}
|
||||
|
||||
public void setScale(float scale, float focalX, float focalY, boolean animate) {
|
||||
attacher.setScale(scale, focalX, focalY, animate);
|
||||
}
|
||||
|
||||
public void setZoomTransitionDuration(int milliseconds) {
|
||||
attacher.setZoomTransitionDuration(milliseconds);
|
||||
}
|
||||
|
||||
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) {
|
||||
attacher.setOnDoubleTapListener(onDoubleTapListener);
|
||||
}
|
||||
|
||||
public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangedListener) {
|
||||
attacher.setOnScaleChangeListener(onScaleChangedListener);
|
||||
}
|
||||
|
||||
public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
|
||||
attacher.setOnSingleFlingListener(onSingleFlingListener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,823 @@
|
|||
/*
|
||||
Copyright 2011, 2012 Chris Banes.
|
||||
<p>
|
||||
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
|
||||
<p>
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
<p>
|
||||
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 com.github.chrisbanes.photoview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Matrix.ScaleToFit;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnLongClickListener;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
/**
|
||||
* The component of {@link PhotoView} which does the work allowing for zooming, scaling, panning, etc.
|
||||
* It is made public in case you need to subclass something other than AppCompatImageView and still
|
||||
* gain the functionality that {@link PhotoView} offers
|
||||
*/
|
||||
public class PhotoViewAttacher implements View.OnTouchListener,
|
||||
View.OnLayoutChangeListener {
|
||||
|
||||
private static float DEFAULT_MAX_SCALE = 3.0f;
|
||||
private static float DEFAULT_MID_SCALE = 1.75f;
|
||||
private static float DEFAULT_MIN_SCALE = 1.0f;
|
||||
private static int DEFAULT_ZOOM_DURATION = 200;
|
||||
|
||||
private static final int HORIZONTAL_EDGE_NONE = -1;
|
||||
private static final int HORIZONTAL_EDGE_LEFT = 0;
|
||||
private static final int HORIZONTAL_EDGE_RIGHT = 1;
|
||||
private static final int HORIZONTAL_EDGE_BOTH = 2;
|
||||
private static final int VERTICAL_EDGE_NONE = -1;
|
||||
private static final int VERTICAL_EDGE_TOP = 0;
|
||||
private static final int VERTICAL_EDGE_BOTTOM = 1;
|
||||
private static final int VERTICAL_EDGE_BOTH = 2;
|
||||
private static int SINGLE_TOUCH = 1;
|
||||
|
||||
private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
|
||||
private int mZoomDuration = DEFAULT_ZOOM_DURATION;
|
||||
private float mMinScale = DEFAULT_MIN_SCALE;
|
||||
private float mMidScale = DEFAULT_MID_SCALE;
|
||||
private float mMaxScale = DEFAULT_MAX_SCALE;
|
||||
|
||||
private boolean mAllowParentInterceptOnEdge = true;
|
||||
private boolean mBlockParentIntercept = false;
|
||||
|
||||
private ImageView mImageView;
|
||||
|
||||
// Gesture Detectors
|
||||
private GestureDetector mGestureDetector;
|
||||
private CustomGestureDetector mScaleDragDetector;
|
||||
|
||||
// These are set so we don't keep allocating them on the heap
|
||||
private final Matrix mBaseMatrix = new Matrix();
|
||||
private final Matrix mDrawMatrix = new Matrix();
|
||||
private final Matrix mSuppMatrix = new Matrix();
|
||||
private final RectF mDisplayRect = new RectF();
|
||||
private final float[] mMatrixValues = new float[9];
|
||||
|
||||
// Listeners
|
||||
private OnMatrixChangedListener mMatrixChangeListener;
|
||||
private OnPhotoTapListener mPhotoTapListener;
|
||||
private OnOutsidePhotoTapListener mOutsidePhotoTapListener;
|
||||
private OnViewTapListener mViewTapListener;
|
||||
private View.OnClickListener mOnClickListener;
|
||||
private OnLongClickListener mLongClickListener;
|
||||
private OnScaleChangedListener mScaleChangeListener;
|
||||
private OnSingleFlingListener mSingleFlingListener;
|
||||
private OnViewDragListener mOnViewDragListener;
|
||||
|
||||
private FlingRunnable mCurrentFlingRunnable;
|
||||
private int mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
|
||||
private int mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
|
||||
private float mBaseRotation;
|
||||
|
||||
private boolean mZoomEnabled = true;
|
||||
private ScaleType mScaleType = ScaleType.FIT_CENTER;
|
||||
|
||||
private OnGestureListener onGestureListener = new OnGestureListener() {
|
||||
@Override
|
||||
public void onDrag(float dx, float dy) {
|
||||
if (mScaleDragDetector.isScaling()) {
|
||||
return; // Do not drag if we are already scaling
|
||||
}
|
||||
if (mOnViewDragListener != null) {
|
||||
mOnViewDragListener.onDrag(dx, dy);
|
||||
}
|
||||
mSuppMatrix.postTranslate(dx, dy);
|
||||
checkAndDisplayMatrix();
|
||||
|
||||
/*
|
||||
* Here we decide whether to let the ImageView's parent to start taking
|
||||
* over the touch event.
|
||||
*
|
||||
* First we check whether this function is enabled. We never want the
|
||||
* parent to take over if we're scaling. We then check the edge we're
|
||||
* on, and the direction of the scroll (i.e. if we're pulling against
|
||||
* the edge, aka 'overscrolling', let the parent take over).
|
||||
*/
|
||||
ViewParent parent = mImageView.getParent();
|
||||
if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
|
||||
if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH
|
||||
|| (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f)
|
||||
|| (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f)
|
||||
|| (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f)
|
||||
|| (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) {
|
||||
if (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFling(float startX, float startY, float velocityX, float velocityY) {
|
||||
mCurrentFlingRunnable = new FlingRunnable(mImageView.getContext());
|
||||
mCurrentFlingRunnable.fling(getImageViewWidth(mImageView),
|
||||
getImageViewHeight(mImageView), (int) velocityX, (int) velocityY);
|
||||
mImageView.post(mCurrentFlingRunnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScale(float scaleFactor, float focusX, float focusY) {
|
||||
onScale(scaleFactor, focusX, focusY, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy) {
|
||||
if (getScale() < mMaxScale || scaleFactor < 1f) {
|
||||
if (mScaleChangeListener != null) {
|
||||
mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
|
||||
}
|
||||
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
|
||||
mSuppMatrix.postTranslate(dx, dy);
|
||||
checkAndDisplayMatrix();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PhotoViewAttacher(ImageView imageView) {
|
||||
mImageView = imageView;
|
||||
imageView.setOnTouchListener(this);
|
||||
imageView.addOnLayoutChangeListener(this);
|
||||
if (imageView.isInEditMode()) {
|
||||
return;
|
||||
}
|
||||
mBaseRotation = 0.0f;
|
||||
// Create Gesture Detectors...
|
||||
mScaleDragDetector = new CustomGestureDetector(imageView.getContext(), onGestureListener);
|
||||
mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||
|
||||
// forward long click listener
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
if (mLongClickListener != null) {
|
||||
mLongClickListener.onLongClick(mImageView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2,
|
||||
float velocityX, float velocityY) {
|
||||
if (mSingleFlingListener != null) {
|
||||
if (getScale() > DEFAULT_MIN_SCALE) {
|
||||
return false;
|
||||
}
|
||||
if (e1.getPointerCount() > SINGLE_TOUCH
|
||||
|| e2.getPointerCount() > SINGLE_TOUCH) {
|
||||
return false;
|
||||
}
|
||||
return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
if (mOnClickListener != null) {
|
||||
mOnClickListener.onClick(mImageView);
|
||||
}
|
||||
final RectF displayRect = getDisplayRect();
|
||||
final float x = e.getX(), y = e.getY();
|
||||
if (mViewTapListener != null) {
|
||||
mViewTapListener.onViewTap(mImageView, x, y);
|
||||
}
|
||||
if (displayRect != null) {
|
||||
// Check to see if the user tapped on the photo
|
||||
if (displayRect.contains(x, y)) {
|
||||
float xResult = (x - displayRect.left)
|
||||
/ displayRect.width();
|
||||
float yResult = (y - displayRect.top)
|
||||
/ displayRect.height();
|
||||
if (mPhotoTapListener != null) {
|
||||
mPhotoTapListener.onPhotoTap(mImageView, xResult, yResult);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (mOutsidePhotoTapListener != null) {
|
||||
mOutsidePhotoTapListener.onOutsidePhotoTap(mImageView);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent ev) {
|
||||
try {
|
||||
float scale = getScale();
|
||||
float x = ev.getX();
|
||||
float y = ev.getY();
|
||||
if (scale < getMediumScale()) {
|
||||
setScale(getMediumScale(), x, y, true);
|
||||
} else if (scale >= getMediumScale() && scale < getMaximumScale()) {
|
||||
setScale(getMaximumScale(), x, y, true);
|
||||
} else {
|
||||
setScale(getMinimumScale(), x, y, true);
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Can sometimes happen when getX() and getY() is called
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
// Wait for the confirmed onDoubleTap() instead
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
|
||||
this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
|
||||
}
|
||||
|
||||
public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangeListener) {
|
||||
this.mScaleChangeListener = onScaleChangeListener;
|
||||
}
|
||||
|
||||
public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
|
||||
this.mSingleFlingListener = onSingleFlingListener;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean isZoomEnabled() {
|
||||
return mZoomEnabled;
|
||||
}
|
||||
|
||||
public RectF getDisplayRect() {
|
||||
checkMatrixBounds();
|
||||
return getDisplayRect(getDrawMatrix());
|
||||
}
|
||||
|
||||
public boolean setDisplayMatrix(Matrix finalMatrix) {
|
||||
if (finalMatrix == null) {
|
||||
throw new IllegalArgumentException("Matrix cannot be null");
|
||||
}
|
||||
if (mImageView.getDrawable() == null) {
|
||||
return false;
|
||||
}
|
||||
mSuppMatrix.set(finalMatrix);
|
||||
checkAndDisplayMatrix();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setBaseRotation(final float degrees) {
|
||||
mBaseRotation = degrees % 360;
|
||||
update();
|
||||
setRotationBy(mBaseRotation);
|
||||
checkAndDisplayMatrix();
|
||||
}
|
||||
|
||||
public void setRotationTo(float degrees) {
|
||||
mSuppMatrix.setRotate(degrees % 360);
|
||||
checkAndDisplayMatrix();
|
||||
}
|
||||
|
||||
public void setRotationBy(float degrees) {
|
||||
mSuppMatrix.postRotate(degrees % 360);
|
||||
checkAndDisplayMatrix();
|
||||
}
|
||||
|
||||
public float getMinimumScale() {
|
||||
return mMinScale;
|
||||
}
|
||||
|
||||
public float getMediumScale() {
|
||||
return mMidScale;
|
||||
}
|
||||
|
||||
public float getMaximumScale() {
|
||||
return mMaxScale;
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow
|
||||
(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
|
||||
}
|
||||
|
||||
public ScaleType getScaleType() {
|
||||
return mScaleType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int
|
||||
oldRight, int oldBottom) {
|
||||
// Update our base matrix, as the bounds have changed
|
||||
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
|
||||
updateBaseMatrix(mImageView.getDrawable());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent ev) {
|
||||
boolean handled = false;
|
||||
if (mZoomEnabled && Util.hasDrawable((ImageView) v)) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
ViewParent parent = v.getParent();
|
||||
// First, disable the Parent from intercepting the touch
|
||||
// event
|
||||
if (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
// If we're flinging, and the user presses down, cancel
|
||||
// fling
|
||||
cancelFling();
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
// If the user has zoomed less than min scale, zoom back
|
||||
// to min scale
|
||||
if (getScale() < mMinScale) {
|
||||
RectF rect = getDisplayRect();
|
||||
if (rect != null) {
|
||||
v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
|
||||
rect.centerX(), rect.centerY()));
|
||||
handled = true;
|
||||
}
|
||||
} else if (getScale() > mMaxScale) {
|
||||
RectF rect = getDisplayRect();
|
||||
if (rect != null) {
|
||||
v.post(new AnimatedZoomRunnable(getScale(), mMaxScale,
|
||||
rect.centerX(), rect.centerY()));
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Try the Scale/Drag detector
|
||||
if (mScaleDragDetector != null) {
|
||||
boolean wasScaling = mScaleDragDetector.isScaling();
|
||||
boolean wasDragging = mScaleDragDetector.isDragging();
|
||||
handled = mScaleDragDetector.onTouchEvent(ev);
|
||||
boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
|
||||
boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
|
||||
mBlockParentIntercept = didntScale && didntDrag;
|
||||
}
|
||||
// Check to see if the user double tapped
|
||||
if (mGestureDetector != null && mGestureDetector.onTouchEvent(ev)) {
|
||||
handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
public void setAllowParentInterceptOnEdge(boolean allow) {
|
||||
mAllowParentInterceptOnEdge = allow;
|
||||
}
|
||||
|
||||
public void setMinimumScale(float minimumScale) {
|
||||
Util.checkZoomLevels(minimumScale, mMidScale, mMaxScale);
|
||||
mMinScale = minimumScale;
|
||||
}
|
||||
|
||||
public void setMediumScale(float mediumScale) {
|
||||
Util.checkZoomLevels(mMinScale, mediumScale, mMaxScale);
|
||||
mMidScale = mediumScale;
|
||||
}
|
||||
|
||||
public void setMaximumScale(float maximumScale) {
|
||||
Util.checkZoomLevels(mMinScale, mMidScale, maximumScale);
|
||||
mMaxScale = maximumScale;
|
||||
}
|
||||
|
||||
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
|
||||
Util.checkZoomLevels(minimumScale, mediumScale, maximumScale);
|
||||
mMinScale = minimumScale;
|
||||
mMidScale = mediumScale;
|
||||
mMaxScale = maximumScale;
|
||||
}
|
||||
|
||||
public void setOnLongClickListener(OnLongClickListener listener) {
|
||||
mLongClickListener = listener;
|
||||
}
|
||||
|
||||
public void setOnClickListener(View.OnClickListener listener) {
|
||||
mOnClickListener = listener;
|
||||
}
|
||||
|
||||
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
|
||||
mMatrixChangeListener = listener;
|
||||
}
|
||||
|
||||
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
|
||||
mPhotoTapListener = listener;
|
||||
}
|
||||
|
||||
public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener mOutsidePhotoTapListener) {
|
||||
this.mOutsidePhotoTapListener = mOutsidePhotoTapListener;
|
||||
}
|
||||
|
||||
public void setOnViewTapListener(OnViewTapListener listener) {
|
||||
mViewTapListener = listener;
|
||||
}
|
||||
|
||||
public void setOnViewDragListener(OnViewDragListener listener) {
|
||||
mOnViewDragListener = listener;
|
||||
}
|
||||
|
||||
public void setScale(float scale) {
|
||||
setScale(scale, false);
|
||||
}
|
||||
|
||||
public void setScale(float scale, boolean animate) {
|
||||
setScale(scale,
|
||||
(mImageView.getRight()) / 2,
|
||||
(mImageView.getBottom()) / 2,
|
||||
animate);
|
||||
}
|
||||
|
||||
public void setScale(float scale, float focalX, float focalY,
|
||||
boolean animate) {
|
||||
// Check to see if the scale is within bounds
|
||||
if (scale < mMinScale || scale > mMaxScale) {
|
||||
throw new IllegalArgumentException("Scale must be within the range of minScale and maxScale");
|
||||
}
|
||||
if (animate) {
|
||||
mImageView.post(new AnimatedZoomRunnable(getScale(), scale,
|
||||
focalX, focalY));
|
||||
} else {
|
||||
mSuppMatrix.setScale(scale, scale, focalX, focalY);
|
||||
checkAndDisplayMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zoom interpolator
|
||||
*
|
||||
* @param interpolator the zoom interpolator
|
||||
*/
|
||||
public void setZoomInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public void setScaleType(ScaleType scaleType) {
|
||||
if (Util.isSupportedScaleType(scaleType) && scaleType != mScaleType) {
|
||||
mScaleType = scaleType;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isZoomable() {
|
||||
return mZoomEnabled;
|
||||
}
|
||||
|
||||
public void setZoomable(boolean zoomable) {
|
||||
mZoomEnabled = zoomable;
|
||||
update();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (mZoomEnabled) {
|
||||
// Update the base matrix using the current drawable
|
||||
updateBaseMatrix(mImageView.getDrawable());
|
||||
} else {
|
||||
// Reset the Matrix...
|
||||
resetMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display matrix
|
||||
*
|
||||
* @param matrix target matrix to copy to
|
||||
*/
|
||||
public void getDisplayMatrix(Matrix matrix) {
|
||||
matrix.set(getDrawMatrix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current support matrix
|
||||
*/
|
||||
public void getSuppMatrix(Matrix matrix) {
|
||||
matrix.set(mSuppMatrix);
|
||||
}
|
||||
|
||||
private Matrix getDrawMatrix() {
|
||||
mDrawMatrix.set(mBaseMatrix);
|
||||
mDrawMatrix.postConcat(mSuppMatrix);
|
||||
return mDrawMatrix;
|
||||
}
|
||||
|
||||
public Matrix getImageMatrix() {
|
||||
return mDrawMatrix;
|
||||
}
|
||||
|
||||
public void setZoomTransitionDuration(int milliseconds) {
|
||||
this.mZoomDuration = milliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that 'unpacks' a Matrix and returns the required value
|
||||
*
|
||||
* @param matrix Matrix to unpack
|
||||
* @param whichValue Which value from Matrix.M* to return
|
||||
* @return returned value
|
||||
*/
|
||||
private float getValue(Matrix matrix, int whichValue) {
|
||||
matrix.getValues(mMatrixValues);
|
||||
return mMatrixValues[whichValue];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the Matrix back to FIT_CENTER, and then displays its contents
|
||||
*/
|
||||
private void resetMatrix() {
|
||||
mSuppMatrix.reset();
|
||||
setRotationBy(mBaseRotation);
|
||||
setImageViewMatrix(getDrawMatrix());
|
||||
checkMatrixBounds();
|
||||
}
|
||||
|
||||
private void setImageViewMatrix(Matrix matrix) {
|
||||
mImageView.setImageMatrix(matrix);
|
||||
// Call MatrixChangedListener if needed
|
||||
if (mMatrixChangeListener != null) {
|
||||
RectF displayRect = getDisplayRect(matrix);
|
||||
if (displayRect != null) {
|
||||
mMatrixChangeListener.onMatrixChanged(displayRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that simply checks the Matrix, and then displays the result
|
||||
*/
|
||||
private void checkAndDisplayMatrix() {
|
||||
if (checkMatrixBounds()) {
|
||||
setImageViewMatrix(getDrawMatrix());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that maps the supplied Matrix to the current Drawable
|
||||
*
|
||||
* @param matrix - Matrix to map Drawable against
|
||||
* @return RectF - Displayed Rectangle
|
||||
*/
|
||||
private RectF getDisplayRect(Matrix matrix) {
|
||||
Drawable d = mImageView.getDrawable();
|
||||
if (d != null) {
|
||||
mDisplayRect.set(0, 0, d.getIntrinsicWidth(),
|
||||
d.getIntrinsicHeight());
|
||||
matrix.mapRect(mDisplayRect);
|
||||
return mDisplayRect;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Matrix for FIT_CENTER
|
||||
*
|
||||
* @param drawable - Drawable being displayed
|
||||
*/
|
||||
private void updateBaseMatrix(Drawable drawable) {
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
final float viewWidth = getImageViewWidth(mImageView);
|
||||
final float viewHeight = getImageViewHeight(mImageView);
|
||||
final int drawableWidth = drawable.getIntrinsicWidth();
|
||||
final int drawableHeight = drawable.getIntrinsicHeight();
|
||||
mBaseMatrix.reset();
|
||||
final float widthScale = viewWidth / drawableWidth;
|
||||
final float heightScale = viewHeight / drawableHeight;
|
||||
if (mScaleType == ScaleType.CENTER) {
|
||||
mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
|
||||
(viewHeight - drawableHeight) / 2F);
|
||||
|
||||
} else if (mScaleType == ScaleType.CENTER_CROP) {
|
||||
float scale = Math.max(widthScale, heightScale);
|
||||
mBaseMatrix.postScale(scale, scale);
|
||||
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
|
||||
(viewHeight - drawableHeight * scale) / 2F);
|
||||
|
||||
} else if (mScaleType == ScaleType.CENTER_INSIDE) {
|
||||
float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
|
||||
mBaseMatrix.postScale(scale, scale);
|
||||
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
|
||||
(viewHeight - drawableHeight * scale) / 2F);
|
||||
|
||||
} else {
|
||||
RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
|
||||
RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
|
||||
if ((int) mBaseRotation % 180 != 0) {
|
||||
mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth);
|
||||
}
|
||||
switch (mScaleType) {
|
||||
case FIT_CENTER:
|
||||
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
|
||||
break;
|
||||
case FIT_START:
|
||||
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
|
||||
break;
|
||||
case FIT_END:
|
||||
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
|
||||
break;
|
||||
case FIT_XY:
|
||||
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
resetMatrix();
|
||||
}
|
||||
|
||||
private boolean checkMatrixBounds() {
|
||||
final RectF rect = getDisplayRect(getDrawMatrix());
|
||||
if (rect == null) {
|
||||
return false;
|
||||
}
|
||||
final float height = rect.height(), width = rect.width();
|
||||
float deltaX = 0, deltaY = 0;
|
||||
final int viewHeight = getImageViewHeight(mImageView);
|
||||
if (height <= viewHeight) {
|
||||
switch (mScaleType) {
|
||||
case FIT_START:
|
||||
deltaY = -rect.top;
|
||||
break;
|
||||
case FIT_END:
|
||||
deltaY = viewHeight - height - rect.top;
|
||||
break;
|
||||
default:
|
||||
deltaY = (viewHeight - height) / 2 - rect.top;
|
||||
break;
|
||||
}
|
||||
mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
|
||||
} else if (rect.top > 0) {
|
||||
mVerticalScrollEdge = VERTICAL_EDGE_TOP;
|
||||
deltaY = -rect.top;
|
||||
} else if (rect.bottom < viewHeight) {
|
||||
mVerticalScrollEdge = VERTICAL_EDGE_BOTTOM;
|
||||
deltaY = viewHeight - rect.bottom;
|
||||
} else {
|
||||
mVerticalScrollEdge = VERTICAL_EDGE_NONE;
|
||||
}
|
||||
final int viewWidth = getImageViewWidth(mImageView);
|
||||
if (width <= viewWidth) {
|
||||
switch (mScaleType) {
|
||||
case FIT_START:
|
||||
deltaX = -rect.left;
|
||||
break;
|
||||
case FIT_END:
|
||||
deltaX = viewWidth - width - rect.left;
|
||||
break;
|
||||
default:
|
||||
deltaX = (viewWidth - width) / 2 - rect.left;
|
||||
break;
|
||||
}
|
||||
mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
|
||||
} else if (rect.left > 0) {
|
||||
mHorizontalScrollEdge = HORIZONTAL_EDGE_LEFT;
|
||||
deltaX = -rect.left;
|
||||
} else if (rect.right < viewWidth) {
|
||||
deltaX = viewWidth - rect.right;
|
||||
mHorizontalScrollEdge = HORIZONTAL_EDGE_RIGHT;
|
||||
} else {
|
||||
mHorizontalScrollEdge = HORIZONTAL_EDGE_NONE;
|
||||
}
|
||||
// Finally actually translate the matrix
|
||||
mSuppMatrix.postTranslate(deltaX, deltaY);
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getImageViewWidth(ImageView imageView) {
|
||||
return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
|
||||
}
|
||||
|
||||
private int getImageViewHeight(ImageView imageView) {
|
||||
return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
|
||||
}
|
||||
|
||||
private void cancelFling() {
|
||||
if (mCurrentFlingRunnable != null) {
|
||||
mCurrentFlingRunnable.cancelFling();
|
||||
mCurrentFlingRunnable = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimatedZoomRunnable implements Runnable {
|
||||
|
||||
private final float mFocalX, mFocalY;
|
||||
private final long mStartTime;
|
||||
private final float mZoomStart, mZoomEnd;
|
||||
|
||||
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom,
|
||||
final float focalX, final float focalY) {
|
||||
mFocalX = focalX;
|
||||
mFocalY = focalY;
|
||||
mStartTime = System.currentTimeMillis();
|
||||
mZoomStart = currentZoom;
|
||||
mZoomEnd = targetZoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
float t = interpolate();
|
||||
float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
|
||||
float deltaScale = scale / getScale();
|
||||
onGestureListener.onScale(deltaScale, mFocalX, mFocalY);
|
||||
// We haven't hit our target scale yet, so post ourselves again
|
||||
if (t < 1f) {
|
||||
Compat.postOnAnimation(mImageView, this);
|
||||
}
|
||||
}
|
||||
|
||||
private float interpolate() {
|
||||
float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration;
|
||||
t = Math.min(1f, t);
|
||||
t = mInterpolator.getInterpolation(t);
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlingRunnable implements Runnable {
|
||||
|
||||
private final OverScroller mScroller;
|
||||
private int mCurrentX, mCurrentY;
|
||||
|
||||
public FlingRunnable(Context context) {
|
||||
mScroller = new OverScroller(context);
|
||||
}
|
||||
|
||||
public void cancelFling() {
|
||||
mScroller.forceFinished(true);
|
||||
}
|
||||
|
||||
public void fling(int viewWidth, int viewHeight, int velocityX,
|
||||
int velocityY) {
|
||||
final RectF rect = getDisplayRect();
|
||||
if (rect == null) {
|
||||
return;
|
||||
}
|
||||
final int startX = Math.round(-rect.left);
|
||||
final int minX, maxX, minY, maxY;
|
||||
if (viewWidth < rect.width()) {
|
||||
minX = 0;
|
||||
maxX = Math.round(rect.width() - viewWidth);
|
||||
} else {
|
||||
minX = maxX = startX;
|
||||
}
|
||||
final int startY = Math.round(-rect.top);
|
||||
if (viewHeight < rect.height()) {
|
||||
minY = 0;
|
||||
maxY = Math.round(rect.height() - viewHeight);
|
||||
} else {
|
||||
minY = maxY = startY;
|
||||
}
|
||||
mCurrentX = startX;
|
||||
mCurrentY = startY;
|
||||
// If we actually can move, fling the scroller
|
||||
if (startX != maxX || startY != maxY) {
|
||||
mScroller.fling(startX, startY, velocityX, velocityY, minX,
|
||||
maxX, minY, maxY, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mScroller.isFinished()) {
|
||||
return; // remaining post that should not be handled
|
||||
}
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
final int newX = mScroller.getCurrX();
|
||||
final int newY = mScroller.getCurrY();
|
||||
mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
|
||||
checkAndDisplayMatrix();
|
||||
mCurrentX = newX;
|
||||
mCurrentY = newY;
|
||||
// Post On animation
|
||||
Compat.postOnAnimation(mImageView, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package com.github.chrisbanes.photoview;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.ImageView;
|
||||
|
||||
class Util {
|
||||
|
||||
static void checkZoomLevels(float minZoom, float midZoom,
|
||||
float maxZoom) {
|
||||
if (minZoom >= midZoom) {
|
||||
throw new IllegalArgumentException(
|
||||
"Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value");
|
||||
} else if (midZoom >= maxZoom) {
|
||||
throw new IllegalArgumentException(
|
||||
"Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value");
|
||||
}
|
||||
}
|
||||
|
||||
static boolean hasDrawable(ImageView imageView) {
|
||||
return imageView.getDrawable() != null;
|
||||
}
|
||||
|
||||
static boolean isSupportedScaleType(final ImageView.ScaleType scaleType) {
|
||||
if (scaleType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (scaleType) {
|
||||
case MATRIX:
|
||||
throw new IllegalStateException("Matrix scale type is not supported");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int getPointerIndex(int action) {
|
||||
return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ include ':contacts'
|
|||
include ':contacts-app'
|
||||
include ':qr'
|
||||
include ':qr-app'
|
||||
include ':sticky-header-grid'
|
||||
include ':photoview'
|
||||
|
||||
project(':app').name = 'Signal-Android'
|
||||
project(':paging').projectDir = file('paging/lib')
|
||||
|
|
2
sticky-header-grid/README.md
Normal file
2
sticky-header-grid/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
This was migrated from [Codewaves/Sticky-Header-Grid](https://github.com/Codewaves/Sticky-Header-Grid), because the artifact is still on published on jcenter.
|
||||
The project was small enough that it was easier to just bring the two files in as a source dependency.
|
62
sticky-header-grid/build.gradle
Normal file
62
sticky-header-grid/build.gradle
Normal file
|
@ -0,0 +1,62 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'com.google.protobuf'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
buildToolsVersion BUILD_TOOL_VERSION
|
||||
compileSdkVersion COMPILE_SDK
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion MINIMUM_SDK
|
||||
targetSdkVersion TARGET_SDK
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JAVA_VERSION
|
||||
targetCompatibility JAVA_VERSION
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = 'com.google.protobuf:protoc:3.18.0'
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
task.builtins {
|
||||
java {
|
||||
option "lite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
lintChecks project(':lintchecks')
|
||||
|
||||
coreLibraryDesugaring libs.android.tools.desugar
|
||||
|
||||
api libs.androidx.annotation
|
||||
|
||||
implementation libs.androidx.core.ktx
|
||||
implementation libs.androidx.lifecycle.common.java8
|
||||
implementation libs.google.protobuf.javalite
|
||||
implementation libs.androidx.sqlite
|
||||
implementation libs.rxjava3.rxjava
|
||||
|
||||
testImplementation testLibs.junit.junit
|
||||
testImplementation testLibs.mockito.core
|
||||
testImplementation (testLibs.robolectric.robolectric) {
|
||||
exclude group: 'com.google.protobuf', module: 'protobuf-java'
|
||||
}
|
||||
}
|
6
sticky-header-grid/src/main/AndroidManifest.xml
Normal file
6
sticky-header-grid/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.codewaves.stickyheadergrid">
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,613 @@
|
|||
package com.codewaves.stickyheadergrid;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Sergej Kravcenko on 4/24/2017.
|
||||
* Copyright (c) 2017 Sergej Kravcenko
|
||||
*/
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public abstract class StickyHeaderGridAdapter extends RecyclerView.Adapter<StickyHeaderGridAdapter.ViewHolder> {
|
||||
public static final String TAG = "StickyHeaderGridAdapter";
|
||||
|
||||
public static final int TYPE_HEADER = 0;
|
||||
public static final int TYPE_ITEM = 1;
|
||||
|
||||
private ArrayList<Section> mSections;
|
||||
private int[] mSectionIndices;
|
||||
private int mTotalItemNumber;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public boolean isHeader() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public int getSectionItemViewType() {
|
||||
return StickyHeaderGridAdapter.externalViewType(getItemViewType());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemViewHolder extends ViewHolder {
|
||||
public ItemViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HeaderViewHolder extends ViewHolder {
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHeader() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Section {
|
||||
private int position;
|
||||
private int itemNumber;
|
||||
private int length;
|
||||
}
|
||||
|
||||
private void calculateSections() {
|
||||
mSections = new ArrayList<>();
|
||||
|
||||
int total = 0;
|
||||
int sectionCount = getSectionCount();
|
||||
for (int s = 0; s < sectionCount; s++) {
|
||||
final Section section = new Section();
|
||||
section.position = total;
|
||||
section.itemNumber = getSectionItemCount(s);
|
||||
section.length = section.itemNumber + 1;
|
||||
mSections.add(section);
|
||||
|
||||
total += section.length;
|
||||
}
|
||||
mTotalItemNumber = total;
|
||||
|
||||
total = 0;
|
||||
mSectionIndices = new int[mTotalItemNumber];
|
||||
for (int s = 0; s < sectionCount; s++) {
|
||||
final Section section = mSections.get(s);
|
||||
for (int i = 0; i < section.length; i++) {
|
||||
mSectionIndices[total + i] = s;
|
||||
}
|
||||
total += section.length;
|
||||
}
|
||||
}
|
||||
|
||||
protected int getItemViewInternalType(int position) {
|
||||
final int section = getAdapterPositionSection(position);
|
||||
final Section sectionObject = mSections.get(section);
|
||||
final int sectionPosition = position - sectionObject.position;
|
||||
|
||||
return getItemViewInternalType(section, sectionPosition);
|
||||
}
|
||||
|
||||
private int getItemViewInternalType(int section, int position) {
|
||||
return position == 0 ? TYPE_HEADER : TYPE_ITEM;
|
||||
}
|
||||
|
||||
static private int internalViewType(int type) {
|
||||
return type & 0xFF;
|
||||
}
|
||||
|
||||
static private int externalViewType(int type) {
|
||||
return type >> 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemCount() {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
return mTotalItemNumber;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
final public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
final int internalType = internalViewType(viewType);
|
||||
final int externalType = externalViewType(viewType);
|
||||
|
||||
switch (internalType) {
|
||||
case TYPE_HEADER:
|
||||
return onCreateHeaderViewHolder(parent, externalType);
|
||||
case TYPE_ITEM:
|
||||
return onCreateItemViewHolder(parent, externalType);
|
||||
default:
|
||||
throw new InvalidParameterException("Invalid viewType: " + viewType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
|
||||
final int section = mSectionIndices[position];
|
||||
final int internalType = internalViewType(holder.getItemViewType());
|
||||
final int externalType = externalViewType(holder.getItemViewType());
|
||||
|
||||
switch (internalType) {
|
||||
case TYPE_HEADER:
|
||||
onBindHeaderViewHolder((HeaderViewHolder)holder, section);
|
||||
break;
|
||||
case TYPE_ITEM:
|
||||
final ItemViewHolder itemHolder = (ItemViewHolder)holder;
|
||||
final int offset = getItemSectionOffset(section, position);
|
||||
onBindItemViewHolder((ItemViewHolder)holder, section, offset);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidParameterException("invalid viewType: " + internalType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemViewType(int position) {
|
||||
final int section = getAdapterPositionSection(position);
|
||||
final Section sectionObject = mSections.get(section);
|
||||
final int sectionPosition = position - sectionObject.position;
|
||||
final int internalType = getItemViewInternalType(section, sectionPosition);
|
||||
int externalType = 0;
|
||||
|
||||
switch (internalType) {
|
||||
case TYPE_HEADER:
|
||||
externalType = getSectionHeaderViewType(section);
|
||||
break;
|
||||
case TYPE_ITEM:
|
||||
externalType = getSectionItemViewType(section, sectionPosition - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
return ((externalType & 0xFF) << 8) | (internalType & 0xFF);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
private int getItemSectionHeaderPosition(int position) {
|
||||
return getSectionHeaderPosition(getAdapterPositionSection(position));
|
||||
}
|
||||
|
||||
private int getAdapterPosition(int section, int offset) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
|
||||
if (section < 0) {
|
||||
throw new IndexOutOfBoundsException("section " + section + " < 0");
|
||||
}
|
||||
|
||||
if (section >= mSections.size()) {
|
||||
throw new IndexOutOfBoundsException("section " + section + " >=" + mSections.size());
|
||||
}
|
||||
|
||||
final Section sectionObject = mSections.get(section);
|
||||
return sectionObject.position + offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a <code>section</code> and an adapter <code>position</code> get the offset of an item
|
||||
* inside <code>section</code>.
|
||||
*
|
||||
* @param section section to query
|
||||
* @param position adapter position
|
||||
* @return The item offset inside the section.
|
||||
*/
|
||||
public int getItemSectionOffset(int section, int position) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
|
||||
if (section < 0) {
|
||||
throw new IndexOutOfBoundsException("section " + section + " < 0");
|
||||
}
|
||||
|
||||
if (section >= mSections.size()) {
|
||||
throw new IndexOutOfBoundsException("section " + section + " >=" + mSections.size());
|
||||
}
|
||||
|
||||
final Section sectionObject = mSections.get(section);
|
||||
final int localPosition = position - sectionObject.position;
|
||||
if (localPosition >= sectionObject.length) {
|
||||
throw new IndexOutOfBoundsException("localPosition: " + localPosition + " >=" + sectionObject.length);
|
||||
}
|
||||
|
||||
return localPosition - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section index having item or header with provided
|
||||
* provider <code>position</code>.
|
||||
*
|
||||
* @param position adapter position
|
||||
* @return The section containing provided adapter position.
|
||||
*/
|
||||
public int getAdapterPositionSection(int position) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
}
|
||||
|
||||
if (getItemCount() == 0) {
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
if (position < 0) {
|
||||
throw new IndexOutOfBoundsException("position " + position + " < 0");
|
||||
}
|
||||
|
||||
if (position >= getItemCount()) {
|
||||
throw new IndexOutOfBoundsException("position " + position + " >=" + getItemCount());
|
||||
}
|
||||
|
||||
return mSectionIndices[position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter position for given <code>section</code> header. Use
|
||||
* this only for {@link RecyclerView#scrollToPosition(int)} or similar functions.
|
||||
* Never directly manipulate adapter items using this position.
|
||||
*
|
||||
* @param section section to query
|
||||
* @return The adapter position.
|
||||
*/
|
||||
public int getSectionHeaderPosition(int section) {
|
||||
return getAdapterPosition(section, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter position for given <code>section</code> and
|
||||
* <code>offset</code>. Use this only for {@link RecyclerView#scrollToPosition(int)}
|
||||
* or similar functions. Never directly manipulate adapter items using this position.
|
||||
*
|
||||
* @param section section to query
|
||||
* @param position item position inside the <code>section</code>
|
||||
* @return The adapter position.
|
||||
*/
|
||||
public int getSectionItemPosition(int section, int position) {
|
||||
return getAdapterPosition(section, position + 1);
|
||||
}
|
||||
|
||||
// Overrides
|
||||
/**
|
||||
* Returns the total number of sections in the data set held by the adapter.
|
||||
*
|
||||
* @return The total number of section in this adapter.
|
||||
*/
|
||||
public int getSectionCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of items in the <code>section</code>.
|
||||
*
|
||||
* @param section section to query
|
||||
* @return The total number of items in the <code>section</code>.
|
||||
*/
|
||||
public int getSectionItemCount(int section) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the view type of the <code>section</code> header for the purposes
|
||||
* of view recycling.
|
||||
*
|
||||
* <p>The default implementation of this method returns 0, making the assumption of
|
||||
* a single view type for the headers. Unlike ListView adapters, types need not
|
||||
* be contiguous. Consider using id resources to uniquely identify item view types.
|
||||
*
|
||||
* @param section section to query
|
||||
* @return integer value identifying the type of the view needed to represent the header in
|
||||
* <code>section</code>. Type codes need not be contiguous.
|
||||
*/
|
||||
public int getSectionHeaderViewType(int section) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the view type of the item at <code>position</code> in <code>section</code> for
|
||||
* the purposes of view recycling.
|
||||
*
|
||||
* <p>The default implementation of this method returns 0, making the assumption of
|
||||
* a single view type for the adapter. Unlike ListView adapters, types need not
|
||||
* be contiguous. Consider using id resources to uniquely identify item view types.
|
||||
*
|
||||
* @param section section to query
|
||||
* @param offset section position to query
|
||||
* @return integer value identifying the type of the view needed to represent the item at
|
||||
* <code>position</code> in <code>section</code>. Type codes need not be
|
||||
* contiguous.
|
||||
*/
|
||||
public int getSectionItemViewType(int section, int offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if header in <code>section</code> is sticky.
|
||||
*
|
||||
* @param section section to query
|
||||
* @return true if <code>section</code> header is sticky.
|
||||
*/
|
||||
public boolean isSectionHeaderSticky(int section) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when RecyclerView needs a new {@link HeaderViewHolder} of the given type to represent
|
||||
* a header.
|
||||
* <p>
|
||||
* This new HeaderViewHolder should be constructed with a new View that can represent the headers
|
||||
* of the given type. You can either create a new View manually or inflate it from an XML
|
||||
* layout file.
|
||||
* <p>
|
||||
* The new HeaderViewHolder will be used to display items of the adapter using
|
||||
* {@link #onBindHeaderViewHolder(HeaderViewHolder, int)}. Since it will be re-used to display
|
||||
* different items in the data set, it is a good idea to cache references to sub views of
|
||||
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to
|
||||
* an adapter position.
|
||||
* @param headerType The view type of the new View.
|
||||
*
|
||||
* @return A new ViewHolder that holds a View of the given view type.
|
||||
* @see #getSectionHeaderViewType(int)
|
||||
* @see #onBindHeaderViewHolder(HeaderViewHolder, int)
|
||||
*/
|
||||
public abstract HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int headerType);
|
||||
|
||||
/**
|
||||
* Called when RecyclerView needs a new {@link ItemViewHolder} of the given type to represent
|
||||
* an item.
|
||||
* <p>
|
||||
* This new ViewHolder should be constructed with a new View that can represent the items
|
||||
* of the given type. You can either create a new View manually or inflate it from an XML
|
||||
* layout file.
|
||||
* <p>
|
||||
* The new ViewHolder will be used to display items of the adapter using
|
||||
* {@link #onBindItemViewHolder(ItemViewHolder, int, int)}. Since it will be re-used to display
|
||||
* different items in the data set, it is a good idea to cache references to sub views of
|
||||
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
|
||||
*
|
||||
* @param parent The ViewGroup into which the new View will be added after it is bound to
|
||||
* an adapter position.
|
||||
* @param itemType The view type of the new View.
|
||||
*
|
||||
* @return A new ViewHolder that holds a View of the given view type.
|
||||
* @see #getSectionItemViewType(int, int)
|
||||
* @see #onBindItemViewHolder(ItemViewHolder, int, int)
|
||||
*/
|
||||
public abstract ItemViewHolder onCreateItemViewHolder(ViewGroup parent, int itemType);
|
||||
|
||||
/**
|
||||
* Called by RecyclerView to display the data at the specified position. This method should
|
||||
* update the contents of the {@link HeaderViewHolder#itemView} to reflect the header at the given
|
||||
* position.
|
||||
* <p>
|
||||
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
|
||||
* again if the position of the header changes in the data set unless the header itself is
|
||||
* invalidated or the new position cannot be determined. For this reason, you should only
|
||||
* use the <code>section</code> parameter while acquiring the
|
||||
* related header data inside this method and should not keep a copy of it. If you need the
|
||||
* position of a header later on (e.g. in a click listener), use
|
||||
* {@link HeaderViewHolder#getAdapterPosition()} which will have the updated adapter
|
||||
* position. Then you can use {@link #getAdapterPositionSection(int)} to get section index.
|
||||
*
|
||||
*
|
||||
* @param viewHolder The ViewHolder which should be updated to represent the contents of the
|
||||
* header at the given position in the data set.
|
||||
* @param section The index of the section.
|
||||
*/
|
||||
public abstract void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int section);
|
||||
|
||||
/**
|
||||
* Called by RecyclerView to display the data at the specified position. This method should
|
||||
* update the contents of the {@link ItemViewHolder#itemView} to reflect the item at the given
|
||||
* position.
|
||||
* <p>
|
||||
* Note that unlike {@link android.widget.ListView}, RecyclerView will not call this method
|
||||
* again if the position of the item changes in the data set unless the item itself is
|
||||
* invalidated or the new position cannot be determined. For this reason, you should only
|
||||
* use the <code>offset</code> and <code>section</code> parameters while acquiring the
|
||||
* related data item inside this method and should not keep a copy of it. If you need the
|
||||
* position of an item later on (e.g. in a click listener), use
|
||||
* {@link ItemViewHolder#getAdapterPosition()} which will have the updated adapter
|
||||
* position. Then you can use {@link #getAdapterPositionSection(int)} and
|
||||
* {@link #getItemSectionOffset(int, int)}
|
||||
*
|
||||
*
|
||||
* @param viewHolder The ViewHolder which should be updated to represent the contents of the
|
||||
* item at the given position in the data set.
|
||||
* @param section The index of the section.
|
||||
* @param offset The position of the item within the section.
|
||||
*/
|
||||
public abstract void onBindItemViewHolder(ItemViewHolder viewHolder, int section, int offset);
|
||||
|
||||
// Notify
|
||||
/**
|
||||
* Notify any registered observers that the data set has changed.
|
||||
*
|
||||
* <p>There are two different classes of data change events, item changes and structural
|
||||
* changes. Item changes are when a single item has its data updated but no positional
|
||||
* changes have occurred. Structural changes are when items are inserted, removed or moved
|
||||
* within the data set.</p>
|
||||
*
|
||||
* <p>This event does not specify what about the data set has changed, forcing
|
||||
* any observers to assume that all existing items and structure may no longer be valid.
|
||||
* LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
|
||||
*
|
||||
* <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
|
||||
* for adapters that report that they have {@link #hasStableIds() stable IDs} when
|
||||
* this method is used. This can help for the purposes of animation and visual
|
||||
* object persistence but individual item views will still need to be rebound
|
||||
* and relaid out.</p>
|
||||
*
|
||||
* <p>If you are writing an adapter it will always be more efficient to use the more
|
||||
* specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
|
||||
* as a last resort.</p>
|
||||
*
|
||||
* @see #notifySectionDataSetChanged(int)
|
||||
* @see #notifySectionHeaderChanged(int)
|
||||
* @see #notifySectionItemChanged(int, int)
|
||||
* @see #notifySectionInserted(int)
|
||||
* @see #notifySectionItemInserted(int, int)
|
||||
* @see #notifySectionItemRangeInserted(int, int, int)
|
||||
* @see #notifySectionRemoved(int)
|
||||
* @see #notifySectionItemRemoved(int, int)
|
||||
* @see #notifySectionItemRangeRemoved(int, int, int)
|
||||
*/
|
||||
public void notifyAllSectionsDataSetChanged() {
|
||||
calculateSections();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void notifySectionDataSetChanged(int section) {
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
notifyItemRangeChanged(sectionObject.position, sectionObject.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionHeaderChanged(int section) {
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
notifyItemRangeChanged(sectionObject.position, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionItemChanged(int section, int position) {
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
notifyItemChanged(sectionObject.position + position + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionInserted(int section) {
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
notifyItemRangeInserted(sectionObject.position, sectionObject.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionItemInserted(int section, int position) {
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
notifyItemInserted(sectionObject.position + position + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionItemRangeInserted(int section, int position, int count) {
|
||||
calculateSections();
|
||||
if (mSections == null) {
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
if (position + count > sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + (position + count) + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
notifyItemRangeInserted(sectionObject.position + position + 1, count);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionRemoved(int section) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
calculateSections();
|
||||
notifyItemRangeRemoved(sectionObject.position, sectionObject.length);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionItemRemoved(int section, int position) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
calculateSections();
|
||||
notifyItemRemoved(sectionObject.position + position + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void notifySectionItemRangeRemoved(int section, int position, int count) {
|
||||
if (mSections == null) {
|
||||
calculateSections();
|
||||
notifyAllSectionsDataSetChanged();
|
||||
}
|
||||
else {
|
||||
final Section sectionObject = mSections.get(section);
|
||||
|
||||
if (position < 0 || position >= sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + position + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
if (position + count > sectionObject.itemNumber) {
|
||||
throw new IndexOutOfBoundsException("Invalid index " + (position + count) + ", size is " + sectionObject.itemNumber);
|
||||
}
|
||||
|
||||
calculateSections();
|
||||
notifyItemRangeRemoved(sectionObject.position + position + 1, count);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue