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.*"
|
includeGroupByRegex "org\\.signal.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
url "https://www.jitpack.io"
|
|
||||||
}
|
|
||||||
|
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -29,10 +26,6 @@ repositories {
|
||||||
jcenter {
|
jcenter {
|
||||||
content {
|
content {
|
||||||
includeVersion "mobi.upod", "time-duration-picker", "1.1.3"
|
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(':contacts')
|
||||||
implementation project(':qr')
|
implementation project(':qr')
|
||||||
implementation project(':sms-exporter')
|
implementation project(':sms-exporter')
|
||||||
|
implementation project(':sticky-header-grid')
|
||||||
|
implementation project(':photoview')
|
||||||
|
|
||||||
implementation libs.libsignal.android
|
implementation libs.libsignal.android
|
||||||
implementation libs.google.protobuf.javalite
|
implementation libs.google.protobuf.javalite
|
||||||
|
@ -494,7 +489,6 @@ dependencies {
|
||||||
implementation libs.emilsjolander.stickylistheaders
|
implementation libs.emilsjolander.stickylistheaders
|
||||||
implementation libs.jpardogo.materialtabstrip
|
implementation libs.jpardogo.materialtabstrip
|
||||||
implementation libs.apache.httpclient.android
|
implementation libs.apache.httpclient.android
|
||||||
implementation libs.photoview
|
|
||||||
implementation libs.glide.glide
|
implementation libs.glide.glide
|
||||||
implementation libs.roundedimageview
|
implementation libs.roundedimageview
|
||||||
implementation libs.materialish.progress
|
implementation libs.materialish.progress
|
||||||
|
@ -503,12 +497,10 @@ dependencies {
|
||||||
implementation libs.google.zxing.android.integration
|
implementation libs.google.zxing.android.integration
|
||||||
implementation libs.time.duration.picker
|
implementation libs.time.duration.picker
|
||||||
implementation libs.google.zxing.core
|
implementation libs.google.zxing.core
|
||||||
|
implementation libs.google.flexbox
|
||||||
implementation (libs.subsampling.scale.image.view) {
|
implementation (libs.subsampling.scale.image.view) {
|
||||||
exclude group: 'com.android.support', module: 'support-annotations'
|
exclude group: 'com.android.support', module: 'support-annotations'
|
||||||
}
|
}
|
||||||
implementation (libs.numberpickerview) {
|
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
|
||||||
}
|
|
||||||
implementation (libs.android.tooltips) {
|
implementation (libs.android.tooltips) {
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
}
|
}
|
||||||
|
@ -517,15 +509,9 @@ dependencies {
|
||||||
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
|
||||||
}
|
}
|
||||||
implementation libs.stream
|
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.lottie
|
||||||
|
|
||||||
implementation libs.stickyheadergrid
|
|
||||||
|
|
||||||
implementation libs.signal.android.database.sqlcipher
|
implementation libs.signal.android.database.sqlcipher
|
||||||
implementation libs.androidx.sqlite
|
implementation libs.androidx.sqlite
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
|
||||||
getLabel().setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin);
|
getLabel().setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin);
|
||||||
break;
|
break;
|
||||||
case PIN_DOES_NOT_MATCH:
|
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)));
|
getString(R.string.ConfirmKbsPinFragment__pins_dont_match)));
|
||||||
getInput().getText().clear();
|
getInput().getText().clear();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class CreateKbsPinFragment extends BaseKbsPinFragment<CreateKbsPinViewMod
|
||||||
viewModel.getNavigationEvents().observe(getViewLifecycleOwner(), e -> onConfirmPin(e.getUserEntry(), e.getKeyboard(), args.getIsPinChange()));
|
viewModel.getNavigationEvents().observe(getViewLifecycleOwner(), e -> onConfirmPin(e.getUserEntry(), e.getKeyboard(), args.getIsPinChange()));
|
||||||
viewModel.getErrorEvents().observe(getViewLifecycleOwner(), e -> {
|
viewModel.getErrorEvents().observe(getViewLifecycleOwner(), e -> {
|
||||||
if (e == CreateKbsPinViewModel.PinErrorEvent.WEAK_PIN) {
|
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)));
|
getString(R.string.CreateKbsPinFragment__choose_a_stronger_pin)));
|
||||||
shake(getInput(), () -> getInput().getText().clear());
|
shake(getInput(), () -> getInput().getText().clear());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,8 +18,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.CustomDefaultPreference;
|
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 {
|
public abstract class CorrectedPreferenceFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
|
@ -40,9 +38,7 @@ public abstract class CorrectedPreferenceFragment extends PreferenceFragmentComp
|
||||||
public void onDisplayPreferenceDialog(Preference preference) {
|
public void onDisplayPreferenceDialog(Preference preference) {
|
||||||
DialogFragment dialogFragment = null;
|
DialogFragment dialogFragment = null;
|
||||||
|
|
||||||
if (preference instanceof ColorPickerPreference) {
|
if (preference instanceof CustomDefaultPreference) {
|
||||||
dialogFragment = ColorPickerPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
|
||||||
} else if (preference instanceof CustomDefaultPreference) {
|
|
||||||
dialogFragment = CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
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.net.ContentProxySelector
|
||||||
import org.thoughtcrime.securesms.util.AppForegroundObserver
|
import org.thoughtcrime.securesms.util.AppForegroundObserver
|
||||||
import org.thoughtcrime.securesms.util.DeviceProperties
|
import org.thoughtcrime.securesms.util.DeviceProperties
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
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">
|
<ripple android:color="@color/green_700" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:id="@android:id/background">
|
<item android:id="@android:id/background">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<solid android:color="@color/green" />
|
<solid android:color="@color/green_500" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</ripple>
|
</ripple>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<ripple android:color="@color/red_700" xmlns:android="http://schemas.android.com/apk/res/android">
|
<ripple android:color="@color/red_700" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:id="@android:id/background">
|
<item android:id="@android:id/background">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<solid android:color="@color/red" />
|
<solid android:color="@color/red_500" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</ripple>
|
</ripple>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</item>
|
</item>
|
||||||
<item android:state_pressed="false">
|
<item android:state_pressed="false">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<solid android:color="@color/green" />
|
<solid android:color="@color/green_500" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</selector>
|
</selector>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
</item>
|
</item>
|
||||||
<item android:state_pressed="false">
|
<item android:state_pressed="false">
|
||||||
<shape android:shape="oval">
|
<shape android:shape="oval">
|
||||||
<solid android:color="@color/red" />
|
<solid android:color="@color/red_500" />
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</selector>
|
</selector>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:background="@color/green"
|
tools:background="@color/green_500"
|
||||||
tools:layout_width="wrap_content"
|
tools:layout_width="wrap_content"
|
||||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical|end"
|
android:gravity="center_vertical|end"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
tools:background="@color/green"
|
tools:background="@color/green_500"
|
||||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<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:layout_height="16dp"
|
||||||
android:src="@drawable/circle_white"
|
android:src="@drawable/circle_white"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
tools:tint="@color/red"/>
|
tools:tint="@color/red_500"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -42,7 +42,7 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:background="@color/red" />
|
tools:background="@color/red_500" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_switch"
|
android:id="@+id/text_switch"
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toEndOf="@id/camera_switch"
|
app:layout_constraintStart_toEndOf="@id/camera_switch"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:background="@color/green" />
|
tools:background="@color/green_500" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -33,7 +33,7 @@
|
||||||
app:layout_constraintHorizontal_bias="1"
|
app:layout_constraintHorizontal_bias="1"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:background="@color/red" />
|
tools:background="@color/red_500" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_switch"
|
android:id="@+id/text_switch"
|
||||||
|
@ -48,6 +48,6 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:background="@color/green" />
|
tools:background="@color/green_500" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
android:background="@drawable/circle_tintable"
|
android:background="@drawable/circle_tintable"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:backgroundTint="?android:windowBackground"
|
app:backgroundTint="?android:windowBackground"
|
||||||
tools:backgroundTint="@color/red"
|
tools:backgroundTint="@color/red_500"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
app:layout_constraintBottom_toBottomOf="@id/story"
|
app:layout_constraintBottom_toBottomOf="@id/story"
|
||||||
app:layout_constraintEnd_toEndOf="@id/story"
|
app:layout_constraintEnd_toEndOf="@id/story"
|
||||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Signal.Story.Preview"
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Signal.Story.Preview"
|
||||||
tools:background="@color/green"
|
tools:background="@color/green_500"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:translationX="-28dp"
|
android:translationX="-28dp"
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
||||||
tools:background="@color/red" />
|
tools:background="@color/red_500" />
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/preview_media_1"
|
android:id="@+id/preview_media_1"
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.WallpaperPreview"
|
||||||
app:strokeColor="@color/signal_colorBackground"
|
app:strokeColor="@color/signal_colorBackground"
|
||||||
app:strokeWidth="3dp"
|
app:strokeWidth="3dp"
|
||||||
tools:background="@color/green" />
|
tools:background="@color/green_500" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
app:layout_constraintEnd_toStartOf="@id/color_bar"
|
app:layout_constraintEnd_toStartOf="@id/color_bar"
|
||||||
app:layout_constraintStart_toStartOf="@id/color_bar"
|
app:layout_constraintStart_toStartOf="@id/color_bar"
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:tint="@color/red" />
|
tools:tint="@color/red_500" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/color_bar"
|
android:id="@+id/color_bar"
|
||||||
|
|
|
@ -192,7 +192,7 @@
|
||||||
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_draw_color_bar"
|
app:layout_constraintEnd_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||||
app:layout_constraintStart_toStartOf="@id/image_editor_hud_draw_color_bar"
|
app:layout_constraintStart_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:tint="@color/red" />
|
tools:tint="@color/red_500" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/image_editor_hud_draw_color_bar"
|
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_constraintEnd_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||||
app:layout_constraintStart_toStartOf="@id/image_editor_hud_draw_color_bar"
|
app:layout_constraintStart_toStartOf="@id/image_editor_hud_draw_color_bar"
|
||||||
tools:alpha="1"
|
tools:alpha="1"
|
||||||
tools:tint="@color/red" />
|
tools:tint="@color/red_500" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatSeekBar
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
android:id="@+id/image_editor_hud_draw_color_bar"
|
android:id="@+id/image_editor_hud_draw_color_bar"
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
tools:viewBindingIgnore="true"
|
tools:viewBindingIgnore="true"
|
||||||
android:layout_width="54dp"
|
android:layout_width="54dp"
|
||||||
android:layout_height="72dp"
|
android:layout_height="72dp"
|
||||||
tools:background="@color/red"
|
tools:background="@color/red_500"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
|
@ -9,7 +9,7 @@
|
||||||
android:background="@null"
|
android:background="@null"
|
||||||
android:clipChildren="true"
|
android:clipChildren="true"
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp"
|
||||||
tools:background="@color/red"
|
tools:background="@color/red_500"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
|
|
@ -74,6 +74,7 @@ dependencyResolutionManagement {
|
||||||
alias('google-ez-vcard').to('com.googlecode.ez-vcard:ez-vcard:0.9.11')
|
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-jsr305').to('com.google.code.findbugs:jsr305:3.0.2')
|
||||||
alias('google-guava-android').to('com.google.guava:guava:30.0-android')
|
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
|
// Exoplayer
|
||||||
alias('exoplayer-core').to('com.google.android.exoplayer', 'exoplayer-core').versionRef('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('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-glide').to('com.github.bumptech.glide', 'glide').versionRef('glide')
|
||||||
alias('glide-compiler').to('com.github.bumptech.glide', 'compiler').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('roundedimageview').to('com.makeramen:roundedimageview:2.1.0')
|
||||||
alias('materialish-progress').to('com.pnikosis:materialish-progress:1.5')
|
alias('materialish-progress').to('com.pnikosis:materialish-progress:1.5')
|
||||||
alias('waitingdots').to('pl.tajchert:waitingdots:0.1.0')
|
alias('waitingdots').to('pl.tajchert:waitingdots:0.1.0')
|
||||||
alias('time-duration-picker').to('mobi.upod:time-duration-picker:1.1.3')
|
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('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-tooltips').to('com.tomergoldst.android:tooltips:1.0.6')
|
||||||
alias('android-smsmms').to('com.klinkerapps:android-smsmms:4.0.1')
|
alias('android-smsmms').to('com.klinkerapps:android-smsmms:4.0.1')
|
||||||
alias('stream').to('com.annimon:stream:1.1.8')
|
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('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('dnsjava').to('dnsjava:dnsjava:2.1.9')
|
||||||
alias('nanohttpd-webserver').to('org.nanohttpd:nanohttpd-webserver:2.3.1')
|
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>
|
</trusted-artifacts>
|
||||||
</configuration>
|
</configuration>
|
||||||
<components>
|
<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">
|
<component group="androidx.activity" name="activity" version="1.5.1">
|
||||||
<artifact name="activity-1.5.1.aar">
|
<artifact name="activity-1.5.1.aar">
|
||||||
<sha256 value="4b04b42d2c1f81c02faf0f7b6e9cc9fede10fdee8f66136cd4b99f88e8f48c0f" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="0361d1526a4d7501255e19779e09e93cdbd07fee0e2f5c50b7a137432d510119" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.appcompat" name="appcompat" version="1.5.1">
|
||||||
<artifact name="appcompat-1.5.1.aar">
|
<artifact name="appcompat-1.5.1.aar">
|
||||||
<sha256 value="50f2c7756e28a7da648bd4551ee3b31e0b71863a6bf591f0ca978428219c5eab" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="8c8e88ccafb55c142ea2ac0669fd4db8e7190a853d73fcc429a5c012ceacddf2" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.appcompat" name="appcompat-resources" version="1.5.1">
|
||||||
<artifact name="appcompat-resources-1.5.1.aar">
|
<artifact name="appcompat-resources-1.5.1.aar">
|
||||||
<sha256 value="6cfa9caae1869ffe03da9ec3ebf47ad84d70a7185ee86ec63aa8289cbb457e67" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="e3877fa529fe29177f34a26e0790ed35544848b0c7503bfed30b2539f1686d65" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.core" name="core" version="1.8.0">
|
||||||
<artifact name="core-1.8.0.aar">
|
<artifact name="core-1.8.0.aar">
|
||||||
<sha256 value="48c64a15ec3eb11bfb33339e5ceb70ec7f821bd2dfa2eb8675ebd5353317e792" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="ce005162c229bf308d2d5b12fb6cad0874069cbbeaccee63a8193bd08d40de04" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.emoji2" name="emoji2" version="1.2.0">
|
||||||
<artifact name="emoji2-1.2.0.aar">
|
<artifact name="emoji2-1.2.0.aar">
|
||||||
<sha256 value="f31a06c150ecb03073f55a6f7b0b74a240a6a8d727c14ce76726d020570dfa8c" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="9d1996cca03777baa1f27cd15531db983a633dae37b90f85bd53501acb56699d" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.emoji2" name="emoji2-views-helper" version="1.2.0">
|
||||||
<artifact name="emoji2-views-helper-1.2.0.aar">
|
<artifact name="emoji2-views-helper-1.2.0.aar">
|
||||||
<sha256 value="7ffa4d464d9db259fca0cdb50fbd4ab63d6872bcda59468b9f7555504c7d5ac4" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="1cedc0f971979cdec31f1542f263a8d41df6cfffc4857ea73a7d85643b73486d" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.lifecycle" name="lifecycle-process" version="2.4.1">
|
||||||
<artifact name="lifecycle-process-2.4.1.aar">
|
<artifact name="lifecycle-process-2.4.1.aar">
|
||||||
<sha256 value="db649b3efa24e31052145310b002db91da346b3f89c093ec38c3046db45e794e" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="2a7b90e5049b674b36bccfd68677b3a0b3178b3f7c2ef7ddf618d3895598c4ce" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.5.1">
|
||||||
<artifact name="lifecycle-runtime-2.5.1.aar">
|
<artifact name="lifecycle-runtime-2.5.1.aar">
|
||||||
<sha256 value="33b0d73dc2f028fceb3599bacabe563c3db6d26f3513d889595863536a4ac8c0" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="23542c8c85cc58fafe0ae8cba201e6c9e01b4c6799223340a2d6a51d7784828c" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.resourceinspection" name="resourceinspection-annotation" version="1.0.1">
|
||||||
<artifact name="resourceinspection-annotation-1.0.1.jar">
|
<artifact name="resourceinspection-annotation-1.0.1.jar">
|
||||||
<sha256 value="8cff870ec6fb31db48a52f4a792335b4bf8de07e03bd37823181526433ccd5cb" origin="Generated by Gradle"/>
|
<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"/>
|
<sha256 value="352a11a8d8a4c1bd6cd2c2fefff9c94ca954d7b5202a0656959db95297f6a2b7" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="androidx.savedstate" name="savedstate" version="1.2.0">
|
||||||
<artifact name="savedstate-1.2.0.aar">
|
<artifact name="savedstate-1.2.0.aar">
|
||||||
<sha256 value="2de528d6898e95ef020d22d9ffdf9d1f77cbdd93f92d39dfaa5d5c43b0c311c8" origin="Generated by Gradle"/>
|
<sha256 value="2de528d6898e95ef020d22d9ffdf9d1f77cbdd93f92d39dfaa5d5c43b0c311c8" origin="Generated by Gradle"/>
|
||||||
|
@ -826,6 +906,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
<component group="androidx.startup" name="startup-runtime" version="1.0.0">
|
<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">
|
<artifact name="startup-runtime-1.0.0.module">
|
||||||
<sha256 value="40effca0d6ee1fde32bc296897e54ebbcc4cf4aa29b0c531036cbd2a824a3c24" origin="Generated by Gradle"/>
|
<sha256 value="40effca0d6ee1fde32bc296897e54ebbcc4cf4aa29b0c531036cbd2a824a3c24" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
|
@ -1649,6 +1732,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
|
||||||
<sha256 value="313157e19cc69358139899fb582d110e361e54ab80bf681b60d7247b5b69a822" origin="Generated by Gradle"/>
|
<sha256 value="313157e19cc69358139899fb582d110e361e54ab80bf681b60d7247b5b69a822" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="com.google.android.gms" name="play-services-auth" version="16.0.1">
|
||||||
<artifact name="play-services-auth-16.0.1.aar">
|
<artifact name="play-services-auth-16.0.1.aar">
|
||||||
<sha256 value="aec9e1c584d442cb9f59481a50b2c66dc191872607c04d97ecb82dd0eb5149ec" origin="Generated by Gradle"/>
|
<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 ':contacts-app'
|
||||||
include ':qr'
|
include ':qr'
|
||||||
include ':qr-app'
|
include ':qr-app'
|
||||||
|
include ':sticky-header-grid'
|
||||||
|
include ':photoview'
|
||||||
|
|
||||||
project(':app').name = 'Signal-Android'
|
project(':app').name = 'Signal-Android'
|
||||||
project(':paging').projectDir = file('paging/lib')
|
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