mirror of https://github.com/iSoron/uhabits.git
parent
11831a2b24
commit
eb8d39fbe3
@ -1,214 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets;
|
|
||||||
|
|
||||||
import android.app.*;
|
|
||||||
import android.content.*;
|
|
||||||
import android.graphics.*;
|
|
||||||
import android.view.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.core.commands.*;
|
|
||||||
import org.isoron.uhabits.core.preferences.*;
|
|
||||||
import org.isoron.uhabits.intents.*;
|
|
||||||
|
|
||||||
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
|
||||||
|
|
||||||
public abstract class BaseWidget
|
|
||||||
{
|
|
||||||
private final int id;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected final WidgetPreferences widgetPrefs;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected final Preferences prefs;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected final PendingIntentFactory pendingIntentFactory;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected final CommandRunner commandRunner;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private WidgetDimensions dimensions;
|
|
||||||
|
|
||||||
public BaseWidget(@NonNull Context context, int id)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
this.context = context;
|
|
||||||
|
|
||||||
HabitsApplication app =
|
|
||||||
(HabitsApplication) context.getApplicationContext();
|
|
||||||
|
|
||||||
widgetPrefs = app.getComponent().getWidgetPreferences();
|
|
||||||
prefs = app.getComponent().getPreferences();
|
|
||||||
commandRunner = app.getComponent().getCommandRunner();
|
|
||||||
pendingIntentFactory = app.getComponent().getPendingIntentFactory();
|
|
||||||
dimensions = new WidgetDimensions(getDefaultWidth(), getDefaultHeight(),
|
|
||||||
getDefaultWidth(), getDefaultHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete()
|
|
||||||
{
|
|
||||||
widgetPrefs.removeWidget(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Context getContext()
|
|
||||||
{
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId()
|
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public RemoteViews getLandscapeRemoteViews()
|
|
||||||
{
|
|
||||||
return getRemoteViews(dimensions.getLandscapeWidth(),
|
|
||||||
dimensions.getLandscapeHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract PendingIntent getOnClickPendingIntent(Context context);
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public RemoteViews getPortraitRemoteViews()
|
|
||||||
{
|
|
||||||
return getRemoteViews(dimensions.getPortraitWidth(),
|
|
||||||
dimensions.getPortraitHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void refreshData(View widgetView);
|
|
||||||
|
|
||||||
public void setDimensions(@NonNull WidgetDimensions dimensions)
|
|
||||||
{
|
|
||||||
this.dimensions = dimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract View buildView();
|
|
||||||
|
|
||||||
protected abstract int getDefaultHeight();
|
|
||||||
|
|
||||||
protected abstract int getDefaultWidth();
|
|
||||||
|
|
||||||
private void adjustRemoteViewsPadding(RemoteViews remoteViews,
|
|
||||||
View view,
|
|
||||||
int width,
|
|
||||||
int height)
|
|
||||||
{
|
|
||||||
int imageWidth = view.getMeasuredWidth();
|
|
||||||
int imageHeight = view.getMeasuredHeight();
|
|
||||||
int p[] = calculatePadding(width, height, imageWidth, imageHeight);
|
|
||||||
remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildRemoteViews(View view,
|
|
||||||
RemoteViews remoteViews,
|
|
||||||
int width,
|
|
||||||
int height)
|
|
||||||
{
|
|
||||||
Bitmap bitmap = getBitmapFromView(view);
|
|
||||||
remoteViews.setImageViewBitmap(R.id.imageView, bitmap);
|
|
||||||
|
|
||||||
adjustRemoteViewsPadding(remoteViews, view, width, height);
|
|
||||||
|
|
||||||
PendingIntent onClickIntent = getOnClickPendingIntent(context);
|
|
||||||
if (onClickIntent != null)
|
|
||||||
remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] calculatePadding(int entireWidth,
|
|
||||||
int entireHeight,
|
|
||||||
int imageWidth,
|
|
||||||
int imageHeight)
|
|
||||||
{
|
|
||||||
int w = (int) (((float) entireWidth - imageWidth) / 2);
|
|
||||||
int h = (int) (((float) entireHeight - imageHeight) / 2);
|
|
||||||
|
|
||||||
return new int[]{w, h, w, h};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private Bitmap getBitmapFromView(View view)
|
|
||||||
{
|
|
||||||
view.invalidate();
|
|
||||||
int width = view.getMeasuredWidth();
|
|
||||||
int height = view.getMeasuredHeight();
|
|
||||||
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
view.draw(canvas);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected RemoteViews getRemoteViews(int width, int height)
|
|
||||||
{
|
|
||||||
View view = buildView();
|
|
||||||
measureView(view, width, height);
|
|
||||||
|
|
||||||
refreshData(view);
|
|
||||||
|
|
||||||
if (view.isLayoutRequested()) measureView(view, width, height);
|
|
||||||
|
|
||||||
RemoteViews remoteViews =
|
|
||||||
new RemoteViews(context.getPackageName(), R.layout.widget_wrapper);
|
|
||||||
|
|
||||||
buildRemoteViews(view, remoteViews, width, height);
|
|
||||||
|
|
||||||
return remoteViews;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void measureView(View view, int width, int height)
|
|
||||||
{
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(context);
|
|
||||||
View entireView = inflater.inflate(R.layout.widget_wrapper, null);
|
|
||||||
|
|
||||||
int specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
|
|
||||||
int specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
|
|
||||||
|
|
||||||
entireView.measure(specWidth, specHeight);
|
|
||||||
entireView.layout(0, 0, entireView.getMeasuredWidth(),
|
|
||||||
entireView.getMeasuredHeight());
|
|
||||||
|
|
||||||
View imageView = entireView.findViewById(R.id.imageView);
|
|
||||||
width = imageView.getMeasuredWidth();
|
|
||||||
height = imageView.getMeasuredHeight();
|
|
||||||
|
|
||||||
specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
|
|
||||||
specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
|
|
||||||
|
|
||||||
view.measure(specWidth, specHeight);
|
|
||||||
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int getPreferedBackgroundAlpha() {
|
|
||||||
return prefs.getWidgetOpacity();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.MeasureSpec
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
|
import org.isoron.uhabits.intents.PendingIntentFactory
|
||||||
|
|
||||||
|
abstract class BaseWidget(val context: Context, val id: Int) {
|
||||||
|
protected val widgetPrefs: WidgetPreferences
|
||||||
|
protected val prefs: Preferences
|
||||||
|
protected val pendingIntentFactory: PendingIntentFactory
|
||||||
|
protected val commandRunner: CommandRunner
|
||||||
|
private var dimensions: WidgetDimensions
|
||||||
|
fun delete() {
|
||||||
|
widgetPrefs.removeWidget(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
val landscapeRemoteViews: RemoteViews
|
||||||
|
get() = getRemoteViews(
|
||||||
|
dimensions.landscapeWidth,
|
||||||
|
dimensions.landscapeHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract fun getOnClickPendingIntent(context: Context): PendingIntent?
|
||||||
|
val portraitRemoteViews: RemoteViews
|
||||||
|
get() = getRemoteViews(
|
||||||
|
dimensions.portraitWidth,
|
||||||
|
dimensions.portraitHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
abstract fun refreshData(widgetView: View)
|
||||||
|
fun setDimensions(dimensions: WidgetDimensions) {
|
||||||
|
this.dimensions = dimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun buildView(): View?
|
||||||
|
protected abstract val defaultHeight: Int
|
||||||
|
protected abstract val defaultWidth: Int
|
||||||
|
private fun adjustRemoteViewsPadding(
|
||||||
|
remoteViews: RemoteViews,
|
||||||
|
view: View,
|
||||||
|
width: Int,
|
||||||
|
height: Int
|
||||||
|
) {
|
||||||
|
val imageWidth = view.measuredWidth
|
||||||
|
val imageHeight = view.measuredHeight
|
||||||
|
val p = calculatePadding(width, height, imageWidth, imageHeight)
|
||||||
|
remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRemoteViews(
|
||||||
|
view: View,
|
||||||
|
remoteViews: RemoteViews,
|
||||||
|
width: Int,
|
||||||
|
height: Int
|
||||||
|
) {
|
||||||
|
val bitmap = getBitmapFromView(view)
|
||||||
|
remoteViews.setImageViewBitmap(R.id.imageView, bitmap)
|
||||||
|
adjustRemoteViewsPadding(remoteViews, view, width, height)
|
||||||
|
val onClickIntent = getOnClickPendingIntent(context)
|
||||||
|
if (onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculatePadding(
|
||||||
|
entireWidth: Int,
|
||||||
|
entireHeight: Int,
|
||||||
|
imageWidth: Int,
|
||||||
|
imageHeight: Int
|
||||||
|
): IntArray {
|
||||||
|
val w = ((entireWidth.toFloat() - imageWidth) / 2).toInt()
|
||||||
|
val h = ((entireHeight.toFloat() - imageHeight) / 2).toInt()
|
||||||
|
return intArrayOf(w, h, w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBitmapFromView(view: View): Bitmap {
|
||||||
|
view.invalidate()
|
||||||
|
val width = view.measuredWidth
|
||||||
|
val height = view.measuredHeight
|
||||||
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
view.draw(canvas)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getRemoteViews(width: Int, height: Int): RemoteViews {
|
||||||
|
val view = buildView()!!
|
||||||
|
measureView(view, width, height)
|
||||||
|
refreshData(view)
|
||||||
|
if (view.isLayoutRequested) measureView(view, width, height)
|
||||||
|
val remoteViews = RemoteViews(context.packageName, R.layout.widget_wrapper)
|
||||||
|
buildRemoteViews(view, remoteViews, width, height)
|
||||||
|
return remoteViews
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measureView(view: View, width: Int, height: Int) {
|
||||||
|
var width = width
|
||||||
|
var height = height
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val entireView = inflater.inflate(R.layout.widget_wrapper, null)
|
||||||
|
var specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
|
||||||
|
var specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||||
|
entireView.measure(specWidth, specHeight)
|
||||||
|
entireView.layout(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
entireView.measuredWidth,
|
||||||
|
entireView.measuredHeight
|
||||||
|
)
|
||||||
|
val imageView = entireView.findViewById<View>(R.id.imageView)
|
||||||
|
width = imageView.measuredWidth
|
||||||
|
height = imageView.measuredHeight
|
||||||
|
specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
|
||||||
|
specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||||
|
view.measure(specWidth, specHeight)
|
||||||
|
view.layout(0, 0, view.measuredWidth, view.measuredHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val preferedBackgroundAlpha: Int
|
||||||
|
protected get() = prefs.widgetOpacity
|
||||||
|
|
||||||
|
init {
|
||||||
|
val app = context.applicationContext as HabitsApplication
|
||||||
|
widgetPrefs = app.component.widgetPreferences
|
||||||
|
prefs = app.component.preferences
|
||||||
|
commandRunner = app.component.commandRunner
|
||||||
|
pendingIntentFactory = app.component.pendingIntentFactory
|
||||||
|
dimensions = WidgetDimensions(
|
||||||
|
defaultWidth,
|
||||||
|
defaultHeight,
|
||||||
|
defaultWidth,
|
||||||
|
defaultHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,206 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets;
|
|
||||||
|
|
||||||
import android.appwidget.*;
|
|
||||||
import android.content.*;
|
|
||||||
import android.os.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.preferences.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static android.appwidget.AppWidgetManager.*;
|
|
||||||
import static org.isoron.uhabits.utils.InterfaceUtils.dpToPixels;
|
|
||||||
|
|
||||||
public abstract class BaseWidgetProvider extends AppWidgetProvider
|
|
||||||
{
|
|
||||||
private HabitList habits;
|
|
||||||
|
|
||||||
private Preferences preferences;
|
|
||||||
|
|
||||||
private WidgetPreferences widgetPrefs;
|
|
||||||
|
|
||||||
public static void updateAppWidget(@NonNull AppWidgetManager manager,
|
|
||||||
@NonNull BaseWidget widget)
|
|
||||||
{
|
|
||||||
RemoteViews landscape = widget.getLandscapeRemoteViews();
|
|
||||||
RemoteViews portrait = widget.getPortraitRemoteViews();
|
|
||||||
RemoteViews views = new RemoteViews(landscape, portrait);
|
|
||||||
manager.updateAppWidget(widget.getId(), views);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx,
|
|
||||||
@NonNull Bundle options)
|
|
||||||
{
|
|
||||||
int maxWidth =
|
|
||||||
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH));
|
|
||||||
int maxHeight =
|
|
||||||
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT));
|
|
||||||
int minWidth =
|
|
||||||
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH));
|
|
||||||
int minHeight =
|
|
||||||
(int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT));
|
|
||||||
|
|
||||||
return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAppWidgetOptionsChanged(@Nullable Context context,
|
|
||||||
@Nullable AppWidgetManager manager,
|
|
||||||
int widgetId,
|
|
||||||
@Nullable Bundle options)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (context == null) throw new RuntimeException("context is null");
|
|
||||||
if (manager == null) throw new RuntimeException("manager is null");
|
|
||||||
if (options == null) throw new RuntimeException("options is null");
|
|
||||||
updateDependencies(context);
|
|
||||||
|
|
||||||
context.setTheme(R.style.WidgetTheme);
|
|
||||||
|
|
||||||
BaseWidget widget = getWidgetFromId(context, widgetId);
|
|
||||||
WidgetDimensions dims = getDimensionsFromOptions(context, options);
|
|
||||||
widget.setDimensions(dims);
|
|
||||||
updateAppWidget(manager, widget);
|
|
||||||
}
|
|
||||||
catch (RuntimeException e)
|
|
||||||
{
|
|
||||||
drawErrorWidget(context, manager, widgetId, e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDeleted(@Nullable Context context, @Nullable int[] ids)
|
|
||||||
{
|
|
||||||
if (context == null) throw new RuntimeException("context is null");
|
|
||||||
if (ids == null) throw new RuntimeException("ids is null");
|
|
||||||
|
|
||||||
updateDependencies(context);
|
|
||||||
|
|
||||||
for (int id : ids)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
BaseWidget widget = getWidgetFromId(context, id);
|
|
||||||
widget.delete();
|
|
||||||
}
|
|
||||||
catch (HabitNotFoundException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpdate(@Nullable Context context,
|
|
||||||
@Nullable AppWidgetManager manager,
|
|
||||||
@Nullable int[] widgetIds)
|
|
||||||
{
|
|
||||||
if (context == null) throw new RuntimeException("context is null");
|
|
||||||
if (manager == null) throw new RuntimeException("manager is null");
|
|
||||||
if (widgetIds == null) throw new RuntimeException("widgetIds is null");
|
|
||||||
updateDependencies(context);
|
|
||||||
context.setTheme(R.style.WidgetTheme);
|
|
||||||
|
|
||||||
new Thread(() ->
|
|
||||||
{
|
|
||||||
Looper.prepare();
|
|
||||||
for (int id : widgetIds)
|
|
||||||
update(context, manager, id);
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Habit> getHabitsFromWidgetId(int widgetId)
|
|
||||||
{
|
|
||||||
long selectedIds[] = widgetPrefs.getHabitIdsFromWidgetId(widgetId);
|
|
||||||
ArrayList<Habit> selectedHabits = new ArrayList<>(selectedIds.length);
|
|
||||||
for (long id : selectedIds)
|
|
||||||
{
|
|
||||||
Habit h = habits.getById(id);
|
|
||||||
if (h == null) throw new HabitNotFoundException();
|
|
||||||
selectedHabits.add(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedHabits;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected abstract BaseWidget getWidgetFromId(@NonNull Context context,
|
|
||||||
int id);
|
|
||||||
|
|
||||||
private void drawErrorWidget(Context context,
|
|
||||||
AppWidgetManager manager,
|
|
||||||
int widgetId,
|
|
||||||
RuntimeException e)
|
|
||||||
{
|
|
||||||
RemoteViews errorView =
|
|
||||||
new RemoteViews(context.getPackageName(), R.layout.widget_error);
|
|
||||||
|
|
||||||
if (e instanceof HabitNotFoundException)
|
|
||||||
{
|
|
||||||
errorView.setCharSequence(R.id.label, "setText",
|
|
||||||
context.getString(R.string.habit_not_found));
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.updateAppWidget(widgetId, errorView);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update(@NonNull Context context,
|
|
||||||
@NonNull AppWidgetManager manager,
|
|
||||||
int widgetId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
BaseWidget widget = getWidgetFromId(context, widgetId);
|
|
||||||
Bundle options = manager.getAppWidgetOptions(widgetId);
|
|
||||||
widget.setDimensions(getDimensionsFromOptions(context, options));
|
|
||||||
updateAppWidget(manager, widget);
|
|
||||||
}
|
|
||||||
catch (RuntimeException e)
|
|
||||||
{
|
|
||||||
drawErrorWidget(context, manager, widgetId, e);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDependencies(Context context)
|
|
||||||
{
|
|
||||||
HabitsApplication app =
|
|
||||||
(HabitsApplication) context.getApplicationContext();
|
|
||||||
habits = app.getComponent().getHabitList();
|
|
||||||
preferences = app.getComponent().getPreferences();
|
|
||||||
widgetPrefs = app.getComponent().getWidgetPreferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Preferences getPreferences()
|
|
||||||
{
|
|
||||||
return preferences;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Looper
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
|
import org.isoron.uhabits.core.models.HabitNotFoundException
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
abstract class BaseWidgetProvider : AppWidgetProvider() {
|
||||||
|
private lateinit var habits: HabitList
|
||||||
|
lateinit var preferences: Preferences
|
||||||
|
private set
|
||||||
|
private lateinit var widgetPrefs: WidgetPreferences
|
||||||
|
fun getDimensionsFromOptions(
|
||||||
|
ctx: Context,
|
||||||
|
options: Bundle
|
||||||
|
): WidgetDimensions {
|
||||||
|
val maxWidth = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH).toFloat()
|
||||||
|
).toInt()
|
||||||
|
val maxHeight = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT).toFloat()
|
||||||
|
).toInt()
|
||||||
|
val minWidth = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat()
|
||||||
|
).toInt()
|
||||||
|
val minHeight = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat()
|
||||||
|
).toInt()
|
||||||
|
return WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAppWidgetOptionsChanged(
|
||||||
|
context: Context,
|
||||||
|
manager: AppWidgetManager,
|
||||||
|
widgetId: Int,
|
||||||
|
options: Bundle
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
updateDependencies(context)
|
||||||
|
context.setTheme(R.style.WidgetTheme)
|
||||||
|
val widget = getWidgetFromId(context, widgetId)
|
||||||
|
val dims = getDimensionsFromOptions(context, options)
|
||||||
|
widget.setDimensions(dims)
|
||||||
|
updateAppWidget(manager, widget)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
drawErrorWidget(context, manager, widgetId, e)
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleted(context: Context?, ids: IntArray?) {
|
||||||
|
if (context == null) throw RuntimeException("context is null")
|
||||||
|
if (ids == null) throw RuntimeException("ids is null")
|
||||||
|
updateDependencies(context)
|
||||||
|
for (id in ids) {
|
||||||
|
try {
|
||||||
|
val widget = getWidgetFromId(context, id)
|
||||||
|
widget.delete()
|
||||||
|
} catch (e: HabitNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpdate(
|
||||||
|
context: Context,
|
||||||
|
manager: AppWidgetManager,
|
||||||
|
widgetIds: IntArray
|
||||||
|
) {
|
||||||
|
updateDependencies(context)
|
||||||
|
context.setTheme(R.style.WidgetTheme)
|
||||||
|
Thread {
|
||||||
|
Looper.prepare()
|
||||||
|
for (id in widgetIds) update(context, manager, id)
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getHabitsFromWidgetId(widgetId: Int): List<Habit> {
|
||||||
|
val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)
|
||||||
|
val selectedHabits = ArrayList<Habit>(selectedIds.size)
|
||||||
|
for (id in selectedIds) {
|
||||||
|
val h = habits.getById(id) ?: throw HabitNotFoundException()
|
||||||
|
selectedHabits.add(h)
|
||||||
|
}
|
||||||
|
return selectedHabits
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun getWidgetFromId(
|
||||||
|
context: Context,
|
||||||
|
id: Int
|
||||||
|
): BaseWidget
|
||||||
|
|
||||||
|
private fun drawErrorWidget(
|
||||||
|
context: Context,
|
||||||
|
manager: AppWidgetManager,
|
||||||
|
widgetId: Int,
|
||||||
|
e: RuntimeException
|
||||||
|
) {
|
||||||
|
val errorView = RemoteViews(context.packageName, R.layout.widget_error)
|
||||||
|
if (e is HabitNotFoundException) {
|
||||||
|
errorView.setCharSequence(
|
||||||
|
R.id.label,
|
||||||
|
"setText",
|
||||||
|
context.getString(R.string.habit_not_found)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
manager.updateAppWidget(widgetId, errorView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(
|
||||||
|
context: Context,
|
||||||
|
manager: AppWidgetManager,
|
||||||
|
widgetId: Int
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
val widget = getWidgetFromId(context, widgetId)
|
||||||
|
val options = manager.getAppWidgetOptions(widgetId)
|
||||||
|
widget.setDimensions(getDimensionsFromOptions(context, options))
|
||||||
|
updateAppWidget(manager, widget)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
drawErrorWidget(context, manager, widgetId, e)
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDependencies(context: Context) {
|
||||||
|
val app = context.applicationContext as HabitsApplication
|
||||||
|
habits = app.component.habitList
|
||||||
|
preferences = app.component.preferences
|
||||||
|
widgetPrefs = app.component.widgetPreferences
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun updateAppWidget(
|
||||||
|
manager: AppWidgetManager,
|
||||||
|
widget: BaseWidget
|
||||||
|
) {
|
||||||
|
val landscape = widget.landscapeRemoteViews
|
||||||
|
val portrait = widget.portraitRemoteViews
|
||||||
|
val views = RemoteViews(landscape, portrait)
|
||||||
|
manager.updateAppWidget(widget.id, views)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,186 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets;
|
|
||||||
|
|
||||||
import android.appwidget.*;
|
|
||||||
import android.content.*;
|
|
||||||
import android.os.*;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.preferences.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static android.appwidget.AppWidgetManager.*;
|
|
||||||
import static org.isoron.uhabits.utils.InterfaceUtils.dpToPixels;
|
|
||||||
import static org.isoron.uhabits.widgets.StackWidgetService.*;
|
|
||||||
|
|
||||||
public class StackWidgetService extends RemoteViewsService
|
|
||||||
{
|
|
||||||
public static final String WIDGET_TYPE = "WIDGET_TYPE";
|
|
||||||
public static final String HABIT_IDS = "HABIT_IDS";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RemoteViewsFactory onGetViewFactory(Intent intent)
|
|
||||||
{
|
|
||||||
return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory
|
|
||||||
{
|
|
||||||
private Context context;
|
|
||||||
private int widgetId;
|
|
||||||
private long[] habitIds;
|
|
||||||
private StackWidgetType widgetType;
|
|
||||||
private ArrayList<RemoteViews> remoteViews = new ArrayList<>();
|
|
||||||
|
|
||||||
public StackRemoteViewsFactory(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
|
|
||||||
AppWidgetManager.INVALID_APPWIDGET_ID);
|
|
||||||
int widgetTypeValue = intent.getIntExtra(WIDGET_TYPE, -1);
|
|
||||||
String habitIdsStr = intent.getStringExtra(HABIT_IDS);
|
|
||||||
|
|
||||||
if (widgetTypeValue < 0) throw new RuntimeException("invalid widget type");
|
|
||||||
if (habitIdsStr == null) throw new RuntimeException("habitIdsStr is null");
|
|
||||||
|
|
||||||
widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue);
|
|
||||||
habitIds = StringUtils.splitLongs(habitIdsStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCreate()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDestroy()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount()
|
|
||||||
{
|
|
||||||
return habitIds.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx,
|
|
||||||
@NonNull Bundle options)
|
|
||||||
{
|
|
||||||
int maxWidth = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH));
|
|
||||||
int maxHeight = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT));
|
|
||||||
int minWidth = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH));
|
|
||||||
int minHeight = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT));
|
|
||||||
|
|
||||||
return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoteViews getViewAt(int position)
|
|
||||||
{
|
|
||||||
Log.i("StackRemoteViewsFactory", "getViewAt " + position);
|
|
||||||
if (position < 0 || position > remoteViews.size()) return null;
|
|
||||||
return remoteViews.get(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private BaseWidget constructWidget(@NonNull Habit habit,
|
|
||||||
@NonNull Preferences prefs)
|
|
||||||
{
|
|
||||||
switch (widgetType)
|
|
||||||
{
|
|
||||||
case CHECKMARK:
|
|
||||||
return new CheckmarkWidget(context, widgetId, habit);
|
|
||||||
case FREQUENCY:
|
|
||||||
return new FrequencyWidget(context, widgetId, habit, prefs.getFirstWeekdayInt());
|
|
||||||
case SCORE:
|
|
||||||
return new ScoreWidget(context, widgetId, habit);
|
|
||||||
case HISTORY:
|
|
||||||
return new HistoryWidget(context, widgetId, habit);
|
|
||||||
case STREAKS:
|
|
||||||
return new StreakWidget(context, widgetId, habit);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoteViews getLoadingView()
|
|
||||||
{
|
|
||||||
Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId);
|
|
||||||
EmptyWidget widget = new EmptyWidget(context, widgetId);
|
|
||||||
widget.setDimensions(getDimensionsFromOptions(context, options));
|
|
||||||
RemoteViews landscapeViews = widget.getLandscapeRemoteViews();
|
|
||||||
RemoteViews portraitViews = widget.getPortraitRemoteViews();
|
|
||||||
return new RemoteViews(landscapeViews, portraitViews);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getViewTypeCount()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getItemId(int position)
|
|
||||||
{
|
|
||||||
return habitIds[position];
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasStableIds()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onDataSetChanged()
|
|
||||||
{
|
|
||||||
Log.i("StackRemoteViewsFactory", "onDataSetChanged started");
|
|
||||||
|
|
||||||
HabitsApplication app = (HabitsApplication) context.getApplicationContext();
|
|
||||||
Preferences prefs = app.getComponent().getPreferences();
|
|
||||||
HabitList habitList = app.getComponent().getHabitList();
|
|
||||||
Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId);
|
|
||||||
ArrayList<RemoteViews> newRemoteViews = new ArrayList<>();
|
|
||||||
|
|
||||||
if (Looper.myLooper() == null) Looper.prepare();
|
|
||||||
|
|
||||||
for (long id : habitIds)
|
|
||||||
{
|
|
||||||
Habit h = habitList.getById(id);
|
|
||||||
if (h == null) throw new HabitNotFoundException();
|
|
||||||
|
|
||||||
BaseWidget widget = constructWidget(h, prefs);
|
|
||||||
widget.setDimensions(getDimensionsFromOptions(context, options));
|
|
||||||
|
|
||||||
RemoteViews landscapeViews = widget.getLandscapeRemoteViews();
|
|
||||||
RemoteViews portraitViews = widget.getPortraitRemoteViews();
|
|
||||||
newRemoteViews.add(new RemoteViews(landscapeViews, portraitViews));
|
|
||||||
|
|
||||||
Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget " + id);
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteViews = newRemoteViews;
|
|
||||||
Log.i("StackRemoteViewsFactory", "onDataSetChanged ended");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import android.widget.RemoteViewsService
|
||||||
|
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitNotFoundException
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.utils.StringUtils.Companion.splitLongs
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
class StackWidgetService : RemoteViewsService() {
|
||||||
|
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
|
||||||
|
return StackRemoteViewsFactory(this.applicationContext, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val WIDGET_TYPE = "WIDGET_TYPE"
|
||||||
|
const val HABIT_IDS = "HABIT_IDS"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class StackRemoteViewsFactory(private val context: Context, intent: Intent) :
|
||||||
|
RemoteViewsFactory {
|
||||||
|
private val widgetId: Int
|
||||||
|
private val habitIds: LongArray
|
||||||
|
private val widgetType: StackWidgetType?
|
||||||
|
private var remoteViews = ArrayList<RemoteViews>()
|
||||||
|
override fun onCreate() {}
|
||||||
|
override fun onDestroy() {}
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return habitIds.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDimensionsFromOptions(
|
||||||
|
ctx: Context,
|
||||||
|
options: Bundle
|
||||||
|
): WidgetDimensions {
|
||||||
|
val maxWidth = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH).toFloat()
|
||||||
|
).toInt()
|
||||||
|
val maxHeight = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT).toFloat()
|
||||||
|
).toInt()
|
||||||
|
val minWidth = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat()
|
||||||
|
).toInt()
|
||||||
|
val minHeight = dpToPixels(
|
||||||
|
ctx,
|
||||||
|
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat()
|
||||||
|
).toInt()
|
||||||
|
return WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewAt(position: Int): RemoteViews? {
|
||||||
|
Log.i("StackRemoteViewsFactory", "getViewAt $position")
|
||||||
|
return if (position < 0 || position > remoteViews.size) null else remoteViews[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun constructWidget(
|
||||||
|
habit: Habit,
|
||||||
|
prefs: Preferences
|
||||||
|
): BaseWidget {
|
||||||
|
when (widgetType) {
|
||||||
|
StackWidgetType.CHECKMARK -> return CheckmarkWidget(context, widgetId, habit)
|
||||||
|
StackWidgetType.FREQUENCY -> return FrequencyWidget(
|
||||||
|
context,
|
||||||
|
widgetId,
|
||||||
|
habit,
|
||||||
|
prefs.firstWeekdayInt
|
||||||
|
)
|
||||||
|
StackWidgetType.SCORE -> return ScoreWidget(context, widgetId, habit)
|
||||||
|
StackWidgetType.HISTORY -> return HistoryWidget(context, widgetId, habit)
|
||||||
|
StackWidgetType.STREAKS -> return StreakWidget(context, widgetId, habit)
|
||||||
|
}
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLoadingView(): RemoteViews {
|
||||||
|
val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId)
|
||||||
|
val widget = EmptyWidget(context, widgetId)
|
||||||
|
widget.setDimensions(getDimensionsFromOptions(context, options))
|
||||||
|
val landscapeViews = widget.landscapeRemoteViews
|
||||||
|
val portraitViews = widget.portraitRemoteViews
|
||||||
|
return RemoteViews(landscapeViews, portraitViews)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewTypeCount(): Int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(position: Int): Long {
|
||||||
|
return habitIds[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasStableIds(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDataSetChanged() {
|
||||||
|
Log.i("StackRemoteViewsFactory", "onDataSetChanged started")
|
||||||
|
val app = context.applicationContext as HabitsApplication
|
||||||
|
val prefs = app.component.preferences
|
||||||
|
val habitList = app.component.habitList
|
||||||
|
val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId)
|
||||||
|
val newRemoteViews = ArrayList<RemoteViews>()
|
||||||
|
if (Looper.myLooper() == null) Looper.prepare()
|
||||||
|
for (id in habitIds) {
|
||||||
|
val h = habitList.getById(id) ?: throw HabitNotFoundException()
|
||||||
|
val widget = constructWidget(h, prefs)
|
||||||
|
widget.setDimensions(getDimensionsFromOptions(context, options))
|
||||||
|
val landscapeViews = widget.landscapeRemoteViews
|
||||||
|
val portraitViews = widget.portraitRemoteViews
|
||||||
|
newRemoteViews.add(RemoteViews(landscapeViews, portraitViews))
|
||||||
|
Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget $id")
|
||||||
|
}
|
||||||
|
remoteViews = newRemoteViews
|
||||||
|
Log.i("StackRemoteViewsFactory", "onDataSetChanged ended")
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
widgetId = intent.getIntExtra(
|
||||||
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
|
AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
)
|
||||||
|
val widgetTypeValue = intent.getIntExtra(StackWidgetService.WIDGET_TYPE, -1)
|
||||||
|
val habitIdsStr = intent.getStringExtra(StackWidgetService.HABIT_IDS)
|
||||||
|
if (widgetTypeValue < 0) throw RuntimeException("invalid widget type")
|
||||||
|
if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null")
|
||||||
|
widgetType = StackWidgetType.Companion.getWidgetTypeFromValue(widgetTypeValue)
|
||||||
|
habitIds = splitLongs(habitIdsStr)
|
||||||
|
}
|
||||||
|
}
|
@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by victoryu on 11/3/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum StackWidgetType {
|
|
||||||
|
|
||||||
CHECKMARK(0),
|
|
||||||
FREQUENCY(1),
|
|
||||||
SCORE(2), // habit strength widget
|
|
||||||
HISTORY(3),
|
|
||||||
STREAKS(4),
|
|
||||||
TARGET(5);
|
|
||||||
|
|
||||||
private int value;
|
|
||||||
StackWidgetType(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static StackWidgetType getWidgetTypeFromValue(int value) {
|
|
||||||
if (CHECKMARK.getValue() == value) {
|
|
||||||
return CHECKMARK;
|
|
||||||
} else if (FREQUENCY.getValue() == value) {
|
|
||||||
return FREQUENCY;
|
|
||||||
} else if (SCORE.getValue() == value) {
|
|
||||||
return SCORE;
|
|
||||||
} else if (HISTORY.getValue() == value) {
|
|
||||||
return HISTORY;
|
|
||||||
} else if (STREAKS.getValue() == value) {
|
|
||||||
return STREAKS;
|
|
||||||
} else if (TARGET.getValue() == value) {
|
|
||||||
return TARGET;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getStackWidgetLayoutId(StackWidgetType type) {
|
|
||||||
switch (type) {
|
|
||||||
case CHECKMARK:
|
|
||||||
return R.layout.checkmark_stackview_widget;
|
|
||||||
case FREQUENCY:
|
|
||||||
return R.layout.frequency_stackview_widget;
|
|
||||||
case SCORE:
|
|
||||||
return R.layout.score_stackview_widget;
|
|
||||||
case HISTORY:
|
|
||||||
return R.layout.history_stackview_widget;
|
|
||||||
case STREAKS:
|
|
||||||
return R.layout.streak_stackview_widget;
|
|
||||||
case TARGET:
|
|
||||||
return R.layout.target_stackview_widget;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getStackWidgetAdapterViewId(StackWidgetType type) {
|
|
||||||
switch (type) {
|
|
||||||
case CHECKMARK:
|
|
||||||
return R.id.checkmarkStackWidgetView;
|
|
||||||
case FREQUENCY:
|
|
||||||
return R.id.frequencyStackWidgetView;
|
|
||||||
case SCORE:
|
|
||||||
return R.id.scoreStackWidgetView;
|
|
||||||
case HISTORY:
|
|
||||||
return R.id.historyStackWidgetView;
|
|
||||||
case STREAKS:
|
|
||||||
return R.id.streakStackWidgetView;
|
|
||||||
case TARGET:
|
|
||||||
return R.id.targetStackWidgetView;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getStackWidgetEmptyViewId(StackWidgetType type) {
|
|
||||||
switch (type) {
|
|
||||||
case CHECKMARK:
|
|
||||||
return R.id.checkmarkStackWidgetEmptyView;
|
|
||||||
case FREQUENCY:
|
|
||||||
return R.id.frequencyStackWidgetEmptyView;
|
|
||||||
case SCORE:
|
|
||||||
return R.id.scoreStackWidgetEmptyView;
|
|
||||||
case HISTORY:
|
|
||||||
return R.id.historyStackWidgetEmptyView;
|
|
||||||
case STREAKS:
|
|
||||||
return R.id.streakStackWidgetEmptyView;
|
|
||||||
case TARGET:
|
|
||||||
return R.id.targetStackWidgetEmptyView;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets
|
||||||
|
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by victoryu on 11/3/17.
|
||||||
|
*/
|
||||||
|
enum class StackWidgetType(val value: Int) {
|
||||||
|
CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget
|
||||||
|
HISTORY(3), STREAKS(4), TARGET(5);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getWidgetTypeFromValue(value: Int): StackWidgetType? {
|
||||||
|
return when {
|
||||||
|
CHECKMARK.value == value -> CHECKMARK
|
||||||
|
FREQUENCY.value == value -> FREQUENCY
|
||||||
|
SCORE.value == value -> SCORE
|
||||||
|
HISTORY.value == value -> HISTORY
|
||||||
|
STREAKS.value == value -> STREAKS
|
||||||
|
TARGET.value == value -> TARGET
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStackWidgetLayoutId(type: StackWidgetType?): Int {
|
||||||
|
when (type) {
|
||||||
|
CHECKMARK -> return R.layout.checkmark_stackview_widget
|
||||||
|
FREQUENCY -> return R.layout.frequency_stackview_widget
|
||||||
|
SCORE -> return R.layout.score_stackview_widget
|
||||||
|
HISTORY -> return R.layout.history_stackview_widget
|
||||||
|
STREAKS -> return R.layout.streak_stackview_widget
|
||||||
|
TARGET -> return R.layout.target_stackview_widget
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int {
|
||||||
|
when (type) {
|
||||||
|
CHECKMARK -> return R.id.checkmarkStackWidgetView
|
||||||
|
FREQUENCY -> return R.id.frequencyStackWidgetView
|
||||||
|
SCORE -> return R.id.scoreStackWidgetView
|
||||||
|
HISTORY -> return R.id.historyStackWidgetView
|
||||||
|
STREAKS -> return R.id.streakStackWidgetView
|
||||||
|
TARGET -> return R.id.targetStackWidgetView
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int {
|
||||||
|
when (type) {
|
||||||
|
CHECKMARK -> return R.id.checkmarkStackWidgetEmptyView
|
||||||
|
FREQUENCY -> return R.id.frequencyStackWidgetEmptyView
|
||||||
|
SCORE -> return R.id.scoreStackWidgetEmptyView
|
||||||
|
HISTORY -> return R.id.historyStackWidgetEmptyView
|
||||||
|
STREAKS -> return R.id.streakStackWidgetEmptyView
|
||||||
|
TARGET -> return R.id.targetStackWidgetEmptyView
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,225 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets.views;
|
|
||||||
|
|
||||||
import android.content.*;
|
|
||||||
import android.util.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.activities.common.views.*;
|
|
||||||
import org.isoron.uhabits.core.preferences.*;
|
|
||||||
import org.isoron.uhabits.inject.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.utils.InterfaceUtils.getDimension;
|
|
||||||
|
|
||||||
public class CheckmarkWidgetView extends HabitWidgetView {
|
|
||||||
protected int activeColor;
|
|
||||||
|
|
||||||
protected float percentage;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected String name;
|
|
||||||
|
|
||||||
protected RingView ring;
|
|
||||||
|
|
||||||
protected TextView label;
|
|
||||||
|
|
||||||
protected int entryValue;
|
|
||||||
|
|
||||||
protected int entryState;
|
|
||||||
|
|
||||||
protected boolean isNumerical;
|
|
||||||
|
|
||||||
private Preferences preferences;
|
|
||||||
|
|
||||||
public CheckmarkWidgetView(Context context)
|
|
||||||
{
|
|
||||||
super(context);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CheckmarkWidgetView(Context context, AttributeSet attrs)
|
|
||||||
{
|
|
||||||
super(context, attrs);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refresh()
|
|
||||||
{
|
|
||||||
if (backgroundPaint == null || frame == null || ring == null) return;
|
|
||||||
|
|
||||||
StyledResources res = new StyledResources(getContext());
|
|
||||||
|
|
||||||
int bgColor;
|
|
||||||
int fgColor;
|
|
||||||
|
|
||||||
switch (entryState) {
|
|
||||||
case Entry.YES_MANUAL:
|
|
||||||
case Entry.SKIP:
|
|
||||||
bgColor = activeColor;
|
|
||||||
fgColor = res.getColor(R.attr.highContrastReverseTextColor);
|
|
||||||
setShadowAlpha(0x4f);
|
|
||||||
backgroundPaint.setColor(bgColor);
|
|
||||||
frame.setBackgroundDrawable(background);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Entry.YES_AUTO:
|
|
||||||
case Entry.NO:
|
|
||||||
case Entry.UNKNOWN:
|
|
||||||
default:
|
|
||||||
bgColor = res.getColor(R.attr.cardBgColor);
|
|
||||||
fgColor = res.getColor(R.attr.mediumContrastTextColor);
|
|
||||||
setShadowAlpha(0x00);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ring.setPercentage(percentage);
|
|
||||||
ring.setColor(fgColor);
|
|
||||||
ring.setBackgroundColor(bgColor);
|
|
||||||
ring.setText(getText());
|
|
||||||
|
|
||||||
label.setText(name);
|
|
||||||
label.setTextColor(fgColor);
|
|
||||||
|
|
||||||
requestLayout();
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntryState(int entryState)
|
|
||||||
{
|
|
||||||
this.entryState = entryState;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getText()
|
|
||||||
{
|
|
||||||
if (isNumerical) return NumberButtonViewKt.toShortString(entryValue / 1000.0);
|
|
||||||
switch (entryState) {
|
|
||||||
case Entry.YES_MANUAL:
|
|
||||||
case Entry.YES_AUTO:
|
|
||||||
return getResources().getString(R.string.fa_check);
|
|
||||||
case Entry.SKIP:
|
|
||||||
return getResources().getString(R.string.fa_skipped);
|
|
||||||
case Entry.UNKNOWN:
|
|
||||||
{
|
|
||||||
if (preferences.areQuestionMarksEnabled())
|
|
||||||
return getResources().getString(R.string.fa_question);
|
|
||||||
else
|
|
||||||
getResources().getString(R.string.fa_times);
|
|
||||||
}
|
|
||||||
case Entry.NO:
|
|
||||||
default:
|
|
||||||
return getResources().getString(R.string.fa_times);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActiveColor(int activeColor)
|
|
||||||
{
|
|
||||||
this.activeColor = activeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEntryValue(int entryValue)
|
|
||||||
{
|
|
||||||
this.entryValue = entryValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(@NonNull String name)
|
|
||||||
{
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPercentage(float percentage)
|
|
||||||
{
|
|
||||||
this.percentage = percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNumerical(boolean isNumerical)
|
|
||||||
{
|
|
||||||
this.isNumerical = isNumerical;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
protected Integer getInnerLayoutId()
|
|
||||||
{
|
|
||||||
return R.layout.widget_checkmark;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
|
||||||
{
|
|
||||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
||||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
||||||
|
|
||||||
float w = width;
|
|
||||||
float h = width * 1.25f;
|
|
||||||
float scale = Math.min(width / w, height / h);
|
|
||||||
|
|
||||||
w *= scale;
|
|
||||||
h *= scale;
|
|
||||||
|
|
||||||
if (h < getDimension(getContext(), R.dimen.checkmarkWidget_heightBreakpoint))
|
|
||||||
ring.setVisibility(GONE);
|
|
||||||
else
|
|
||||||
ring.setVisibility(VISIBLE);
|
|
||||||
|
|
||||||
widthMeasureSpec =
|
|
||||||
MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY);
|
|
||||||
heightMeasureSpec =
|
|
||||||
MeasureSpec.makeMeasureSpec((int) h, MeasureSpec.EXACTLY);
|
|
||||||
|
|
||||||
float textSize = 0.15f * h;
|
|
||||||
float maxTextSize = getDimension(getContext(), R.dimen.smallerTextSize);
|
|
||||||
textSize = Math.min(textSize, maxTextSize);
|
|
||||||
|
|
||||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
|
||||||
ring.setTextSize(textSize);
|
|
||||||
ring.setThickness(0.15f * textSize);
|
|
||||||
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
HabitsApplicationComponent appComponent;
|
|
||||||
appComponent = ((HabitsApplication) getContext().getApplicationContext()).getComponent();
|
|
||||||
preferences = appComponent.getPreferences();
|
|
||||||
|
|
||||||
ring = (RingView) findViewById(R.id.scoreRing);
|
|
||||||
label = (TextView) findViewById(R.id.label);
|
|
||||||
|
|
||||||
if (ring != null) ring.setIsTransparencyEnabled(true);
|
|
||||||
|
|
||||||
if (isInEditMode())
|
|
||||||
{
|
|
||||||
percentage = 0.75f;
|
|
||||||
name = "Wake up early";
|
|
||||||
activeColor = PaletteUtils.getAndroidTestColor(6);
|
|
||||||
entryValue = Entry.YES_MANUAL;
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.common.views.RingView
|
||||||
|
import org.isoron.uhabits.activities.habits.list.views.toShortString
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
|
||||||
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.inject.HabitsApplicationComponent
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils.getDimension
|
||||||
|
import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor
|
||||||
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
|
|
||||||
|
class CheckmarkWidgetView : HabitWidgetView {
|
||||||
|
var activeColor: Int = 0
|
||||||
|
|
||||||
|
var percentage = 0f
|
||||||
|
var name: String? = null
|
||||||
|
protected lateinit var ring: RingView
|
||||||
|
protected lateinit var label: TextView
|
||||||
|
var entryValue = 0
|
||||||
|
var entryState = 0
|
||||||
|
var isNumerical = false
|
||||||
|
private var preferences: Preferences? = null
|
||||||
|
|
||||||
|
constructor(context: Context?) : super(context) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
if (backgroundPaint == null || frame == null) return
|
||||||
|
val res = StyledResources(context)
|
||||||
|
val bgColor: Int
|
||||||
|
val fgColor: Int
|
||||||
|
when (entryState) {
|
||||||
|
YES_MANUAL, SKIP -> {
|
||||||
|
bgColor = activeColor
|
||||||
|
fgColor = res.getColor(R.attr.highContrastReverseTextColor)
|
||||||
|
setShadowAlpha(0x4f)
|
||||||
|
backgroundPaint!!.color = bgColor
|
||||||
|
frame!!.setBackgroundDrawable(background)
|
||||||
|
}
|
||||||
|
YES_AUTO, NO, UNKNOWN -> {
|
||||||
|
bgColor = res.getColor(R.attr.cardBgColor)
|
||||||
|
fgColor = res.getColor(R.attr.mediumContrastTextColor)
|
||||||
|
setShadowAlpha(0x00)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
bgColor = res.getColor(R.attr.cardBgColor)
|
||||||
|
fgColor = res.getColor(R.attr.mediumContrastTextColor)
|
||||||
|
setShadowAlpha(0x00)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ring.percentage = percentage
|
||||||
|
ring.color = fgColor
|
||||||
|
ring.setBackgroundColor(bgColor)
|
||||||
|
ring.setText(text)
|
||||||
|
label.text = name
|
||||||
|
label.setTextColor(fgColor)
|
||||||
|
requestLayout()
|
||||||
|
postInvalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val text: String
|
||||||
|
get() = if (isNumerical) {
|
||||||
|
(entryValue / 1000.0).toShortString()
|
||||||
|
} else when (entryState) {
|
||||||
|
YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check)
|
||||||
|
SKIP -> resources.getString(R.string.fa_skipped)
|
||||||
|
UNKNOWN -> {
|
||||||
|
run {
|
||||||
|
if (preferences!!.areQuestionMarksEnabled()) {
|
||||||
|
return resources.getString(R.string.fa_question)
|
||||||
|
} else {
|
||||||
|
resources.getString(R.string.fa_times)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resources.getString(R.string.fa_times)
|
||||||
|
}
|
||||||
|
NO -> resources.getString(R.string.fa_times)
|
||||||
|
else -> resources.getString(R.string.fa_times)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val innerLayoutId: Int
|
||||||
|
get() = R.layout.widget_checkmark
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
var widthMeasureSpec = widthMeasureSpec
|
||||||
|
var heightMeasureSpec = heightMeasureSpec
|
||||||
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
|
var w = width.toFloat()
|
||||||
|
var h = width * 1.25f
|
||||||
|
val scale = Math.min(width / w, height / h)
|
||||||
|
w *= scale
|
||||||
|
h *= scale
|
||||||
|
if (h < getDimension(context, R.dimen.checkmarkWidget_heightBreakpoint)) ring.visibility =
|
||||||
|
GONE else ring.visibility = VISIBLE
|
||||||
|
widthMeasureSpec = MeasureSpec.makeMeasureSpec(w.toInt(), MeasureSpec.EXACTLY)
|
||||||
|
heightMeasureSpec = MeasureSpec.makeMeasureSpec(h.toInt(), MeasureSpec.EXACTLY)
|
||||||
|
var textSize = 0.15f * h
|
||||||
|
val maxTextSize = getDimension(context, R.dimen.smallerTextSize)
|
||||||
|
textSize = Math.min(textSize, maxTextSize)
|
||||||
|
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||||
|
ring.setTextSize(textSize)
|
||||||
|
ring.setThickness(0.15f * textSize)
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
val appComponent: HabitsApplicationComponent
|
||||||
|
appComponent = (context.applicationContext as HabitsApplication).component
|
||||||
|
preferences = appComponent.preferences
|
||||||
|
ring = findViewById<View>(R.id.scoreRing) as RingView
|
||||||
|
label = findViewById<View>(R.id.label) as TextView
|
||||||
|
ring.setIsTransparencyEnabled(true)
|
||||||
|
if (isInEditMode) {
|
||||||
|
percentage = 0.75f
|
||||||
|
name = "Wake up early"
|
||||||
|
activeColor = getAndroidTestColor(6)
|
||||||
|
entryValue = YES_MANUAL
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets.views;
|
|
||||||
|
|
||||||
import android.content.*;
|
|
||||||
import android.view.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
|
|
||||||
public class GraphWidgetView extends HabitWidgetView
|
|
||||||
{
|
|
||||||
|
|
||||||
private final View dataView;
|
|
||||||
|
|
||||||
private TextView title;
|
|
||||||
|
|
||||||
public GraphWidgetView(Context context, View dataView)
|
|
||||||
{
|
|
||||||
super(context);
|
|
||||||
this.dataView = dataView;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public View getDataView()
|
|
||||||
{
|
|
||||||
return dataView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String text)
|
|
||||||
{
|
|
||||||
title.setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
protected Integer getInnerLayoutId()
|
|
||||||
{
|
|
||||||
return R.layout.widget_graph;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
ViewGroup.LayoutParams params =
|
|
||||||
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
|
||||||
dataView.setLayoutParams(params);
|
|
||||||
|
|
||||||
ViewGroup innerFrame = (ViewGroup) findViewById(R.id.innerFrame);
|
|
||||||
innerFrame.addView(dataView);
|
|
||||||
|
|
||||||
title = (TextView) findViewById(R.id.title);
|
|
||||||
title.setVisibility(VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
|
||||||
|
class GraphWidgetView(context: Context?, val dataView: View) : HabitWidgetView(context) {
|
||||||
|
private lateinit var title: TextView
|
||||||
|
fun setTitle(text: String?) {
|
||||||
|
title.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
override val innerLayoutId: Int
|
||||||
|
get() = R.layout.widget_graph
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
val params = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
dataView.layoutParams = params
|
||||||
|
val innerFrame = findViewById<View>(R.id.innerFrame) as ViewGroup
|
||||||
|
innerFrame.addView(dataView)
|
||||||
|
title = findViewById<View>(R.id.title) as TextView
|
||||||
|
title.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
|
||||||
*
|
|
||||||
* This file is part of Loop Habit Tracker.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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.
|
|
||||||
*
|
|
||||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.isoron.uhabits.widgets.views;
|
|
||||||
|
|
||||||
import android.content.*;
|
|
||||||
import android.graphics.*;
|
|
||||||
import android.graphics.drawable.*;
|
|
||||||
import android.graphics.drawable.shapes.*;
|
|
||||||
import android.util.*;
|
|
||||||
import android.view.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.utils.InterfaceUtils.*;
|
|
||||||
|
|
||||||
public abstract class HabitWidgetView extends FrameLayout
|
|
||||||
{
|
|
||||||
@Nullable
|
|
||||||
protected InsetDrawable background;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected Paint backgroundPaint;
|
|
||||||
|
|
||||||
protected ViewGroup frame;
|
|
||||||
|
|
||||||
private int shadowAlpha;
|
|
||||||
|
|
||||||
private StyledResources res;
|
|
||||||
|
|
||||||
private int backgroundAlpha;
|
|
||||||
|
|
||||||
public HabitWidgetView(Context context)
|
|
||||||
{
|
|
||||||
super(context);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public HabitWidgetView(Context context, AttributeSet attrs)
|
|
||||||
{
|
|
||||||
super(context, attrs);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShadowAlpha(int shadowAlpha)
|
|
||||||
{
|
|
||||||
this.shadowAlpha = shadowAlpha;
|
|
||||||
rebuildBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBackgroundAlpha(int backgroundAlpha)
|
|
||||||
{
|
|
||||||
this.backgroundAlpha = backgroundAlpha;
|
|
||||||
rebuildBackground();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract
|
|
||||||
@NonNull
|
|
||||||
Integer getInnerLayoutId();
|
|
||||||
|
|
||||||
public void rebuildBackground()
|
|
||||||
{
|
|
||||||
Context context = getContext();
|
|
||||||
|
|
||||||
int shadowRadius = (int) dpToPixels(context, 2);
|
|
||||||
int shadowOffset = (int) dpToPixels(context, 1);
|
|
||||||
int shadowColor = Color.argb(shadowAlpha, 0, 0, 0);
|
|
||||||
|
|
||||||
float cornerRadius = dpToPixels(context, 5);
|
|
||||||
float[] radii = new float[8];
|
|
||||||
Arrays.fill(radii, cornerRadius);
|
|
||||||
|
|
||||||
RoundRectShape shape = new RoundRectShape(radii, null, null);
|
|
||||||
ShapeDrawable innerDrawable = new ShapeDrawable(shape);
|
|
||||||
|
|
||||||
int insetLeftTop = Math.max(shadowRadius - shadowOffset, 0);
|
|
||||||
int insetRightBottom = shadowRadius + shadowOffset;
|
|
||||||
|
|
||||||
background =
|
|
||||||
new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop,
|
|
||||||
insetRightBottom, insetRightBottom);
|
|
||||||
backgroundPaint = innerDrawable.getPaint();
|
|
||||||
backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset,
|
|
||||||
shadowColor);
|
|
||||||
backgroundPaint.setColor(res.getColor(R.attr.cardBgColor));
|
|
||||||
backgroundPaint.setAlpha(backgroundAlpha);
|
|
||||||
|
|
||||||
frame = (ViewGroup) findViewById(R.id.frame);
|
|
||||||
if (frame != null) frame.setBackground(background);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init()
|
|
||||||
{
|
|
||||||
inflate(getContext(), getInnerLayoutId(), this);
|
|
||||||
res = new StyledResources(getContext());
|
|
||||||
shadowAlpha = (int) (255 * res.getFloat(R.attr.widgetShadowAlpha));
|
|
||||||
rebuildBackground();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||||
|
*
|
||||||
|
* This file is part of Loop Habit Tracker.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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.
|
||||||
|
*
|
||||||
|
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.widgets.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.drawable.InsetDrawable
|
||||||
|
import android.graphics.drawable.ShapeDrawable
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
|
||||||
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
|
abstract class HabitWidgetView : FrameLayout {
|
||||||
|
protected var background: InsetDrawable? = null
|
||||||
|
protected var backgroundPaint: Paint? = null
|
||||||
|
protected var frame: ViewGroup? = null
|
||||||
|
private var shadowAlpha = 0
|
||||||
|
private var res: StyledResources? = null
|
||||||
|
private var backgroundAlpha = 0
|
||||||
|
|
||||||
|
constructor(context: Context?) : super(context!!) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context?, attrs: AttributeSet?) : super(
|
||||||
|
context!!,
|
||||||
|
attrs
|
||||||
|
) {
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShadowAlpha(shadowAlpha: Int) {
|
||||||
|
this.shadowAlpha = shadowAlpha
|
||||||
|
rebuildBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBackgroundAlpha(backgroundAlpha: Int) {
|
||||||
|
this.backgroundAlpha = backgroundAlpha
|
||||||
|
rebuildBackground()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract val innerLayoutId: Int
|
||||||
|
fun rebuildBackground() {
|
||||||
|
val context = context
|
||||||
|
val shadowRadius = dpToPixels(context, 2f).toInt()
|
||||||
|
val shadowOffset = dpToPixels(context, 1f).toInt()
|
||||||
|
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
|
||||||
|
val cornerRadius = dpToPixels(context, 5f)
|
||||||
|
val radii = FloatArray(8)
|
||||||
|
Arrays.fill(radii, cornerRadius)
|
||||||
|
val shape = RoundRectShape(radii, null, null)
|
||||||
|
val innerDrawable = ShapeDrawable(shape)
|
||||||
|
val insetLeftTop = Math.max(shadowRadius - shadowOffset, 0)
|
||||||
|
val insetRightBottom = shadowRadius + shadowOffset
|
||||||
|
background = InsetDrawable(
|
||||||
|
innerDrawable,
|
||||||
|
insetLeftTop,
|
||||||
|
insetLeftTop,
|
||||||
|
insetRightBottom,
|
||||||
|
insetRightBottom
|
||||||
|
)
|
||||||
|
backgroundPaint = innerDrawable.paint
|
||||||
|
backgroundPaint?.setShadowLayer(
|
||||||
|
shadowRadius.toFloat(),
|
||||||
|
shadowOffset.toFloat(),
|
||||||
|
shadowOffset.toFloat(),
|
||||||
|
shadowColor
|
||||||
|
)
|
||||||
|
backgroundPaint?.color = res!!.getColor(R.attr.cardBgColor)
|
||||||
|
backgroundPaint?.alpha = backgroundAlpha
|
||||||
|
frame = findViewById<View>(R.id.frame) as ViewGroup
|
||||||
|
if (frame != null) frame!!.background = background
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
inflate(context, innerLayoutId, this)
|
||||||
|
res = StyledResources(context)
|
||||||
|
shadowAlpha = (255 * res!!.getFloat(R.attr.widgetShadowAlpha)).toInt()
|
||||||
|
rebuildBackground()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue