mirror of
https://github.com/alesimula/Murine-launcher.git
synced 2026-10-28 09:43:42 +00:00
CLOCK - show preview for clock widget
This commit is contained in:
1 parent
33095adfbb
commit
16f6ec6f26
5 files changed
+128
-11
No files matched your search
@@ -1,13 +1,7 @@
|
||||
package app.murinelauncher.widget.smartspace
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.icu.text.DateFormat
|
||||
import android.icu.text.DisplayContext
|
||||
import android.provider.AlarmClock
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextClock
|
||||
@@ -82,6 +76,16 @@ class MurineClockView @JvmOverloads constructor(
|
||||
clock.format24Hour = pattern
|
||||
}
|
||||
|
||||
/**
|
||||
* Will just cause it to refresh and correctly show the hour (useful for showing widget preview)
|
||||
*/
|
||||
fun refreshClockFormat() {
|
||||
listOfNotNull(clockView, dateText).forEach { tc ->
|
||||
tc.format12Hour = tc.format12Hour
|
||||
tc.format24Hour = tc.format24Hour
|
||||
}
|
||||
}
|
||||
|
||||
fun setTextColor(color: Int) {
|
||||
clockView?.setTextColor(color)
|
||||
dateText?.setTextColor(color)
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.systemui.plugins.CustomWidgetPlugin;
|
||||
|
||||
/**
|
||||
@@ -29,14 +30,36 @@ public class MurineClockWidgetPlugin implements CustomWidgetPlugin {
|
||||
parent.addView(clockView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useLivePreview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View createPreviewView(Context context) {
|
||||
View view = LayoutInflater.from(context)
|
||||
.inflate(R.layout.murine_clock_widget, null, false);
|
||||
if (view instanceof MurineClockView murineClock) murineClock.refreshClockFormat();
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateWidgetInfo(AppWidgetProviderInfo info, Context context) {
|
||||
info.label = context.getString(R.string.smartspace_mode_clock);
|
||||
if (info instanceof LauncherAppWidgetProviderInfo wpi) {
|
||||
wpi.forceResizableX = true;
|
||||
wpi.forceResizableY = true;
|
||||
// 99 is just unrealistically wide enough in order to always pick the workspace width
|
||||
wpi.spanX = 99;
|
||||
wpi.spanY = 1;
|
||||
wpi.minSpanX = 1;
|
||||
wpi.minSpanY = 1;
|
||||
}
|
||||
info.minResizeWidth = 250;
|
||||
info.minResizeHeight = 48;
|
||||
info.minWidth = 250;
|
||||
info.minHeight = 48;
|
||||
info.resizeMode = AppWidgetProviderInfo.RESIZE_HORIZONTAL;
|
||||
info.resizeMode = AppWidgetProviderInfo.RESIZE_HORIZONTAL | AppWidgetProviderInfo.RESIZE_VERTICAL;
|
||||
info.icon = R.drawable.ic_pref_smartspace_clock;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
@@ -33,9 +34,11 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.os.BuildCompat;
|
||||
|
||||
@@ -52,6 +55,7 @@ import com.android.launcher3.util.CancellableTask;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
import com.android.launcher3.widget.util.WidgetSizes;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -155,6 +159,14 @@ public class DatabaseWidgetPreviewLoader {
|
||||
// Load the preview image if possible
|
||||
if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
|
||||
|
||||
if (info.isCustomWidget()) {
|
||||
Bitmap livePreview = generateCustomWidgetPreview(info, maxPreviewWidth, preScaledWidthOut);
|
||||
if (livePreview != null) {
|
||||
return livePreview;
|
||||
}
|
||||
// Otherwise fall through to the standard (icon) preview.
|
||||
}
|
||||
|
||||
Drawable drawable = null;
|
||||
if (info.previewImage != 0) {
|
||||
try {
|
||||
@@ -176,14 +188,15 @@ public class DatabaseWidgetPreviewLoader {
|
||||
}
|
||||
|
||||
final boolean widgetPreviewExists = (drawable != null);
|
||||
final int spanX = info.spanX;
|
||||
final int spanY = info.spanY;
|
||||
DeviceProfile dp = ActivityContext.lookupContext(mContext).getDeviceProfile();
|
||||
// Clamp the span to the current grid (and honor min span) so a custom widget that declares
|
||||
// an intentionally-large "full width" span doesn't produce an oversized preview.
|
||||
final int spanX = Math.max(1, Math.min(Math.max(info.spanX, info.minSpanX), dp.inv.numColumns));
|
||||
final int spanY = Math.max(1, Math.min(Math.max(info.spanY, info.minSpanY), dp.inv.numRows));
|
||||
|
||||
int previewWidth;
|
||||
int previewHeight;
|
||||
|
||||
DeviceProfile dp = ActivityContext.lookupContext(mContext).getDeviceProfile();
|
||||
|
||||
if (widgetPreviewExists && drawable.getIntrinsicWidth() > 0
|
||||
&& drawable.getIntrinsicHeight() > 0) {
|
||||
previewWidth = drawable.getIntrinsicWidth();
|
||||
@@ -273,6 +286,51 @@ public class DatabaseWidgetPreviewLoader {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a custom widget's live preview view (supplied by its plugin) to a bitmap. Returns
|
||||
* {@code null} if the plugin provides no preview view or rendering fails, so the caller can fall
|
||||
* back to the standard preview. The view is inflated and drawn on the UI thread.
|
||||
*/
|
||||
@Nullable
|
||||
private Bitmap generateCustomWidgetPreview(LauncherAppWidgetProviderInfo info,
|
||||
int maxPreviewWidth, int[] preScaledWidthOut) {
|
||||
CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
|
||||
DeviceProfile dp = ActivityContext.lookupContext(mContext).getDeviceProfile();
|
||||
// Clamp the span to the current grid (and honor min span) so a custom widget that declares
|
||||
// an intentionally-large "full width" span doesn't produce an oversized preview.
|
||||
int spanX = Math.max(1, Math.min(Math.max(info.spanX, info.minSpanX), dp.inv.numColumns));
|
||||
int spanY = Math.max(1, Math.min(Math.max(info.spanY, info.minSpanY), dp.inv.numRows));
|
||||
Size widgetSize = WidgetSizes.getWidgetSizePx(dp, spanX, spanY);
|
||||
final int width = Math.max(widgetSize.getWidth(), 1);
|
||||
final int height = Math.max(widgetSize.getHeight(), 1);
|
||||
if (preScaledWidthOut != null) {
|
||||
preScaledWidthOut[0] = width;
|
||||
}
|
||||
final float scale = width > maxPreviewWidth ? (maxPreviewWidth / (float) width) : 1f;
|
||||
final int scaledWidth = Math.max((int) (scale * width), 1);
|
||||
final int scaledHeight = Math.max((int) (scale * height), 1);
|
||||
try {
|
||||
return MAIN_EXECUTOR.submit(() -> {
|
||||
View view = cwm.createCustomWidgetPreview(mContext, info.provider);
|
||||
if (view == null) {
|
||||
return null;
|
||||
}
|
||||
view.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
|
||||
view.layout(0, 0, width, height);
|
||||
Bitmap bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
if (scale != 1f) canvas.scale(scale, scale);
|
||||
view.draw(canvas);
|
||||
return bitmap;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to render custom widget live preview for " + info.provider, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap generateShortcutPreview(
|
||||
ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) {
|
||||
int iconSize = ActivityContext.lookupContext(mContext).getDeviceProfile().allAppsIconSizePx;
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -160,6 +161,20 @@ public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin> {
|
||||
return mCustomWidgets.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the live preview view supplied by the plugin for the given custom widget provider, or
|
||||
* {@code null} if there is no plugin, it hasn't opted into a live preview, or it supplies none.
|
||||
* Must be called on the UI thread.
|
||||
*/
|
||||
@Nullable
|
||||
public View createCustomWidgetPreview(Context context, ComponentName provider) {
|
||||
CustomWidgetPlugin plugin = mPlugins.get(provider);
|
||||
if (plugin == null || !plugin.useLivePreview()) {
|
||||
return null;
|
||||
}
|
||||
return plugin.createPreviewView(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the widget provider in respect to given widget id.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.systemui.plugins;
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.plugins.annotations.ProvidesInterface;
|
||||
|
||||
@@ -51,4 +52,20 @@ public interface CustomWidgetPlugin extends Plugin {
|
||||
* Used to modify a widgets' info.
|
||||
*/
|
||||
default void updateWidgetInfo(AppWidgetProviderInfo info, Context context) { }
|
||||
|
||||
/**
|
||||
* Whether this widget should render a live preview (via {@link #createPreviewView}) in the
|
||||
* picker instead of the default icon preview. Off unless a plugin opts in.
|
||||
*/
|
||||
default boolean useLivePreview() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view rendered as the widget's preview in the picker, or {@code null} to fall back
|
||||
* to the standard icon preview. Called on the UI thread, and only if {@link #useLivePreview()}.
|
||||
*/
|
||||
default View createPreviewView(Context context) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user