ControlCenter: added folders to ControlCenter

This commit is contained in:
Daniel Dakhno 2022-06-23 23:12:08 +02:00 committed by Gitea
parent 3d6940154d
commit 62f77ef8d0
17 changed files with 505 additions and 147 deletions

View File

@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
final Schema schema = new Schema(38, MAIN_PACKAGE + ".entities");
final Schema schema = new Schema(42, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@ -181,8 +181,9 @@ public class GBDaoGenerator {
device.addStringProperty("manufacturer").notNull();
device.addStringProperty("identifier").notNull().unique().javaDocGetterAndSetter("The fixed identifier, i.e. MAC address of the device.");
device.addIntProperty("type").notNull().javaDocGetterAndSetter("The DeviceType key, i.e. the GBDevice's type.");
device.addStringProperty("model").javaDocGetterAndSetter("An optional model, further specifying the kind of device-");
device.addStringProperty("model").javaDocGetterAndSetter("An optional model, further specifying the kind of device.");
device.addStringProperty("alias");
device.addStringProperty("parentFolder").javaDocGetterAndSetter("Folder name containing this device.");
Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty();
// sorted by the from-date, newest first
Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC);

View File

@ -121,6 +121,7 @@ public class ControlCenterv2 extends AppCompatActivity
case DeviceManager.ACTION_DEVICES_CHANGED:
case GBApplication.ACTION_NEW_DATA:
createRefreshTask("get activity data", getApplication()).execute();
mGBDeviceAdapter.rebuildFolders();
refreshPairedDevices();
break;
case DeviceService.ACTION_REALTIME_SAMPLES:

View File

@ -769,7 +769,7 @@ public class DebugActivity extends AbstractGBActivity {
try (
DBHandler db = GBApplication.acquireDB()) {
DaoSession daoSession = db.getDaoSession();
GBDevice gbDevice = new GBDevice(deviceMac, deviceType.name(), "", deviceType);
GBDevice gbDevice = new GBDevice(deviceMac, deviceType.name(), "", null, deviceType);
gbDevice.setFirmwareVersion("N/A");
gbDevice.setFirmwareVersion2("N/A");

View File

@ -23,13 +23,17 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.text.InputType;
import android.text.TextUtils;
import android.transition.TransitionManager;
import android.util.ArraySet;
import android.util.Pair;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
@ -40,6 +44,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
@ -47,6 +52,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.RecyclerView;
@ -55,6 +61,7 @@ import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.utils.MPPointF;
import com.google.android.flexbox.FlexboxLayout;
import com.google.android.material.snackbar.Snackbar;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
@ -68,6 +75,7 @@ import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -78,7 +86,6 @@ import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
@ -90,6 +97,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceFolder;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
@ -99,6 +107,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
/**
* Adapter for displaying GBDevice instances.
@ -108,16 +117,52 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
private final Context context;
private List<GBDevice> deviceList;
private List<GBDevice> devicesListWithFolders;
private String expandedDeviceAddress = "";
private String expandedFolderName = "";
private ViewGroup parent;
private HashMap<String, long[]> deviceActivityMap = new HashMap();
public GBDeviceAdapterv2(Context context, List<GBDevice> deviceList, HashMap<String,long[]> deviceMap) {
this.context = context;
this.deviceList = deviceList;
rebuildFolders();
this.deviceActivityMap = deviceMap;
}
public void rebuildFolders(){
this.devicesListWithFolders = enrichDeviceListWithFolder(deviceList);
}
private List<GBDevice> enrichDeviceListWithFolder(List<GBDevice> deviceList) {
ArrayList<GBDevice> enrichedList = new ArrayList<>();
Set<String> folders = new ArraySet<>();
for(GBDevice device : deviceList){
String parentFolder = device.getParentFolder();
if(StringUtils.isNullOrEmpty(parentFolder)){
enrichedList.add(device);
continue;
}
folders.add(parentFolder);
}
for(String folder : folders){
enrichedList.add(new GBDeviceFolder(folder));
for(GBDevice potentialChild : deviceList){
String parentFolder = potentialChild.getParentFolder();
if(StringUtils.isNullOrEmpty(parentFolder)){
continue;
}
if(!parentFolder.equals(folder)){
continue;
}
enrichedList.add(potentialChild);
}
}
return enrichedList;
}
@NonNull
@Override
public GBDeviceAdapterv2.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -126,9 +171,94 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
return new ViewHolder(view);
}
private int countDevicesInFolder(String folderName, boolean needsToBeConnected){
int count = 0;
for(GBDevice device : deviceList){
if(folderName.equals(device.getParentFolder()) && ((!needsToBeConnected) || device.isConnected())){
count++;
}
}
return count;
}
private void showDeviceFolder(ViewHolder holder, final GBDeviceFolder folder){
holder.container.setVisibility(View.VISIBLE);
holder.deviceNameLabel.setText(folder.getName());
holder.infoIcons.setVisibility(View.GONE);
holder.cardViewActivityCardLayout.setVisibility(View.GONE);
if(countDevicesInFolder(folder.getName(), true) == 0){
holder.deviceImageView.setImageResource(R.drawable.ic_device_folder_disabled);
}else{
holder.deviceImageView.setImageResource(R.drawable.ic_device_folder);
}
holder.deviceInfoView.setVisibility(View.GONE);
int countInFolder = countDevicesInFolder(folder.getName(), false);
int connectedInFolder = countDevicesInFolder(folder.getName(), true);
holder.deviceStatusLabel.setText(context.getString(R.string.controlcenter_connected_fraction, connectedInFolder, countInFolder));
holder.container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(expandedFolderName.equals(folder.getName())){
// collapse open folder
expandedFolderName = "";
}else {
expandedFolderName = folder.getName();
}
notifyDataSetChanged();
}
});
holder.container.setOnLongClickListener(null);
}
private void setItemMargin(ViewHolder holder, GBDevice device){
Resources r = context.getResources();
int widthDp = 8;
if(!StringUtils.isNullOrEmpty(device.getParentFolder())){
widthDp = 16;
}
float px = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
widthDp,
r.getDisplayMetrics()
);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) holder.container.getLayoutParams();
layoutParams.setMarginStart((int) px);
holder.container.setLayoutParams(layoutParams);
int alpha = 0;
if(device instanceof GBDeviceFolder && device.getName().equals(expandedFolderName)){
alpha = 50;
}else if(!StringUtils.isNullOrEmpty(device.getParentFolder()) && expandedFolderName.equals(device.getParentFolder())){
alpha = 50;
}
holder.root.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
final GBDevice device = deviceList.get(position);
final GBDevice device = devicesListWithFolders.get(position);
setItemMargin(holder, device);
if(device instanceof GBDeviceFolder){
showDeviceFolder(holder, (GBDeviceFolder) device);
return;
}
String parentFolder = device.getParentFolder();
if(!StringUtils.isNullOrEmpty(parentFolder)){
if(parentFolder.equals(expandedFolderName)){
holder.container.setVisibility(View.VISIBLE);
}else{
holder.container.setVisibility(View.GONE);
}
}else{
holder.container.setVisibility(View.VISIBLE);
}
long[] dailyTotals = new long[]{0, 0};
if (deviceActivityMap.containsKey(device.getAddress())) {
dailyTotals = deviceActivityMap.get(device.getAddress());
@ -152,10 +282,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
holder.container.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect(device);
}
showDeviceSubmenu(v, device);
return true;
}
});
@ -385,21 +512,19 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
justifyListViewHeightBasedOnChildren(holder.deviceInfoList);
holder.deviceInfoList.setFocusable(false);
holder.infoIcons.setVisibility(View.VISIBLE);
final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
holder.deviceInfoView.setVisibility(showInfoIcon ? View.VISIBLE : View.GONE);
holder.deviceInfoBox.setActivated(detailsShown);
holder.deviceInfoBox.setVisibility(detailsShown ? View.VISIBLE : View.GONE);
holder.deviceInfoView.setVisibility(View.VISIBLE);
holder.deviceInfoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expandedDeviceAddress = detailsShown ? "" : device.getAddress();
TransitionManager.beginDelayedTransition(parent);
notifyDataSetChanged();
}
}
);
@Override
public void onClick(View v) {
showDeviceSubmenu(v, device);
}
});
holder.findDevice.setVisibility(device.isInitialized() && coordinator.supportsFindDevice() ? View.VISIBLE : View.GONE);
holder.findDevice.setOnClickListener(new View.OnClickListener() {
@ -668,91 +793,6 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
});
}
//remove device, hidden under details
holder.removeDevice.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
new AlertDialog.Builder(context)
.setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName()))
.setMessage(R.string.controlcenter_delete_device_dialogmessage)
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
if (coordinator != null) {
coordinator.deleteDevice(device);
}
DeviceHelper.getInstance().removeBond(device);
} catch (Exception ex) {
GB.toast(context, "Error deleting device: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
});
//set alias, hidden under details
holder.setAlias.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
final EditText input = new EditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(device.getAlias());
FrameLayout container = new FrameLayout(context);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
input.setLayoutParams(params);
container.addView(input);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
new AlertDialog.Builder(context)
.setView(container)
.setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_set_alias))
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
Device dbDevice = DBHelper.getDevice(device, session);
String alias = input.getText().toString();
dbDevice.setAlias(alias);
dbDevice.update();
device.setAlias(alias);
} catch (Exception ex) {
GB.toast(context, context.getString(R.string.error_setting_alias) + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
});
holder.cardViewActivityCardLayout.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
holder.cardViewActivityCardLayout.setMinimumWidth(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
@ -761,13 +801,182 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
}
}
private void showDeviceSubmenu(final View v, final GBDevice device) {
boolean deviceConnected = device.getState() != GBDevice.State.NOT_CONNECTED;
PopupMenu menu = new PopupMenu(v.getContext(), v);
menu.inflate(R.menu.activity_controlcenterv2_device_submenu);
final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
menu.getMenu().findItem(R.id.controlcenter_device_submenu_connect).setVisible(!deviceConnected);
menu.getMenu().findItem(R.id.controlcenter_device_submenu_disconnect).setVisible(deviceConnected);
menu.getMenu().findItem(R.id.controlcenter_device_submenu_show_details).setEnabled(showInfoIcon);
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()){
case R.id.controlcenter_device_submenu_connect:
if (device.getState() != GBDevice.State.CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
GBApplication.deviceService().connect(device);
}
return true;
case R.id.controlcenter_device_submenu_disconnect:
if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect(device);
}
return true;
case R.id.controlcenter_device_submenu_set_alias:
showSetAliasDialog(device);
return true;
case R.id.controlcenter_device_submenu_remove:
showRemoveDeviceDialog(device);
return true;
case R.id.controlcenter_device_submenu_show_details:
expandedDeviceAddress = detailsShown ? "" : device.getAddress();
TransitionManager.beginDelayedTransition(parent);
notifyDataSetChanged();
return true;
case R.id.controlcenter_device_submenu_set_parent_folder:
showSetParentFolderDialog(device);
return true;
}
return false;
}
});
menu.show();
}
private void showRemoveDeviceDialog(final GBDevice device) {
new AlertDialog.Builder(context)
.setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName()))
.setMessage(R.string.controlcenter_delete_device_dialogmessage)
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
if (coordinator != null) {
coordinator.deleteDevice(device);
}
DeviceHelper.getInstance().removeBond(device);
} catch (Exception ex) {
GB.toast(context, context.getString(R.string.error_deleting_device, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
private void showSetParentFolderDialog(final GBDevice device) {
final EditText input = new EditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(device.getParentFolder());
FrameLayout container = new FrameLayout(context);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
input.setLayoutParams(params);
container.addView(input);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
new AlertDialog.Builder(context)
.setView(container)
.setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_set_parent_folder))
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
Device dbDevice = DBHelper.getDevice(device, session);
String parentFolder = input.getText().toString();
dbDevice.setParentFolder(parentFolder);
dbDevice.update();
device.setParentFolder(parentFolder);
expandedFolderName = parentFolder;
} catch (Exception ex) {
GB.toast(context, context.getString(R.string.error_setting_parent_folder, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
private void showSetAliasDialog(final GBDevice device) {
final EditText input = new EditText(context);
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(device.getAlias());
FrameLayout container = new FrameLayout(context);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
input.setLayoutParams(params);
container.addView(input);
// Specify the type of input expected; this, for example, sets the input as a password, and will mask the text
new AlertDialog.Builder(context)
.setView(container)
.setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_set_alias))
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
Device dbDevice = DBHelper.getDevice(device, session);
String alias = input.getText().toString();
dbDevice.setAlias(alias);
dbDevice.update();
device.setAlias(alias);
} catch (Exception ex) {
GB.toast(context, context.getString(R.string.error_setting_alias) + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
@Override
public int getItemCount() {
return deviceList.size();
return devicesListWithFolders.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
View root;
CardView container;
ImageView deviceImageView;
@ -798,6 +1007,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
LinearLayout heartRateStatusBox;
ImageView heartRateIcon;
TextView heartRateStatusLabel;
FlexboxLayout infoIcons;
ImageView deviceInfoView;
@ -805,8 +1015,6 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
final RelativeLayout deviceInfoBox;
ListView deviceInfoList;
ImageView findDevice;
ImageView removeDevice;
ImageView setAlias;
LinearLayout fmFrequencyBox;
TextView fmFrequencyLabel;
ImageView ledColor;
@ -821,6 +1029,8 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ViewHolder(View view) {
super(view);
root = view;
container = view.findViewById(R.id.card_view);
deviceImageView = view.findViewById(R.id.device_image);
@ -857,8 +1067,6 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
//overflow
deviceInfoList = view.findViewById(R.id.device_item_infos);
findDevice = view.findViewById(R.id.device_action_find);
removeDevice = view.findViewById(R.id.device_action_remove);
setAlias = view.findViewById(R.id.device_action_set_alias);
fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
ledColor = view.findViewById(R.id.device_led_color);
@ -866,6 +1074,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
heartRateStatusBox = view.findViewById(R.id.device_heart_rate_status_box);
heartRateStatusLabel = view.findViewById(R.id.heart_rate_status);
heartRateIcon = view.findViewById(R.id.device_heart_rate_status);
infoIcons = view.findViewById(R.id.device_info_icons);
cardViewActivityCardLayout = view.findViewById(R.id.card_view_activity_card_layout);

View File

@ -0,0 +1,38 @@
/* Copyright (C) 2017-2020 Andreas Shimokawa, protomors
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.database.schema;
import android.database.sqlite.SQLiteDatabase;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
public class GadgetbridgeUpdate_42 implements DBUpdateScript {
@Override
public void upgradeSchema(SQLiteDatabase db) {
if (!DBHelper.existsColumn(DeviceDao.TABLENAME, DeviceDao.Properties.ParentFolder.columnName, db)) {
String ADD_COLUMN_CPONTAINED_FOLDER = "ALTER TABLE " + DeviceDao.TABLENAME + " ADD COLUMN "
+ DeviceDao.Properties.ParentFolder.columnName + " TEXT";
db.execSQL(ADD_COLUMN_CPONTAINED_FOLDER);
}
}
@Override
public void downgradeSchema(SQLiteDatabase db) {
}
}

View File

@ -81,7 +81,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
@Override
public GBDevice createDevice(GBDeviceCandidate candidate) {
GBDevice gbDevice = new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), null, getDeviceType());
GBDevice gbDevice = new GBDevice(candidate.getDevice().getAddress(), candidate.getName(), null, null, getDeviceType());
for (BatteryConfig batteryConfig : getBatteryConfig()) {
gbDevice.setBatteryIcon(batteryConfig.getBatteryIcon(), batteryConfig.getBatteryIndex());
gbDevice.setBatteryLabel(batteryConfig.getBatteryLabel(), batteryConfig.getBatteryIndex());

View File

@ -89,7 +89,7 @@ public class DeviceManager {
if (dev.getAddress() != null) {
int index = deviceList.indexOf(dev); // search by address
if (index >= 0) {
deviceList.set(index, dev);
deviceList.get(index).copyFromDevice(dev);
} else {
deviceList.add(dev);
}

View File

@ -73,6 +73,7 @@ public class GBDevice implements Parcelable {
public static final String BATTERY_INDEX = "battery_index";
private String mName;
private String mAlias;
private String parentFolder;
private final String mAddress;
private String mVolatileAddress;
private final DeviceType mDeviceType;
@ -98,22 +99,24 @@ public class GBDevice implements Parcelable {
private int mNotificationIconDisconnected = R.drawable.ic_notification_disconnected;
private int mNotificationIconLowBattery = R.drawable.ic_notification_low_battery;
public GBDevice(String address, String name, String alias, DeviceType deviceType) {
this(address, null, name, alias, deviceType);
public GBDevice(String address, String name, String alias, String parentFolder, DeviceType deviceType) {
this(address, null, name, alias, parentFolder, deviceType);
}
public GBDevice(String address, String address2, String name, String alias, DeviceType deviceType) {
public GBDevice(String address, String address2, String name, String alias, String parentFolder, DeviceType deviceType) {
mAddress = address;
mVolatileAddress = address2;
mName = (name != null) ? name : mAddress;
mAlias = alias;
mDeviceType = deviceType;
this.parentFolder = parentFolder;
validate();
}
private GBDevice(Parcel in) {
mName = in.readString();
mAlias = in.readString();
parentFolder = in.readString();
mAddress = in.readString();
mVolatileAddress = in.readString();
mDeviceType = DeviceType.values()[in.readInt()];
@ -138,10 +141,39 @@ public class GBDevice implements Parcelable {
validate();
}
public void copyFromDevice(GBDevice device){
if(!device.mAddress.equals(mAddress)){
throw new RuntimeException("Cannot copy from device with other address");
}
mName = device.mName;
mAlias = device.mAlias;
parentFolder = device.parentFolder;
mVolatileAddress = device.mVolatileAddress;
mFirmwareVersion = device.mFirmwareVersion;
mFirmwareVersion2 = device.mFirmwareVersion2;
mModel = device.mModel;
mState = device.mState;
mBatteryLevel = device.mBatteryLevel;
mBatteryVoltage = device.mBatteryVoltage;
mBatteryThresholdPercent = device.mBatteryThresholdPercent;
mBatteryState = device.mBatteryState;
mBatteryIcons = device.mBatteryIcons;
mBatteryLabels = device.mBatteryLabels;
mRssi = device.mRssi;
mBusyTask = device.mBusyTask;
mDeviceInfos = device.mDeviceInfos;
mExtraInfos = device.mExtraInfos;
mNotificationIconConnected = device.mNotificationIconConnected;
mNotificationIconDisconnected = device.mNotificationIconDisconnected;
mNotificationIconLowBattery = device.mNotificationIconLowBattery;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeString(mAlias);
dest.writeString(parentFolder);
dest.writeString(mAddress);
dest.writeString(mVolatileAddress);
dest.writeInt(mDeviceType.ordinal());
@ -186,6 +218,13 @@ public class GBDevice implements Parcelable {
return enums;
}
public String getParentFolder() {
return parentFolder;
}
public void setParentFolder(String parentFolder) {
this.parentFolder = parentFolder;
}
public String getName() {
return mName;

View File

@ -0,0 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.impl;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class GBDeviceFolder extends GBDevice{
public GBDeviceFolder(String name) {
super("", name, null, null, null);
}
}

View File

@ -194,14 +194,14 @@ public class DeviceHelper {
Prefs prefs = GBApplication.getPrefs();
String miAddress = prefs.getString(MiBandConst.PREF_MIBAND_ADDRESS, "");
if (miAddress.length() > 0) {
GBDevice miDevice = new GBDevice(miAddress, "MI", null, DeviceType.MIBAND);
GBDevice miDevice = new GBDevice(miAddress, "MI", null, null, DeviceType.MIBAND);
availableDevices.add(miDevice);
}
String pebbleEmuAddr = prefs.getString("pebble_emu_addr", "");
String pebbleEmuPort = prefs.getString("pebble_emu_port", "");
if (pebbleEmuAddr.length() >= 7 && pebbleEmuPort.length() > 0) {
GBDevice pebbleEmuDevice = new GBDevice(pebbleEmuAddr + ":" + pebbleEmuPort, "Pebble qemu", "", DeviceType.PEBBLE);
GBDevice pebbleEmuDevice = new GBDevice(pebbleEmuAddr + ":" + pebbleEmuPort, "Pebble qemu", "", null, DeviceType.PEBBLE);
availableDevices.add(pebbleEmuDevice);
}
return availableDevices;
@ -361,7 +361,7 @@ public class DeviceHelper {
*/
public GBDevice toGBDevice(Device dbDevice) {
DeviceType deviceType = DeviceType.fromKey(dbDevice.getType());
GBDevice gbDevice = new GBDevice(dbDevice.getIdentifier(), dbDevice.getName(), dbDevice.getAlias(), deviceType);
GBDevice gbDevice = new GBDevice(dbDevice.getIdentifier(), dbDevice.getName(), dbDevice.getAlias(), dbDevice.getParentFolder(), deviceType);
DeviceCoordinator coordinator = getCoordinator(gbDevice);
for (BatteryConfig batteryConfig : coordinator.getBatteryConfig()) {
gbDevice.setBatteryIcon(batteryConfig.getBatteryIcon(), batteryConfig.getBatteryIndex());

View File

@ -103,6 +103,10 @@ public class StringUtils {
return "";
}
public static boolean isNullOrEmpty(String string){
return string == null || string.isEmpty();
}
public static boolean isEmpty(String string) {
return string != null && string.length() == 0;
}

View File

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="45sp"
android:height="45sp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#1f7fdb"
android:pathData="M3.871 3.877h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.824a0.947 0.947 0 0 1 0.947-0.947z"
android:strokeWidth="3.5" />
<path
android:fillColor="#4dabf5"
android:pathData="M3.879 3.035h20.925a0.947 0.947 0 0 1 0.947 0.947v20.01a0.947 0.947 0 0 1-0.947 0.948H3.88a0.947 0.947 0 0 1-0.947-0.948V3.982A0.947 0.947 0 0 1 3.88 3.035z"
android:strokeWidth="3.5" />
<path
android:fillColor="#2196f3"
android:pathData="M3.871 3.413h20.925a0.947 0.947 0 0 1 0.948 0.947v20.01a0.947 0.947 0 0 1-0.948 0.948H3.871a0.947 0.947 0 0 1-0.947-0.948V4.36A0.947 0.947 0 0 1 3.87 3.413z"
android:strokeWidth="3.5" />
<path
android:pathData="M6.8,6.95C6.22,6.95 5.75,7.42 5.75,8L5.75,20.92C5.73,21 5.74,21.08 5.77,21.16C5.87,21.63 6.3,22 6.8,22L20.45,22C20.97,22 21.4,21.61 21.48,21.1C21.48,21.08 21.49,21.06 21.49,21.04C21.49,21.03 21.49,21.02 21.49,21.02L21.5,20.96C21.5,20.96 21.5,20.95 21.5,20.95L23.24,11.57L23.25,11.53L23.25,11.5C23.25,10.92 22.78,10.45 22.2,10.45L22.2,9.4C22.2,8.82 21.73,8.35 21.15,8.35L12.06,8.35C12.06,8.35 12.06,8.35 12.05,8.35C12.04,8.34 12.01,8.32 11.95,8.25C11.87,8.15 11.77,7.99 11.66,7.81C11.55,7.64 11.43,7.45 11.28,7.29C11.14,7.13 10.94,6.95 10.65,6.95ZM6.8,7.65L10.65,7.65C10.63,7.65 10.67,7.65 10.76,7.75C10.85,7.85 10.95,8.01 11.05,8.19C11.16,8.36 11.28,8.55 11.43,8.71C11.57,8.88 11.76,9.05 12.05,9.05L21.15,9.05C21.35,9.05 21.5,9.2 21.5,9.4L21.5,10.45L8.55,10.45C8,10.45 7.56,10.89 7.52,11.43L7.51,11.43L7.5,11.5L6.45,17.17L6.45,8C6.45,7.8 6.6,7.65 6.8,7.65ZM8.55,11.15L22.2,11.15C22.4,11.15 22.55,11.3 22.55,11.5L20.83,20.79L20.82,20.81C20.82,20.82 20.81,20.84 20.81,20.85C20.81,20.87 20.8,20.88 20.8,20.9C20.8,20.9 20.8,20.91 20.8,20.92C20.8,20.92 20.8,20.92 20.8,20.93C20.79,20.95 20.79,20.96 20.79,20.98C20.79,20.99 20.79,21 20.79,21.02C20.79,21.03 20.79,21.04 20.79,21.06C20.75,21.2 20.61,21.3 20.45,21.3L6.8,21.3C6.6,21.3 6.45,21.15 6.45,20.95L8.19,11.57L8.2,11.53L8.2,11.5C8.2,11.3 8.35,11.15 8.55,11.15Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="45sp"
android:height="45sp"
android:viewportWidth="30"
android:viewportHeight="30">
<path
android:fillColor="#7a7a7a"
android:pathData="M3.8712,3.8769L24.7963,3.8769A0.9472,0.9472 0,0 1,25.7435 4.8241L25.7435,24.8343A0.9472,0.9472 0,0 1,24.7963 25.7815L3.8712,25.7815A0.9472,0.9472 0,0 1,2.924 24.8343L2.924,4.8241A0.9472,0.9472 0,0 1,3.8712 3.8769z"
android:strokeWidth="3.57115173"
android:strokeColor="#00000000" />
<path
android:fillAlpha="0.9411765"
android:fillColor="#9f9f9f"
android:pathData="M3.879,3.0349L24.8041,3.0349A0.9472,0.9472 0,0 1,25.7514 3.9822L25.7514,23.9924A0.9472,0.9472 0,0 1,24.8041 24.9396L3.879,24.9396A0.9472,0.9472 0,0 1,2.9318 23.9924L2.9318,3.9822A0.9472,0.9472 0,0 1,3.879 3.0349z"
android:strokeWidth="3.57115173"
android:strokeColor="#00000000" />
<path
android:fillColor="#8a8a8a"
android:pathData="M3.8711,3.4131L24.7962,3.4131A0.9472,0.9472 0,0 1,25.7435 4.3603L25.7435,24.3705A0.9472,0.9472 0,0 1,24.7962 25.3177L3.8711,25.3177A0.9472,0.9472 0,0 1,2.9239 24.3705L2.9239,4.3603A0.9472,0.9472 0,0 1,3.8711 3.4131z"
android:strokeWidth="3.57115173"
android:strokeColor="#00000000" />
<path
android:fillAlpha="0.9411765"
android:fillColor="#8a8a8a"
android:pathData="m12.4827,10.8654h3.907v6.978h-3.907z" />
<path
android:pathData="M6.8,6.95C6.22,6.95 5.75,7.42 5.75,8L5.75,20.92C5.73,21 5.74,21.08 5.77,21.16C5.87,21.63 6.3,22 6.8,22L20.45,22C20.97,22 21.4,21.61 21.48,21.1C21.48,21.08 21.49,21.06 21.49,21.04C21.49,21.03 21.49,21.02 21.49,21.02L21.5,20.96C21.5,20.96 21.5,20.95 21.5,20.95L23.24,11.57L23.25,11.53L23.25,11.5C23.25,10.92 22.78,10.45 22.2,10.45L22.2,9.4C22.2,8.82 21.73,8.35 21.15,8.35L12.06,8.35C12.06,8.35 12.06,8.35 12.05,8.35C12.04,8.34 12.01,8.32 11.95,8.25C11.87,8.15 11.77,7.99 11.66,7.81C11.55,7.64 11.43,7.45 11.28,7.29C11.14,7.13 10.94,6.95 10.65,6.95ZM6.8,7.65L10.65,7.65C10.63,7.65 10.67,7.65 10.76,7.75C10.85,7.85 10.95,8.01 11.05,8.19C11.16,8.36 11.28,8.55 11.43,8.71C11.57,8.88 11.76,9.05 12.05,9.05L21.15,9.05C21.35,9.05 21.5,9.2 21.5,9.4L21.5,10.45L8.55,10.45C8,10.45 7.56,10.89 7.52,11.43L7.51,11.43L7.5,11.5L6.45,17.17L6.45,8C6.45,7.8 6.6,7.65 6.8,7.65ZM8.55,11.15L22.2,11.15C22.4,11.15 22.55,11.3 22.55,11.5L20.83,20.79L20.82,20.81C20.82,20.82 20.81,20.84 20.81,20.85C20.81,20.87 20.8,20.88 20.8,20.9C20.8,20.9 20.8,20.91 20.8,20.92C20.8,20.92 20.8,20.92 20.8,20.93C20.79,20.95 20.79,20.96 20.79,20.98C20.79,20.99 20.79,21 20.79,21.02C20.79,21.03 20.79,21.04 20.79,21.06C20.75,21.2 20.61,21.3 20.45,21.3L6.8,21.3C6.6,21.3 6.45,21.15 6.45,20.95L8.19,11.57L8.2,11.53L8.2,11.5C8.2,11.3 8.35,11.15 8.55,11.15Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -10,7 +10,9 @@
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:foreground="?android:attr/selectableItemBackground"
card_view:cardCornerRadius="3dp"
card_view:cardElevation="3dp"
@ -35,38 +37,9 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/device_action_set_alias"
android:focusable="false"
android:scrollbars="none">
</ListView>
<ImageView
android:id="@+id/device_action_set_alias"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="36dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/controlcenter_set_alias"
android:focusable="true"
card_view:srcCompat="@drawable/ic_create"
card_view:tint="?attr/textColorTertiary" />
<ImageView
android:id="@+id/device_action_remove"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/controlcenter_delete_device"
android:focusable="true"
card_view:srcCompat="@drawable/ic_delete"
card_view:tint="?attr/textColorTertiary" />
</RelativeLayout>
<ImageView

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/controlcenter_device_submenu_connect"
android:title="@string/controlcenter_connect" />
<item
android:id="@+id/controlcenter_device_submenu_disconnect"
android:title="@string/controlcenter_disconnect" />
<item
android:id="@+id/controlcenter_device_submenu_set_parent_folder"
android:title="@string/controlcenter_set_parent_folder" />
<item
android:id="@+id/controlcenter_device_submenu_set_alias"
android:title="@string/controlcenter_set_alias" />
<item
android:id="@+id/controlcenter_device_submenu_show_details"
android:title="@string/controlcenter_toggle_details" />
<item
android:id="@+id/controlcenter_device_submenu_remove"
android:title="@string/controlcenter_delete_device" />
</menu>

View File

@ -1734,4 +1734,7 @@
<string name="notification_gps_title">Gadgetbridge GPS</string>
<string name="portuguese_pt">Portugiesisch (Portugal)</string>
<string name="notification_gps_text">Senden des GPS-Standorts an %1$d Geräte</string>
<string name="controlcenter_set_parent_folder">Überordner setzen</string>
<string name="controlcenter_toggle_details">Details anzeigen</string>
<string name="controlcenter_connected_fraction">Verbunden: %d/%d</string>
</resources>

View File

@ -1678,4 +1678,9 @@
<string name="info_no_devices_connected">no devices connected</string>
<string name="info_connected_count">%d devices connected</string>
<string name="controlcenter_set_parent_folder">Set parent folder</string>
<string name="controlcenter_toggle_details">Toggle details</string>
<string name="controlcenter_connected_fraction">Connected: %d/%d</string>
<string name="error_setting_parent_folder">Error setting parent folder: %s</string>
<string name="error_deleting_device">Error deleting device: %s</string>
</resources>