Convert BezelImageView, EndlessOnScrollListener, ComposeScheduleView, ProgressImageView to Kotlin (#3147)

* Convert BezelImageView to Kotlin

* Convert EndlessOnScrollListener to Kotlin

* Convert ComposeScheduleView to use view binding

* Convert ComposeScheduleView to Kotlin

* Convert ProgressImageView to Kotlin

* Apply reviewer feedback
This commit is contained in:
Nik Clayton 2023-01-13 19:49:56 +01:00 committed by GitHub
parent aa96d02923
commit e5e076b0d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 410 additions and 485 deletions

View file

@ -1244,7 +1244,7 @@ class ComposeActivity :
}
}
override fun onTimeSet(time: String) {
override fun onTimeSet(time: String?) {
viewModel.updateScheduledAt(time)
if (verifyScheduledTime()) {
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN

View file

@ -1,248 +0,0 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.compose.view;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;
import com.keylesspalace.tusky.R;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public class ComposeScheduleView extends ConstraintLayout {
public interface OnTimeSetListener {
void onTimeSet(String time);
}
private OnTimeSetListener listener;
private DateFormat dateFormat;
private DateFormat timeFormat;
private SimpleDateFormat iso8601;
private Button resetScheduleButton;
private TextView scheduledDateTimeView;
private TextView invalidScheduleWarningView;
private Calendar scheduleDateTime;
public static int MINIMUM_SCHEDULED_SECONDS = 330; // Minimum is 5 minutes, pad 30 seconds for posting
public ComposeScheduleView(Context context) {
super(context);
init();
}
public ComposeScheduleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ComposeScheduleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
inflate(getContext(), R.layout.view_compose_schedule, this);
dateFormat = SimpleDateFormat.getDateInstance();
timeFormat = SimpleDateFormat.getTimeInstance();
iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
iso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
resetScheduleButton = findViewById(R.id.resetScheduleButton);
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
invalidScheduleWarningView = findViewById(R.id.invalidScheduleWarning);
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
invalidScheduleWarningView.setText(R.string.warning_scheduling_interval);
scheduleDateTime = null;
setScheduledDateTime();
setEditIcons();
}
public void setListener(OnTimeSetListener listener) {
this.listener = listener;
}
private void setScheduledDateTime() {
if (scheduleDateTime == null) {
scheduledDateTimeView.setText("");
invalidScheduleWarningView.setVisibility(GONE);
} else {
Date scheduled = scheduleDateTime.getTime();
scheduledDateTimeView.setText(String.format("%s %s",
dateFormat.format(scheduled),
timeFormat.format(scheduled)));
verifyScheduledTime(scheduled);
}
}
private void setEditIcons() {
Drawable icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_create_24dp);
if (icon == null) {
return;
}
final int size = scheduledDateTimeView.getLineHeight();
icon.setBounds(0, 0, size, size);
scheduledDateTimeView.setCompoundDrawables(null, null, icon, null);
}
public void setResetOnClickListener(OnClickListener listener) {
resetScheduleButton.setOnClickListener(listener);
}
public void resetSchedule() {
scheduleDateTime = null;
setScheduledDateTime();
}
public void openPickDateDialog() {
long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000;
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder()
.setValidator(
DateValidatorPointForward.from(yesterday))
.build();
initializeSuggestedTime();
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
.datePicker()
.setSelection(scheduleDateTime.getTimeInMillis())
.setCalendarConstraints(calendarConstraints)
.build();
picker.addOnPositiveButtonClickListener(this::onDateSet);
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "date_picker");
}
private void openPickTimeDialog() {
MaterialTimePicker.Builder pickerBuilder = new MaterialTimePicker.Builder();
if (scheduleDateTime != null) {
pickerBuilder.setHour(scheduleDateTime.get(Calendar.HOUR_OF_DAY))
.setMinute(scheduleDateTime.get(Calendar.MINUTE));
}
if (android.text.format.DateFormat.is24HourFormat(this.getContext())) {
pickerBuilder.setTimeFormat(TimeFormat.CLOCK_24H);
} else {
pickerBuilder.setTimeFormat(TimeFormat.CLOCK_12H);
}
MaterialTimePicker picker = pickerBuilder.build();
picker.addOnPositiveButtonClickListener(v -> onTimeSet(picker.getHour(), picker.getMinute()));
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
}
public Date getDateTime(String scheduledAt) {
if (scheduledAt != null) {
try {
return iso8601.parse(scheduledAt);
} catch (ParseException e) {
}
}
return null;
}
public void setDateTime(String scheduledAt) {
Date date;
try {
date = iso8601.parse(scheduledAt);
} catch (ParseException e) {
return;
}
initializeSuggestedTime();
scheduleDateTime.setTime(date);
setScheduledDateTime();
}
public boolean verifyScheduledTime(@Nullable Date scheduledTime) {
boolean valid;
if (scheduledTime != null) {
Calendar minimumScheduledTime = getCalendar();
minimumScheduledTime.add(Calendar.SECOND, MINIMUM_SCHEDULED_SECONDS);
valid = scheduledTime.after(minimumScheduledTime.getTime());
} else {
valid = true;
}
invalidScheduleWarningView.setVisibility(valid ? GONE : VISIBLE);
return valid;
}
private void onDateSet(long selection) {
initializeSuggestedTime();
Calendar newDate = getCalendar();
// working around bug in DatePicker where date is UTC #1720
// see https://github.com/material-components/material-components-android/issues/882
newDate.setTimeZone(TimeZone.getTimeZone("UTC"));
newDate.setTimeInMillis(selection);
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
openPickTimeDialog();
}
private void onTimeSet(int hourOfDay, int minute) {
initializeSuggestedTime();
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
scheduleDateTime.set(Calendar.MINUTE, minute);
setScheduledDateTime();
if (listener != null) {
listener.onTimeSet(getTime());
}
}
public String getTime() {
if (scheduleDateTime == null) {
return null;
}
return iso8601.format(scheduleDateTime.getTime());
}
@NonNull
public static Calendar getCalendar() {
return Calendar.getInstance(TimeZone.getDefault());
}
private void initializeSuggestedTime() {
if (scheduleDateTime == null) {
scheduleDateTime = getCalendar();
scheduleDateTime.add(Calendar.MINUTE, 15);
}
}
}

View file

@ -0,0 +1,210 @@
/* Copyright 2019 kyori19
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.compose.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.databinding.ViewComposeScheduleBinding
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
class ComposeScheduleView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
interface OnTimeSetListener {
fun onTimeSet(time: String?)
}
private var binding = ViewComposeScheduleBinding.inflate(
(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater),
this
)
private var listener: OnTimeSetListener? = null
private var dateFormat = SimpleDateFormat.getDateInstance()
private var timeFormat = SimpleDateFormat.getTimeInstance()
private var iso8601 = SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
Locale.getDefault()
).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private var scheduleDateTime: Calendar? = null
init {
binding.scheduledDateTime.setOnClickListener { openPickDateDialog() }
binding.invalidScheduleWarning.setText(R.string.warning_scheduling_interval)
updateScheduleUi()
setEditIcons()
}
fun setListener(listener: OnTimeSetListener?) {
this.listener = listener
}
private fun updateScheduleUi() {
if (scheduleDateTime == null) {
binding.scheduledDateTime.text = ""
binding.invalidScheduleWarning.visibility = GONE
return
}
val scheduled = scheduleDateTime!!.time
binding.scheduledDateTime.text = String.format(
"%s %s",
dateFormat.format(scheduled),
timeFormat.format(scheduled)
)
verifyScheduledTime(scheduled)
}
private fun setEditIcons() {
val icon = ContextCompat.getDrawable(context, R.drawable.ic_create_24dp) ?: return
val size = binding.scheduledDateTime.lineHeight
icon.setBounds(0, 0, size, size)
binding.scheduledDateTime.setCompoundDrawables(null, null, icon, null)
}
fun setResetOnClickListener(listener: OnClickListener?) {
binding.resetScheduleButton.setOnClickListener(listener)
}
fun resetSchedule() {
scheduleDateTime = null
updateScheduleUi()
}
fun openPickDateDialog() {
val yesterday = Calendar.getInstance().timeInMillis - 24 * 60 * 60 * 1000
val calendarConstraints = CalendarConstraints.Builder()
.setValidator(
DateValidatorPointForward.from(yesterday)
)
.build()
initializeSuggestedTime()
val picker = MaterialDatePicker.Builder
.datePicker()
.setSelection(scheduleDateTime!!.timeInMillis)
.setCalendarConstraints(calendarConstraints)
.build()
picker.addOnPositiveButtonClickListener { selection: Long -> onDateSet(selection) }
picker.show((context as AppCompatActivity).supportFragmentManager, "date_picker")
}
private fun getTimeFormat(context: Context): Int {
return if (android.text.format.DateFormat.is24HourFormat(context)) {
TimeFormat.CLOCK_24H
} else {
TimeFormat.CLOCK_12H
}
}
private fun openPickTimeDialog() {
val pickerBuilder = MaterialTimePicker.Builder()
scheduleDateTime?.let {
pickerBuilder.setHour(it[Calendar.HOUR_OF_DAY])
.setMinute(it[Calendar.MINUTE])
}
pickerBuilder.setTimeFormat(getTimeFormat(context))
val picker = pickerBuilder.build()
picker.addOnPositiveButtonClickListener { onTimeSet(picker.hour, picker.minute) }
picker.show((context as AppCompatActivity).supportFragmentManager, "time_picker")
}
fun getDateTime(scheduledAt: String?): Date? {
scheduledAt?.let {
try {
return iso8601.parse(it)
} catch (_: ParseException) {
}
}
return null
}
fun setDateTime(scheduledAt: String?) {
val date = getDateTime(scheduledAt) ?: return
initializeSuggestedTime()
scheduleDateTime!!.time = date
updateScheduleUi()
}
fun verifyScheduledTime(scheduledTime: Date?): Boolean {
val valid: Boolean = if (scheduledTime != null) {
val minimumScheduledTime = calendar()
minimumScheduledTime.add(
Calendar.SECOND,
MINIMUM_SCHEDULED_SECONDS
)
scheduledTime.after(minimumScheduledTime.time)
} else {
true
}
binding.invalidScheduleWarning.visibility = if (valid) GONE else VISIBLE
return valid
}
private fun onDateSet(selection: Long) {
initializeSuggestedTime()
val newDate = calendar()
// working around bug in DatePicker where date is UTC #1720
// see https://github.com/material-components/material-components-android/issues/882
newDate.timeZone = TimeZone.getTimeZone("UTC")
newDate.timeInMillis = selection
scheduleDateTime!![newDate[Calendar.YEAR], newDate[Calendar.MONTH]] = newDate[Calendar.DATE]
openPickTimeDialog()
}
private fun onTimeSet(hourOfDay: Int, minute: Int) {
initializeSuggestedTime()
scheduleDateTime?.set(Calendar.HOUR_OF_DAY, hourOfDay)
scheduleDateTime?.set(Calendar.MINUTE, minute)
updateScheduleUi()
listener?.onTimeSet(time)
}
val time: String?
get() = scheduleDateTime?.time?.let { iso8601.format(it) }
private fun initializeSuggestedTime() {
if (scheduleDateTime == null) {
scheduleDateTime = calendar().apply {
add(Calendar.MINUTE, 15)
}
}
}
companion object {
var MINIMUM_SCHEDULED_SECONDS = 330 // Minimum is 5 minutes, pad 30 seconds for posting
fun calendar(): Calendar = Calendar.getInstance(TimeZone.getDefault())
}
}

View file

@ -1,121 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.compose.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.view.MediaPreviewImageView;
import at.connyduck.sparkbutton.helpers.Utils;
public final class ProgressImageView extends MediaPreviewImageView {
private int progress = -1;
private final RectF progressRect = new RectF();
private final RectF biggerRect = new RectF();
private final Paint circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint clearPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint markBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Drawable captionDrawable;
public ProgressImageView(Context context) {
super(context);
init();
}
public ProgressImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ProgressImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
circlePaint.setColor(getContext().getColor(R.color.tusky_blue));
circlePaint.setStrokeWidth(Utils.dpToPx(getContext(), 4));
circlePaint.setStyle(Paint.Style.STROKE);
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
markBgPaint.setStyle(Paint.Style.FILL);
markBgPaint.setColor(getContext().getColor(R.color.tusky_grey_10));
captionDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.spellcheck);
}
public void setProgress(int progress) {
this.progress = progress;
if (progress != -1) {
setColorFilter(Color.rgb(123, 123, 123), PorterDuff.Mode.MULTIPLY);
} else {
clearColorFilter();
}
invalidate();
}
public void setChecked(boolean checked) {
this.markBgPaint.setColor(getContext().getColor(checked ? R.color.tusky_blue : R.color.tusky_grey_10));
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float angle = (progress / 100f) * 360 - 90;
float halfWidth = getWidth() / 2;
float halfHeight = getHeight() / 2;
progressRect.set(halfWidth * 0.75f, halfHeight * 0.75f, halfWidth * 1.25f, halfHeight * 1.25f);
biggerRect.set(progressRect);
int margin = 8;
biggerRect.set(progressRect.left - margin, progressRect.top - margin, progressRect.right + margin, progressRect.bottom + margin);
canvas.saveLayer(biggerRect, null, Canvas.ALL_SAVE_FLAG);
if (progress != -1) {
canvas.drawOval(progressRect, circlePaint);
canvas.drawArc(biggerRect, angle, 360 - angle - 90, true, clearPaint);
}
canvas.restore();
int circleRadius = Utils.dpToPx(getContext(), 14);
int circleMargin = Utils.dpToPx(getContext(), 14);
int circleY = getHeight() - circleMargin - circleRadius / 2;
int circleX = getWidth() - circleMargin - circleRadius / 2;
canvas.drawCircle(circleX, circleY, circleRadius, markBgPaint);
captionDrawable.setBounds(getWidth() - circleMargin - circleRadius,
getHeight() - circleMargin - circleRadius,
getWidth() - circleMargin,
getHeight() - circleMargin);
captionDrawable.setTint(Color.WHITE);
captionDrawable.draw(canvas);
}
}

View file

@ -0,0 +1,103 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.components.compose.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.RectF
import android.util.AttributeSet
import androidx.appcompat.content.res.AppCompatResources
import at.connyduck.sparkbutton.helpers.Utils
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.view.MediaPreviewImageView
class ProgressImageView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MediaPreviewImageView(context, attrs, defStyleAttr) {
private var progress = -1
private val progressRect = RectF()
private val biggerRect = RectF()
private val circlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = context.getColor(R.color.tusky_blue)
strokeWidth = Utils.dpToPx(context, 4).toFloat()
style = Paint.Style.STROKE
}
private val clearPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
}
private val markBgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
color = context.getColor(R.color.tusky_grey_10)
}
private val captionDrawable = AppCompatResources.getDrawable(
context,
R.drawable.spellcheck
)!!.apply {
setTint(Color.WHITE)
}
private val circleRadius = Utils.dpToPx(context, 14)
private val circleMargin = Utils.dpToPx(context, 14)
fun setProgress(progress: Int) {
this.progress = progress
if (progress != -1) {
setColorFilter(Color.rgb(123, 123, 123), PorterDuff.Mode.MULTIPLY)
} else {
clearColorFilter()
}
invalidate()
}
fun setChecked(checked: Boolean) {
markBgPaint.color =
context.getColor(if (checked) R.color.tusky_blue else R.color.tusky_grey_10)
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val angle = progress / 100f * 360 - 90
val halfWidth = width / 2f
val halfHeight = height / 2f
progressRect[halfWidth * 0.75f, halfHeight * 0.75f, halfWidth * 1.25f] = halfHeight * 1.25f
biggerRect.set(progressRect)
val margin = 8
biggerRect[progressRect.left - margin, progressRect.top - margin, progressRect.right + margin] =
progressRect.bottom + margin
canvas.saveLayer(biggerRect, null)
if (progress != -1) {
canvas.drawOval(progressRect, circlePaint)
canvas.drawArc(biggerRect, angle, 360 - angle - 90, true, clearPaint)
}
canvas.restore()
val circleY = height - circleMargin - circleRadius / 2
val circleX = width - circleMargin - circleRadius / 2
canvas.drawCircle(circleX.toFloat(), circleY.toFloat(), circleRadius.toFloat(), markBgPaint)
captionDrawable.setBounds(
width - circleMargin - circleRadius,
height - circleMargin - circleRadius,
width - circleMargin,
height - circleMargin
)
captionDrawable.draw(canvas)
}
}

View file

@ -1,61 +0,0 @@
/* Copyright 2019 Tusky contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.view;
import android.content.Context;
import android.graphics.Outline;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
/**
* override BezelImageView from MaterialDrawer library to provide custom outline
*/
public class BezelImageView extends com.mikepenz.materialdrawer.view.BezelImageView {
public BezelImageView(Context context) {
this(context, null);
}
public BezelImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BezelImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onSizeChanged(int w, int h, int old_w, int old_h) {
setOutlineProvider(new CustomOutline(w, h));
}
private static class CustomOutline extends ViewOutlineProvider {
int width;
int height;
CustomOutline(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, width, height, width < height ? width / 8f : height / 8f);
}
}
}

View file

@ -0,0 +1,48 @@
/* Copyright 2019 Tusky contributors
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.view
import android.content.Context
import android.graphics.Outline
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import com.mikepenz.materialdrawer.view.BezelImageView
/**
* override BezelImageView from MaterialDrawer library to provide custom outline
*/
class BezelImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : BezelImageView(context, attrs, defStyle) {
override fun onSizeChanged(w: Int, h: Int, old_w: Int, old_h: Int) {
outlineProvider = CustomOutline(w, h)
}
private class CustomOutline(var width: Int, var height: Int) :
ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0,
0,
width,
height,
if (width < height) width / 8f else height / 8f
)
}
}
}

View file

@ -1,54 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.view;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public abstract class EndlessOnScrollListener extends RecyclerView.OnScrollListener {
private static final int VISIBLE_THRESHOLD = 15;
private int previousTotalItemCount;
private LinearLayoutManager layoutManager;
public EndlessOnScrollListener(LinearLayoutManager layoutManager) {
this.layoutManager = layoutManager;
previousTotalItemCount = 0;
}
@Override
public void onScrolled(@NonNull RecyclerView view, int dx, int dy) {
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
if (totalItemCount < previousTotalItemCount) {
previousTotalItemCount = totalItemCount;
}
if (totalItemCount != previousTotalItemCount) {
previousTotalItemCount = totalItemCount;
}
if (lastVisibleItemPosition + VISIBLE_THRESHOLD > totalItemCount) {
onLoadMore(totalItemCount, view);
}
}
public void reset() {
previousTotalItemCount = 0;
}
public abstract void onLoadMore(int totalItemsCount, RecyclerView view);
}

View file

@ -0,0 +1,48 @@
/* Copyright 2017 Andrew Dawson
*
* This file is a part of Tusky.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky; if not,
* see <http://www.gnu.org/licenses>. */
package com.keylesspalace.tusky.view
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
abstract class EndlessOnScrollListener(private val layoutManager: LinearLayoutManager) :
RecyclerView.OnScrollListener() {
private var previousTotalItemCount = 0
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
val totalItemCount = layoutManager.itemCount
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
if (totalItemCount < previousTotalItemCount) {
previousTotalItemCount = totalItemCount
}
if (totalItemCount != previousTotalItemCount) {
previousTotalItemCount = totalItemCount
}
if (lastVisibleItemPosition + VISIBLE_THRESHOLD > totalItemCount) {
onLoadMore(totalItemCount, view)
}
}
fun reset() {
previousTotalItemCount = 0
}
abstract fun onLoadMore(totalItemsCount: Int, view: RecyclerView)
companion object {
private const val VISIBLE_THRESHOLD = 15
}
}