1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2025-01-24 00:27:33 +01:00

Fossil Hybrid HR: Add watchface preview images in the app manager

This commit is contained in:
Arjan Schrijver 2022-06-16 23:53:58 +02:00
parent ec5e51b9a9
commit 48212d4185
7 changed files with 226 additions and 70 deletions

View File

@ -23,6 +23,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
@ -47,7 +49,6 @@ import java.util.UUID;
import androidx.fragment.app.Fragment;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
@ -61,6 +62,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GridAutoFitLayoutManager;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
@ -122,8 +124,9 @@ public abstract class AbstractAppManagerFragment extends Fragment {
String appCreator = intent.getStringExtra("app_creator" + i);
UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i));
GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i, 0)];
Bitmap previewImage = getAppPreviewImage(uuid.toString());
GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType);
GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType, previewImage);
app.setOnDevice(true);
if (filterApp(app)) {
appList.add(app);
@ -131,6 +134,20 @@ public abstract class AbstractAppManagerFragment extends Fragment {
}
}
private Bitmap getAppPreviewImage(String name) {
Bitmap previewImage = null;
try {
File cacheDir = mCoordinator.getAppCacheDir();
File previewImgFile = new File(cacheDir, name + "_preview.png");
if (previewImgFile.exists()) {
previewImage = BitmapFactory.decodeFile(previewImgFile.getAbsolutePath());
}
} catch (IOException e) {
LOG.warn("Couldn't load watch app preview image", e);
}
return previewImage;
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -183,7 +200,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
try {
String jsonstring = FileUtils.getStringFromFile(jsonFile);
JSONObject json = new JSONObject(jsonstring);
cachedAppList.add(new GBDeviceApp(json, configFile.exists()));
cachedAppList.add(new GBDeviceApp(json, configFile.exists(), getAppPreviewImage(baseName)));
} catch (Exception e) {
LOG.info("could not read json file for " + baseName);
if (mGBDevice.getType() == DeviceType.PEBBLE) {
@ -293,8 +310,8 @@ public abstract class AbstractAppManagerFragment extends Fragment {
}
}
});
appListView.setLayoutManager(new LinearLayoutManager(getActivity()));
mGBDeviceAppAdapter = new GBDeviceAppAdapter(appList, R.layout.item_pebble_watchapp, this);
appListView.setLayoutManager(new GridAutoFitLayoutManager(getActivity(), 300));
mGBDeviceAppAdapter = new GBDeviceAppAdapter(appList, R.layout.item_appmanager_watchapp, this);
appListView.setAdapter(mGBDeviceAppAdapter);
ItemTouchHelper.Callback appItemTouchHelperCallback = new AppItemTouchHelperCallback(mGBDeviceAppAdapter);

View File

@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@ -29,6 +30,10 @@ import java.util.List;
import java.util.UUID;
import androidx.recyclerview.widget.RecyclerView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
@ -39,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
*/
public class GBDeviceAppAdapter extends RecyclerView.Adapter<GBDeviceAppAdapter.AppViewHolder> {
private final Logger LOG = LoggerFactory.getLogger(GBDeviceAppAdapter.class);
private final int mLayoutId;
private final List<GBDeviceApp> appList;
@ -79,6 +85,14 @@ public class GBDeviceAppAdapter extends RecyclerView.Adapter<GBDeviceAppAdapter.
String appNameLabelText = deviceApp.getName();
holder.mDeviceAppNameLabel.setText(appNameLabelText);
Bitmap previewImage = deviceApp.getPreviewImage();
holder.mPreviewImage.setImageBitmap(previewImage);
if (previewImage == null) {
holder.mPreviewImage.setVisibility(View.GONE);
} else {
holder.mPreviewImage.setVisibility(View.VISIBLE);
}
switch (deviceApp.getType()) {
case APP_GENERIC:
holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp);
@ -130,6 +144,7 @@ public class GBDeviceAppAdapter extends RecyclerView.Adapter<GBDeviceAppAdapter.
final TextView mDeviceAppNameLabel;
final ImageView mDeviceImageView;
final ImageView mDragHandle;
final ImageView mPreviewImage;
AppViewHolder(View itemView) {
super(itemView);
@ -137,6 +152,7 @@ public class GBDeviceAppAdapter extends RecyclerView.Adapter<GBDeviceAppAdapter.
mDeviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name);
mDeviceImageView = (ImageView) itemView.findViewById(R.id.item_image);
mDragHandle = (ImageView) itemView.findViewById(R.id.drag_handle);
mPreviewImage = (ImageView) itemView.findViewById(R.id.item_preview_image);
}
}

View File

@ -165,7 +165,7 @@ public class FossilFileReader {
} else {
mAppKeys.put("type", "APP_GENERIC");
}
app = new GBDeviceApp(mAppKeys, false);
app = new GBDeviceApp(mAppKeys, false, null);
}
private ArrayList<String> parseAppFilenames(ByteBuffer buf, int untilPosition, boolean cutTrailingNull) {

View File

@ -17,6 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.impl;
import android.graphics.Bitmap;
import org.json.JSONException;
import org.json.JSONObject;
@ -31,6 +33,20 @@ public class GBDeviceApp {
private final boolean inCache;
private boolean isOnDevice;
private final boolean configurable;
private final Bitmap previewImage;
public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type, Bitmap previewImage) {
this.uuid = uuid;
this.name = name;
this.creator = creator;
this.version = version;
this.type = type;
this.previewImage = previewImage;
//FIXME: do not assume
this.inCache = false;
this.configurable = false;
this.isOnDevice = false;
}
public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) {
this.uuid = uuid;
@ -38,13 +54,14 @@ public class GBDeviceApp {
this.creator = creator;
this.version = version;
this.type = type;
this.previewImage = null;
//FIXME: do not assume
this.inCache = false;
this.configurable = false;
this.isOnDevice = false;
}
public GBDeviceApp(JSONObject json, boolean configurable) {
public GBDeviceApp(JSONObject json, boolean configurable, Bitmap previewImage) {
UUID uuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
String name = "";
String creator = "";
@ -66,6 +83,7 @@ public class GBDeviceApp {
this.creator = creator;
this.version = version;
this.type = type;
this.previewImage = previewImage;
//FIXME: do not assume
this.inCache = true;
this.configurable = configurable;
@ -103,6 +121,10 @@ public class GBDeviceApp {
return type;
}
public Bitmap getPreviewImage() {
return previewImage;
}
public enum Type {
UNKNOWN,
WATCHFACE,

View File

@ -0,0 +1,77 @@
// https://gist.github.com/omidraha/af3aa017d4ec06342bdc03c49d4b83b1
// https://stackoverflow.com/a/30256880/538284
// https://stackoverflow.com/a/42241730/538284
// https://stackoverflow.com/a/38082715/538284
package nodomain.freeyourgadget.gadgetbridge.util;
import android.content.Context;
import android.util.TypedValue;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class GridAutoFitLayoutManager extends GridLayoutManager {
private int mColumnWidth;
private boolean mColumnWidthChanged = true;
private boolean mWidthChanged = true;
private int mWidth;
private static final int sColumnWidth = 200; // assume cell width of 200dp
public GridAutoFitLayoutManager(Context context, int columnWidth) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}
public GridAutoFitLayoutManager(Context context, int columnWidth, int orientation, boolean reverseLayout) {
/* Initially set spanCount to 1, will be changed automatically later. */
super(context, 1, orientation, reverseLayout);
setColumnWidth(checkedColumnWidth(context, columnWidth));
}
private int checkedColumnWidth(Context context, int columnWidth) {
if (columnWidth <= 0) {
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sColumnWidth,
context.getResources().getDisplayMetrics());
} else {
columnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, columnWidth,
context.getResources().getDisplayMetrics());
}
return columnWidth;
}
private void setColumnWidth(int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != mColumnWidth) {
mColumnWidth = newColumnWidth;
mColumnWidthChanged = true;
}
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
int width = getWidth();
int height = getHeight();
if (width != mWidth) {
mWidthChanged = true;
mWidth = width;
}
if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0
|| mWidthChanged) {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = width - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = height - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / mColumnWidth);
setSpanCount(spanCount);
mColumnWidthChanged = false;
mWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/appmanager_item_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
app:cardCornerRadius="3dp"
app:cardElevation="3dp"
app:contentPadding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/item_preview_image"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:minHeight="60dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_centerVertical="true"
android:contentDescription="@string/candidate_item_device_image" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@+id/item_image"
android:layout_toStartOf="@+id/drag_handle"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingTop="8dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="Item Name"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Item Description"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
<ImageView
android:id="@+id/drag_handle"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
android:layout_marginTop="8dp"
android:layout_centerVertical="true"
android:contentDescription="drag handle"
app:tint="@color/secondarytext"
app:srcCompat="@drawable/ic_drag_handle" />
</RelativeLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:minHeight="60dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_centerVertical="true"
android:contentDescription="@string/candidate_item_device_image" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_toEndOf="@+id/item_image"
android:layout_toStartOf="@+id/drag_handle"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingTop="8dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:scrollHorizontally="false"
android:text="Item Name"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Item Description"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
<ImageView
android:id="@+id/drag_handle"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
android:layout_marginTop="8dp"
android:layout_centerVertical="true"
android:contentDescription="drag handle"
android:tint="@color/secondarytext"
app:srcCompat="@drawable/ic_drag_handle" />
</RelativeLayout>