mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-10 17:11:56 +01:00
Zepp OS: Manage contacts on watch
This commit is contained in:
parent
f68e4c865b
commit
2b6a79f462
@ -43,7 +43,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(45, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(46, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -89,6 +89,7 @@ public class GBDaoGenerator {
|
||||
addAlarms(schema, user, device);
|
||||
addReminders(schema, user, device);
|
||||
addWorldClocks(schema, user, device);
|
||||
addContacts(schema, user, device);
|
||||
|
||||
Entity notificationFilter = addNotificationFilters(schema);
|
||||
|
||||
@ -598,6 +599,24 @@ public class GBDaoGenerator {
|
||||
worldClock.addToOne(device, deviceId);
|
||||
}
|
||||
|
||||
private static void addContacts(Schema schema, Entity user, Entity device) {
|
||||
Entity contact = addEntity(schema, "Contact");
|
||||
contact.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.Contact");
|
||||
Property deviceId = contact.addLongProperty("deviceId").notNull().getProperty();
|
||||
Property userId = contact.addLongProperty("userId").notNull().getProperty();
|
||||
Property contactId = contact.addStringProperty("contactId").notNull().primaryKey().getProperty();
|
||||
Index indexUnique = new Index();
|
||||
indexUnique.addProperty(deviceId);
|
||||
indexUnique.addProperty(userId);
|
||||
indexUnique.addProperty(contactId);
|
||||
indexUnique.makeUnique();
|
||||
contact.addIndex(indexUnique);
|
||||
contact.addStringProperty("name").notNull();
|
||||
contact.addStringProperty("number").notNull();
|
||||
contact.addToOne(user, userId);
|
||||
contact.addToOne(device, deviceId);
|
||||
}
|
||||
|
||||
private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
|
||||
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
|
||||
notificatonFilterEntry.addIdProperty().autoincrement();
|
||||
|
@ -518,6 +518,10 @@
|
||||
android:name=".activities.ConfigureReminders"
|
||||
android:label="@string/title_activity_set_reminders"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.ConfigureContacts"
|
||||
android:label="@string/title_activity_set_contacts"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.ConfigureWorldClocks"
|
||||
android:label="@string/pref_world_clocks_title"
|
||||
@ -537,6 +541,12 @@
|
||||
android:parentActivityName=".activities.ConfigureReminders"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".activities.ContactDetails"
|
||||
android:label="@string/title_activity_contact_details"
|
||||
android:parentActivityName=".activities.ConfigureReminders"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".activities.WorldClockDetails"
|
||||
android:label="@string/title_activity_world_clock_details"
|
||||
|
@ -0,0 +1,168 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
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.activities;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBContactListAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
|
||||
public class ConfigureContacts extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConfigureContacts.class);
|
||||
|
||||
private static final int REQ_CONFIGURE_CONTACT = 1;
|
||||
|
||||
private GBContactListAdapter mGBContactListAdapter;
|
||||
private GBDevice gbDevice;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_configure_contacts);
|
||||
|
||||
gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
mGBContactListAdapter = new GBContactListAdapter(this);
|
||||
|
||||
final RecyclerView contactsRecyclerView = findViewById(R.id.contact_list);
|
||||
contactsRecyclerView.setHasFixedSize(true);
|
||||
contactsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
contactsRecyclerView.setAdapter(mGBContactListAdapter);
|
||||
updateContactsFromDB();
|
||||
|
||||
final FloatingActionButton fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(v -> {
|
||||
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
|
||||
int deviceSlots = coordinator.getContactsSlotCount(gbDevice);
|
||||
|
||||
if (mGBContactListAdapter.getItemCount() >= deviceSlots) {
|
||||
// No more free slots
|
||||
new AlertDialog.Builder(v.getContext())
|
||||
.setTitle(R.string.reminder_no_free_slots_title)
|
||||
.setMessage(getBaseContext().getString(R.string.contact_no_free_slots_description, String.format(Locale.getDefault(), "%d", deviceSlots)))
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> {
|
||||
})
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
final Contact contact;
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
final Device device = DBHelper.getDevice(gbDevice, daoSession);
|
||||
final User user = DBHelper.getUser(daoSession);
|
||||
contact = createDefaultContact(device, user);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error accessing database", e);
|
||||
return;
|
||||
}
|
||||
|
||||
configureContact(contact);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQ_CONFIGURE_CONTACT && resultCode == 1) {
|
||||
updateContactsFromDB();
|
||||
sendContactsToDevice();
|
||||
}
|
||||
}
|
||||
|
||||
private Contact createDefaultContact(@NonNull Device device, @NonNull User user) {
|
||||
final Contact contact = new Contact();
|
||||
contact.setName("");
|
||||
contact.setNumber("");
|
||||
contact.setDeviceId(device.getId());
|
||||
contact.setUserId(user.getId());
|
||||
contact.setContactId(UUID.randomUUID().toString());
|
||||
|
||||
return contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the available contacts from the database and updates the view afterwards.
|
||||
*/
|
||||
private void updateContactsFromDB() {
|
||||
final List<Contact> contacts = DBHelper.getContacts(gbDevice);
|
||||
|
||||
mGBContactListAdapter.setContactList(contacts);
|
||||
mGBContactListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// back button
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public void configureContact(final Contact contact) {
|
||||
final Intent startIntent = new Intent(getApplicationContext(), ContactDetails.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
|
||||
startIntent.putExtra(Contact.EXTRA_CONTACT, contact);
|
||||
startActivityForResult(startIntent, REQ_CONFIGURE_CONTACT);
|
||||
}
|
||||
|
||||
public void deleteContact(final Contact contact) {
|
||||
DBHelper.delete(contact);
|
||||
updateContactsFromDB();
|
||||
sendContactsToDevice();
|
||||
}
|
||||
|
||||
private void sendContactsToDevice() {
|
||||
if (gbDevice.isInitialized()) {
|
||||
GBApplication.deviceService(gbDevice).onSetContacts(mGBContactListAdapter.getContactList());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
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.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class ContactDetails extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ContactDetails.class);
|
||||
|
||||
private Contact contact;
|
||||
private GBDevice device;
|
||||
|
||||
EditText contactName;
|
||||
EditText contactNumber;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_contact_details);
|
||||
|
||||
contact = (Contact) getIntent().getSerializableExtra(Contact.EXTRA_CONTACT);
|
||||
|
||||
if (contact == null) {
|
||||
GB.toast("No contact provided to ContactDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
contactName = findViewById(R.id.contact_name);
|
||||
contactNumber = findViewById(R.id.contact_number);
|
||||
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
|
||||
contactName.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
contact.setName(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
contactNumber.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
contact.setNumber(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
final FloatingActionButton fab = findViewById(R.id.fab_save);
|
||||
fab.setOnClickListener(view -> {
|
||||
if (StringUtils.isNullOrEmpty(contact.getName())) {
|
||||
GB.toast(getBaseContext().getString(R.string.contact_missing_name), Toast.LENGTH_LONG, GB.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (StringUtils.isNullOrEmpty(contact.getNumber())) {
|
||||
GB.toast(getBaseContext().getString(R.string.contact_missing_number), Toast.LENGTH_LONG, GB.WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
updateContact();
|
||||
ContactDetails.this.setResult(1);
|
||||
finish();
|
||||
});
|
||||
|
||||
updateUiFromContact();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
// back button
|
||||
// TODO confirm when exiting without saving
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void updateContact() {
|
||||
DBHelper.store(contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull final Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
state.putSerializable("contact", contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
contact = (Contact) savedInstanceState.getSerializable("contact");
|
||||
updateUiFromContact();
|
||||
}
|
||||
|
||||
public void updateUiFromContact() {
|
||||
contactName.setText(contact.getName());
|
||||
contactNumber.setText(contact.getNumber());
|
||||
}
|
||||
}
|
@ -223,6 +223,7 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_FAKE_RING_DURATION = "fake_ring_duration";
|
||||
|
||||
public static final String PREF_WORLD_CLOCKS = "pref_world_clocks";
|
||||
public static final String PREF_CONTACTS = "pref_contacts";
|
||||
|
||||
public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled";
|
||||
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
|
||||
|
@ -50,6 +50,7 @@ import java.util.Set;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.CalBlacklistActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
|
||||
import nodomain.freeyourgadget.gadgetbridge.capabilities.HeartRateCapability;
|
||||
import nodomain.freeyourgadget.gadgetbridge.capabilities.password.PasswordCapabilityImpl;
|
||||
@ -755,6 +756,19 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
});
|
||||
}
|
||||
|
||||
final Preference contacts = findPreference(PREF_CONTACTS);
|
||||
if (contacts != null) {
|
||||
contacts.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final Intent intent = new Intent(getContext(), ConfigureContacts.class);
|
||||
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference calendarBlacklist = findPreference("blacklist_calendars");
|
||||
if (calendarBlacklist != null) {
|
||||
calendarBlacklist.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
|
@ -0,0 +1,109 @@
|
||||
/* Copyright (C) 2023 José Rebelo
|
||||
|
||||
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.adapter;
|
||||
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
|
||||
|
||||
/**
|
||||
* Adapter for displaying Contact instances.
|
||||
*/
|
||||
public class GBContactListAdapter extends RecyclerView.Adapter<GBContactListAdapter.ViewHolder> {
|
||||
|
||||
private final Context mContext;
|
||||
private ArrayList<Contact> contactList;
|
||||
|
||||
public GBContactListAdapter(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void setContactList(List<Contact> contacts) {
|
||||
this.contactList = new ArrayList<>(contacts);
|
||||
}
|
||||
|
||||
public ArrayList<Contact> getContactList() {
|
||||
return contactList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GBContactListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
|
||||
final Contact contact = contactList.get(position);
|
||||
|
||||
holder.container.setOnClickListener(v -> ((ConfigureContacts) mContext).configureContact(contact));
|
||||
|
||||
holder.container.setOnLongClickListener(v -> {
|
||||
new AlertDialog.Builder(v.getContext())
|
||||
.setTitle(R.string.contact_delete_confirm_title)
|
||||
.setMessage(mContext.getString(R.string.contact_delete_confirm_description, contact.getName()))
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||
((ConfigureContacts) mContext).deleteContact(contact);
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
holder.contactName.setText(contact.getName());
|
||||
holder.contactNumber.setText(contact.getNumber());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return contactList.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final CardView container;
|
||||
|
||||
final TextView contactName;
|
||||
final TextView contactNumber;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
|
||||
container = view.findViewById(R.id.card_contact);
|
||||
|
||||
contactName = view.findViewById(R.id.contact_item_name);
|
||||
contactNumber = view.findViewById(R.id.contact_item_phone_number);
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescription;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescriptionDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.ContactDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
|
||||
@ -668,6 +670,28 @@ public class DBHelper {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<Contact> getContacts(@NonNull GBDevice gbDevice) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
final User user = getUser(daoSession);
|
||||
final Device dbDevice = DBHelper.findDevice(gbDevice, daoSession);
|
||||
if (dbDevice != null) {
|
||||
final ContactDao contactDao = daoSession.getContactDao();
|
||||
final Long deviceId = dbDevice.getId();
|
||||
final QueryBuilder<Contact> qb = contactDao.queryBuilder();
|
||||
qb.where(
|
||||
ContactDao.Properties.UserId.eq(user.getId()),
|
||||
ContactDao.Properties.DeviceId.eq(deviceId)).orderAsc(ContactDao.Properties.Name);
|
||||
return qb.build().list();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error reading contacts from db", e);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static void store(final Reminder reminder) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
@ -686,6 +710,15 @@ public class DBHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static void store(final Contact contact) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
daoSession.insertOrReplace(contact);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error acquiring database", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(final Reminder reminder) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
@ -704,6 +737,15 @@ public class DBHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(final Contact contact) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
daoSession.delete(contact);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error acquiring database", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearSession() {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
|
@ -269,6 +269,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContactsSlotCount(final GBDevice device) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRgbLedColor() {
|
||||
return false;
|
||||
|
@ -403,6 +403,11 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsDisabledWorldClocks();
|
||||
|
||||
/**
|
||||
* Indicates the maximum number of slots available for contacts in the device.
|
||||
*/
|
||||
int getContactsSlotCount(GBDevice device);
|
||||
|
||||
/**
|
||||
* Indicates whether the device has an led which supports custom colors
|
||||
*/
|
||||
|
@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -53,6 +54,8 @@ public interface EventHandler {
|
||||
|
||||
void onSetWorldClocks(ArrayList<? extends WorldClock> clocks);
|
||||
|
||||
void onSetContacts(ArrayList<? extends Contact> contacts);
|
||||
|
||||
void onSetCallState(CallSpec callSpec);
|
||||
|
||||
void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
|
||||
|
@ -45,6 +45,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HuamiExtendedActivitySample
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.AbstractHuami2021FWInstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsContactsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsShortcutCardsService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services.ZeppOsConfigService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiLanguageType;
|
||||
@ -171,6 +172,11 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator {
|
||||
return getPrefs(device).getInt(Huami2021Service.REMINDERS_PREF_CAPABILITY, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContactsSlotCount(final GBDevice device) {
|
||||
return getPrefs(device).getInt(ZeppOsContactsService.PREF_CONTACTS_SLOT_COUNT, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedLanguageSettings(final GBDevice device) {
|
||||
// Return all known languages by default. Unsupported languages will be removed by Huami2021SettingsCustomizer
|
||||
@ -280,6 +286,9 @@ public abstract class Huami2021Coordinator extends HuamiCoordinator {
|
||||
// Other
|
||||
//
|
||||
settings.add(R.xml.devicesettings_header_other);
|
||||
if (getContactsSlotCount(device) > 0) {
|
||||
settings.add(R.xml.devicesettings_contacts);
|
||||
}
|
||||
settings.add(R.xml.devicesettings_offline_voice);
|
||||
settings.add(R.xml.devicesettings_device_actions_without_not_wear);
|
||||
settings.add(R.xml.devicesettings_buttonactions_upper_long);
|
||||
|
@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
@ -268,6 +269,13 @@ public class GBDeviceService implements DeviceService {
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetContacts(ArrayList<? extends Contact> contacts) {
|
||||
Intent intent = createIntent().setAction(ACTION_SET_CONTACTS)
|
||||
.putExtra(EXTRA_CONTACTS, contacts);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
|
||||
|
@ -60,6 +60,7 @@ public interface DeviceService extends EventHandler {
|
||||
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
|
||||
String ACTION_SET_REMINDERS = PREFIX + ".action.set_reminders";
|
||||
String ACTION_SET_WORLD_CLOCKS = PREFIX + ".action.set_world_clocks";
|
||||
String ACTION_SET_CONTACTS = PREFIX + ".action.set_contacts";
|
||||
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
|
||||
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
|
||||
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
|
||||
@ -118,6 +119,7 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_ALARMS = "alarms";
|
||||
String EXTRA_REMINDERS = "reminders";
|
||||
String EXTRA_WORLD_CLOCKS = "world_clocks";
|
||||
String EXTRA_CONTACTS = "contacts";
|
||||
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
|
||||
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
|
||||
String EXTRA_INTERVAL_SECONDS = "interval_seconds";
|
||||
|
@ -84,6 +84,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -639,6 +640,16 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If contacts can be configured on the device, this method can be
|
||||
* overridden and implemented by the device support class.
|
||||
* @param contacts {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Contact} instances
|
||||
*/
|
||||
@Override
|
||||
public void onSetContacts(ArrayList<? extends Contact> contacts) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* If the device can receive and display notifications, this method
|
||||
* can be overridden and implemented by the device support class.
|
||||
|
@ -77,6 +77,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -126,6 +127,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONSTANT_VIBRATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONTACTS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_FM_FREQUENCY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_GPS_LOCATION;
|
||||
@ -161,6 +163,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAN
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONTACTS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FM_FREQUENCY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_GPS_LOCATION;
|
||||
@ -918,6 +921,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
ArrayList<? extends WorldClock> clocks = (ArrayList<? extends WorldClock>) intent.getSerializableExtra(EXTRA_WORLD_CLOCKS);
|
||||
deviceSupport.onSetWorldClocks(clocks);
|
||||
break;
|
||||
case ACTION_SET_CONTACTS:
|
||||
ArrayList<? extends Contact> contacts = (ArrayList<? extends Contact>) intent.getSerializableExtra(EXTRA_CONTACTS);
|
||||
deviceSupport.onSetContacts(contacts);
|
||||
break;
|
||||
case ACTION_ENABLE_REALTIME_STEPS: {
|
||||
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
|
||||
deviceSupport.onEnableRealtimeSteps(enable);
|
||||
|
@ -36,6 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -355,6 +356,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
delegate.onSetReminders(reminders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetContacts(ArrayList<? extends Contact> contacts) {
|
||||
if (checkBusy("set contacts")) {
|
||||
return;
|
||||
}
|
||||
delegate.onSetContacts(contacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
if (checkBusy("set world clocks")) {
|
||||
|
@ -91,6 +91,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
@ -595,6 +596,11 @@ public abstract class Huami2021Support extends HuamiSupport {
|
||||
writeToChunked2021(builder, CHUNKED2021_ENDPOINT_REMINDERS, buf.array(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetContacts(ArrayList<? extends Contact> contacts) {
|
||||
contactsService.setContacts((List<Contact>) contacts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isWorldClocksEncrypted() {
|
||||
return true;
|
||||
@ -1182,7 +1188,6 @@ public abstract class Huami2021Support extends HuamiSupport {
|
||||
phoneService.requestCapabilities(builder);
|
||||
phoneService.requestEnabled(builder);
|
||||
}
|
||||
//contactsService.requestCapabilities(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,6 +23,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
@ -43,6 +44,8 @@ public class ZeppOsContactsService extends AbstractZeppOsService {
|
||||
private int version = 0;
|
||||
private int maxContacts = 0;
|
||||
|
||||
public static final String PREF_CONTACTS_SLOT_COUNT = "zepp_os_contacts_slot_count";
|
||||
|
||||
public ZeppOsContactsService(final Huami2021Support support) {
|
||||
super(support);
|
||||
}
|
||||
@ -68,6 +71,7 @@ public class ZeppOsContactsService extends AbstractZeppOsService {
|
||||
}
|
||||
maxContacts = BLETypeConversions.toUint16(payload, 2);
|
||||
LOG.info("Contacts version={}, maxContacts={}", version, maxContacts);
|
||||
getSupport().evaluateGBDeviceEvent(new GBDeviceEventUpdatePreferences(PREF_CONTACTS_SLOT_COUNT, maxContacts));
|
||||
break;
|
||||
case CMD_SET_LIST_ACK:
|
||||
LOG.info("Got contacts set list ack, status = {}", payload[1]);
|
||||
@ -77,12 +81,9 @@ public class ZeppOsContactsService extends AbstractZeppOsService {
|
||||
}
|
||||
}
|
||||
|
||||
public int maxContacts() {
|
||||
return maxContacts;
|
||||
}
|
||||
|
||||
public boolean isSupported() {
|
||||
return version == 1 && maxContacts != 0;
|
||||
@Override
|
||||
public void initialize(final TransactionBuilder builder) {
|
||||
requestCapabilities(builder);
|
||||
}
|
||||
|
||||
public void requestCapabilities(final TransactionBuilder builder) {
|
||||
|
27
app/src/main/res/layout/activity_configure_contacts.xml
Normal file
27
app/src/main/res/layout/activity_configure_contacts.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureContacts">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/contact_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:divider="@null" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
app:srcCompat="@drawable/ic_add" />
|
||||
</RelativeLayout>
|
96
app/src/main/res/layout/activity_contact_details.xml
Normal file
96
app/src/main/res/layout/activity_contact_details.xml
Normal file
@ -0,0 +1,96 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ContactDetails">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_contact_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
card_view:cardCornerRadius="4dp"
|
||||
card_view:cardElevation="4dp"
|
||||
card_view:contentPadding="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_contact_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/contact_name"
|
||||
android:textAppearance="?android:attr/textAppearance"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contact_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_contact_name" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_contact_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_contact_name"
|
||||
card_view:cardCornerRadius="4dp"
|
||||
card_view:cardElevation="4dp"
|
||||
card_view:contentPadding="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_contact_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/contact_phone_number"
|
||||
android:textAppearance="?android:attr/textAppearance"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contact_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_contact_number" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_save"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/ic_save" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
51
app/src/main/res/layout/item_contact.xml
Normal file
51
app/src/main/res/layout/item_contact.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_contact"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
card_view:cardCornerRadius="4dp"
|
||||
card_view:cardElevation="4dp"
|
||||
card_view:contentPadding="4dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contact_item_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="3dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="John Smith"
|
||||
android:textAppearance="?android:attr/textAppearance" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contact_item_phone_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="3dp"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:text="+1 000 000 000"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -668,8 +668,11 @@
|
||||
<string name="title_activity_charts">Activity and Sleep</string>
|
||||
<string name="title_activity_set_alarm">Configure alarms</string>
|
||||
<string name="title_activity_set_reminders">Configure reminders</string>
|
||||
<string name="title_activity_set_contacts">Configure contacts</string>
|
||||
<string name="pref_world_clocks_title">World Clocks</string>
|
||||
<string name="pref_world_clocks_summary">Configure clocks for other timezones</string>
|
||||
<string name="pref_contacts_title">Contacts</string>
|
||||
<string name="pref_contacts_summary">Configure contacts on the watch</string>
|
||||
<string name="controlcenter_start_configure_alarms">Configure alarms</string>
|
||||
<string name="controlcenter_start_configure_reminders">Configure reminders</string>
|
||||
<string name="reminder_repeat">Repeat</string>
|
||||
@ -690,6 +693,9 @@
|
||||
<string name="reminder_delete_confirm_description">Are you sure you want to delete the reminder?</string>
|
||||
<string name="reminder_no_free_slots_title">No free slots</string>
|
||||
<string name="reminder_no_free_slots_description">The device has no free slots for reminders (total slots: %1$s)</string>
|
||||
<string name="contact_delete_confirm_title">Delete contact</string>
|
||||
<string name="contact_delete_confirm_description">Are you sure you want to delete \'%1$s\'?</string>
|
||||
<string name="contact_no_free_slots_description">The device has no free slots for contacts (total slots: %1$s)</string>
|
||||
<string name="world_clock_delete_confirm_title">Delete \'%1$s\'</string>
|
||||
<string name="world_clock_delete_confirm_description">Are you sure you want to delete the world clock?</string>
|
||||
<string name="world_clock_no_free_slots_title">No free slots</string>
|
||||
@ -700,6 +706,7 @@
|
||||
<string name="world_clock_code">Code</string>
|
||||
<string name="title_activity_alarm_details">Alarm details</string>
|
||||
<string name="title_activity_reminder_details">Reminder details</string>
|
||||
<string name="title_activity_contact_details">Contact details</string>
|
||||
<string name="title_activity_world_clock_details">World Clock details</string>
|
||||
<string name="alarm_sun_short">Sun</string>
|
||||
<string name="alarm_mon_short">Mon</string>
|
||||
@ -2100,4 +2107,8 @@
|
||||
<string name="fossil_hr_confirmation_timeout">Confirmation timeout, continuing</string>
|
||||
<string name="debug_companion_show_associated">Show associated companion devices</string>
|
||||
<string name="debug_companion_pair_current">Pair current device as companion</string>
|
||||
<string name="contact_name">Name</string>
|
||||
<string name="contact_phone_number">Phone number</string>
|
||||
<string name="contact_missing_name">Contact name is empty</string>
|
||||
<string name="contact_missing_number">Contact number is empty</string>
|
||||
</resources>
|
||||
|
8
app/src/main/res/xml/devicesettings_contacts.xml
Normal file
8
app/src/main/res/xml/devicesettings_contacts.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Preference
|
||||
android:icon="@drawable/ic_person"
|
||||
android:key="pref_contacts"
|
||||
android:summary="@string/pref_contacts_summary"
|
||||
android:title="@string/pref_contacts_title" />
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user