diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 2fb2ff9a..fb7c24e3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -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 diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java deleted file mode 100644 index 14c574a1..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java +++ /dev/null @@ -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 . */ - -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 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); - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt new file mode 100644 index 00000000..f2e00d34 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.kt @@ -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 . */ +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()) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java deleted file mode 100644 index 3be800e7..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.java +++ /dev/null @@ -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 . */ - -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); - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.kt new file mode 100644 index 00000000..6678356e --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ProgressImageView.kt @@ -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 . */ +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) + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java b/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java deleted file mode 100644 index c31b37e7..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.java +++ /dev/null @@ -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 . */ - -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); - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.kt b/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.kt new file mode 100644 index 00000000..f217ddef --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/view/BezelImageView.kt @@ -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 . */ +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 + ) + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java b/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java deleted file mode 100644 index 50f9ea6f..00000000 --- a/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.java +++ /dev/null @@ -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 . */ - -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); -} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.kt b/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.kt new file mode 100644 index 00000000..c240adf9 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/view/EndlessOnScrollListener.kt @@ -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 . */ +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 + } +}