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