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:
parent
ec5e51b9a9
commit
48212d4185
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
87
app/src/main/res/layout/item_appmanager_watchapp.xml
Normal file
87
app/src/main/res/layout/item_appmanager_watchapp.xml
Normal 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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user