Simplify LocationControlSet

pull/795/head
Alex Baker 7 years ago
parent be0432c86c
commit c7401762b2

@ -18,26 +18,36 @@ import com.google.android.gms.location.LocationServices;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.PermissionChecker; import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
public class GeofenceApi { public class GeofenceApi {
private final Context context; private final Context context;
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
private final LocationDao locationDao;
@Inject @Inject
public GeofenceApi( public GeofenceApi(
@ForApplication Context context, @ForApplication Context context,
Preferences preferences, PermissionChecker permissionChecker,
PermissionChecker permissionChecker) { LocationDao locationDao) {
this.context = context; this.context = context;
this.permissionChecker = permissionChecker; this.permissionChecker = permissionChecker;
this.locationDao = locationDao;
}
public void registerAll() {
register(locationDao.getActiveGeofences());
}
public void register(long taskId) {
register(locationDao.getActiveGeofences(taskId));
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
void register(final List<Location> locations) { public void register(final List<Location> locations) {
if (!permissionChecker.canAccessLocation()) { if (!permissionChecker.canAccessLocation()) {
return; return;
} }
@ -55,8 +65,14 @@ public class GeofenceApi {
} }
} }
public void cancel(long taskId) {
cancel(locationDao.getGeofences(taskId));
}
public void cancel(final Location location) { public void cancel(final Location location) {
cancel(singletonList(location)); if (location != null) {
cancel(singletonList(location));
}
} }
public void cancel(final List<Location> locations) { public void cancel(final List<Location> locations) {

@ -17,7 +17,7 @@ import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingApplication; import org.tasks.injection.InjectingApplication;
import org.tasks.injection.InjectingJobIntentService; import org.tasks.injection.InjectingJobIntentService;
import org.tasks.jobs.WorkManager; import org.tasks.jobs.WorkManager;
import org.tasks.location.GeofenceService; import org.tasks.location.GeofenceApi;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.receivers.RefreshReceiver; import org.tasks.receivers.RefreshReceiver;
import org.tasks.scheduling.CalendarNotificationIntentService; import org.tasks.scheduling.CalendarNotificationIntentService;
@ -36,7 +36,7 @@ public class Tasks extends InjectingApplication {
@Inject ThemeCache themeCache; @Inject ThemeCache themeCache;
@Inject WorkManager workManager; @Inject WorkManager workManager;
@Inject RefreshScheduler refreshScheduler; @Inject RefreshScheduler refreshScheduler;
@Inject GeofenceService geofenceService; @Inject GeofenceApi geofenceApi;
@Inject LocalBroadcastManager localBroadcastManager; @Inject LocalBroadcastManager localBroadcastManager;
@Override @Override
@ -84,7 +84,7 @@ public class Tasks extends InjectingApplication {
workManager.updateBackgroundSync(); workManager.updateBackgroundSync();
workManager.scheduleMidnightRefresh(); workManager.scheduleMidnightRefresh();
workManager.scheduleBackup(); workManager.scheduleBackup();
geofenceService.setupGeofences(); geofenceApi.registerAll();
FileHelper.delete(context, preferences.getCacheDirectory()); FileHelper.delete(context, preferences.getCacheDirectory());
} }

@ -18,8 +18,8 @@ public interface LocationDao {
Location getGeofence(Long id); Location getGeofence(Long id);
@Query( @Query(
"SELECT * FROM geofences INNER JOIN places ON geofences.place = places.uid WHERE task = :taskId ORDER BY name ASC") "SELECT * FROM geofences INNER JOIN places ON geofences.place = places.uid WHERE task = :taskId ORDER BY name ASC LIMIT 1")
List<Location> getGeofences(long taskId); Location getGeofences(long taskId);
@Query( @Query(
"SELECT geofences.*, places.* FROM geofences INNER JOIN places ON geofences.place = places.uid INNER JOIN tasks ON tasks._id = geofences.task WHERE tasks._id = :taskId AND tasks.deleted = 0 AND tasks.completed = 0") "SELECT geofences.*, places.* FROM geofences INNER JOIN places ON geofences.place = places.uid INNER JOIN tasks ON tasks._id = geofences.task WHERE tasks._id = :taskId AND tasks.deleted = 0 AND tasks.completed = 0")
@ -39,7 +39,7 @@ public interface LocationDao {
void delete(Place place); void delete(Place place);
@Insert @Insert
void insert(Geofence location); long insert(Geofence location);
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
long insert(Place place); long insert(Place place);

@ -142,7 +142,6 @@ public class LocationDialog extends InjectingDialogFragment {
private void sendResult(Location result) { private void sendResult(Location result) {
Intent data = new Intent(); Intent data = new Intent();
data.putExtra(EXTRA_ORIGINAL, (Parcelable) getOriginal());
data.putExtra(EXTRA_LOCATION, (Parcelable) result); data.putExtra(EXTRA_LOCATION, (Parcelable) result);
getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, data); getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, data);
dismiss(); dismiss();

@ -24,7 +24,7 @@ import org.tasks.R;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingWorker; import org.tasks.injection.InjectingWorker;
import org.tasks.injection.JobComponent; import org.tasks.injection.JobComponent;
import org.tasks.location.GeofenceService; import org.tasks.location.GeofenceApi;
import org.tasks.notifications.NotificationManager; import org.tasks.notifications.NotificationManager;
import org.tasks.scheduling.RefreshScheduler; import org.tasks.scheduling.RefreshScheduler;
import org.tasks.sync.SyncAdapters; import org.tasks.sync.SyncAdapters;
@ -41,7 +41,7 @@ public class AfterSaveWork extends InjectingWorker {
@Inject RepeatTaskHelper repeatTaskHelper; @Inject RepeatTaskHelper repeatTaskHelper;
@Inject @ForApplication Context context; @Inject @ForApplication Context context;
@Inject NotificationManager notificationManager; @Inject NotificationManager notificationManager;
@Inject GeofenceService geofenceService; @Inject GeofenceApi geofenceApi;
@Inject TimerPlugin timerPlugin; @Inject TimerPlugin timerPlugin;
@Inject ReminderService reminderService; @Inject ReminderService reminderService;
@Inject RefreshScheduler refreshScheduler; @Inject RefreshScheduler refreshScheduler;
@ -94,9 +94,9 @@ public class AfterSaveWork extends InjectingWorker {
if (justCompleted || justDeleted) { if (justCompleted || justDeleted) {
notificationManager.cancel(taskId); notificationManager.cancel(taskId);
geofenceService.cancelGeofences(taskId); geofenceApi.cancel(taskId);
} else if (completionDateModified || deletionDateModified) { } else if (completionDateModified || deletionDateModified) {
geofenceService.setupGeofences(taskId); geofenceApi.register(taskId);
} }
if (justCompleted) { if (justCompleted) {

@ -14,7 +14,7 @@ import org.tasks.data.UserActivityDao;
import org.tasks.files.FileHelper; import org.tasks.files.FileHelper;
import org.tasks.injection.InjectingWorker; import org.tasks.injection.InjectingWorker;
import org.tasks.injection.JobComponent; import org.tasks.injection.JobComponent;
import org.tasks.location.GeofenceService; import org.tasks.location.GeofenceApi;
import org.tasks.notifications.NotificationManager; import org.tasks.notifications.NotificationManager;
import timber.log.Timber; import timber.log.Timber;
@ -23,7 +23,7 @@ public class CleanupWork extends InjectingWorker {
static final String EXTRA_TASK_IDS = "extra_task_ids"; static final String EXTRA_TASK_IDS = "extra_task_ids";
private final Context context; private final Context context;
@Inject NotificationManager notificationManager; @Inject NotificationManager notificationManager;
@Inject GeofenceService geofenceService; @Inject GeofenceApi geofenceApi;
@Inject TimerPlugin timerPlugin; @Inject TimerPlugin timerPlugin;
@Inject ReminderService reminderService; @Inject ReminderService reminderService;
@Inject AlarmService alarmService; @Inject AlarmService alarmService;
@ -47,7 +47,7 @@ public class CleanupWork extends InjectingWorker {
alarmService.cancelAlarms(task); alarmService.cancelAlarms(task);
reminderService.cancelReminder(task); reminderService.cancelReminder(task);
notificationManager.cancel(task); notificationManager.cancel(task);
geofenceService.cancelGeofences(task); geofenceApi.cancel(task);
for (TaskAttachment attachment : taskAttachmentDao.getAttachments(task)) { for (TaskAttachment attachment : taskAttachmentDao.getAttachments(task)) {
FileHelper.delete(context, attachment.parseUri()); FileHelper.delete(context, attachment.parseUri());
taskAttachmentDao.delete(attachment); taskAttachmentDao.delete(attachment);

@ -1,102 +0,0 @@
package org.tasks.location;
import static com.google.common.collect.Lists.newArrayList;
import static com.todoroo.astrid.data.Task.NO_ID;
import static java.util.Collections.emptyList;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import org.tasks.data.Geofence;
import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Place;
public class GeofenceService {
private final GeofenceApi geofenceApi;
private final LocationDao locationDao;
@Inject
public GeofenceService(GeofenceApi geofenceApi, LocationDao locationDao) {
this.geofenceApi = geofenceApi;
this.locationDao = locationDao;
}
public List<Location> getGeofences(long taskId) {
return taskId == NO_ID ? emptyList() : locationDao.getGeofences(taskId);
}
public void setupGeofences() {
geofenceApi.register(locationDao.getActiveGeofences());
}
public void setupGeofences(long taskId) {
geofenceApi.register(getGeofencesForTask(taskId));
}
public void cancelGeofences(long taskId) {
for (Location location : getGeofences(taskId)) {
geofenceApi.cancel(location);
}
}
public boolean synchronizeGeofences(final long taskId, Set<Location> locations) {
boolean changed = synchronizeMetadata(taskId, newArrayList(locations), geofenceApi::cancel);
if (changed) {
setupGeofences(taskId);
}
return changed;
}
private boolean synchronizeMetadata(
long taskId, List<Location> locations, final SynchronizeGeofenceCallback callback) {
boolean dirty = false;
for (Location metadatum : locations) {
metadatum.setTask(taskId);
metadatum.setId(0L);
}
for (Location item : locationDao.getGeofences(taskId)) {
long id = item.getId();
// clear item id when matching with incoming values
item.setId(0L);
if (locations.contains(item)) {
locations.remove(item);
} else {
// not matched. cut it
item.setId(id);
if (callback != null) {
callback.beforeDelete(item);
}
locationDao.delete(item.geofence);
dirty = true;
}
}
// everything that remains shall be written
for (Location location : locations) {
Place place = location.place;
Geofence geofence = location.geofence;
geofence.setTask(taskId);
geofence.setPlace(place.getUid());
locationDao.insert(geofence);
dirty = true;
}
return dirty;
}
private List<Location> getGeofencesForTask(long taskId) {
return locationDao.getActiveGeofences(taskId);
}
interface SynchronizeGeofenceCallback {
void beforeDelete(Location location);
}
}

@ -1,14 +1,11 @@
package org.tasks.ui; package org.tasks.ui;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static org.tasks.dialogs.LocationDialog.newLocationDialog; import static org.tasks.dialogs.LocationDialog.newLocationDialog;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
@ -16,7 +13,6 @@ import android.text.style.ClickableSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -25,43 +21,46 @@ import butterknife.OnClick;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.Collection; import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.Geofence;
import org.tasks.data.Location; import org.tasks.data.Location;
import org.tasks.data.LocationDao;
import org.tasks.data.Place;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.LocationDialog; import org.tasks.dialogs.LocationDialog;
import org.tasks.injection.FragmentComponent; import org.tasks.injection.FragmentComponent;
import org.tasks.location.GeofenceService; import org.tasks.location.GeofenceApi;
import org.tasks.location.PlacePicker; import org.tasks.location.PlacePicker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import timber.log.Timber;
public class LocationControlSet extends TaskEditControlFragment { public class LocationControlSet extends TaskEditControlFragment {
public static final int TAG = R.string.TEA_ctrl_locations_pref; public static final int TAG = R.string.TEA_ctrl_locations_pref;
private static final int REQUEST_LOCATION_REMINDER = 12153; private static final int REQUEST_LOCATION_REMINDER = 12153;
private static final int REQUEST_LOCATION_DETAILS = 12154; private static final int REQUEST_LOCATION_DETAILS = 12154;
private static final String FRAG_TAG_LOCATION_DIALOG = "location_dialog"; private static final String FRAG_TAG_LOCATION_DIALOG = "location_dialog";
private static final String EXTRA_ORIGINAL = "extra_original_location";
private static final String EXTRA_LOCATION = "extra_new_location";
private static final String EXTRA_GEOFENCES = "extra_geofences";
private final Set<Location> locations = new LinkedHashSet<>();
@Inject GeofenceService geofenceService;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject GeofenceApi geofenceApi;
@Inject LocationDao locationDao;
@BindView(R.id.location_name)
TextView locationName;
@BindView(R.id.alert_container) @BindView(R.id.location_address)
LinearLayout alertContainer; TextView locationAddress;
@BindView(R.id.alarms_add) @BindView(R.id.location_more)
View addLocation; View locationOptions;
private long taskId; private long taskId;
private Location original;
private Location location;
@Nullable @Nullable
@Override @Override
@ -70,27 +69,39 @@ public class LocationControlSet extends TaskEditControlFragment {
View view = super.onCreateView(inflater, container, savedInstanceState); View view = super.onCreateView(inflater, container, savedInstanceState);
taskId = task.getId(); taskId = task.getId();
if (savedInstanceState == null) { if (savedInstanceState == null) {
locations.addAll(geofenceService.getGeofences(taskId)); original = locationDao.getGeofences(taskId);
location = original;
} else { } else {
List<Parcelable> geofenceArray = savedInstanceState.getParcelableArrayList(EXTRA_GEOFENCES); original = savedInstanceState.getParcelable(EXTRA_ORIGINAL);
for (Parcelable geofence : geofenceArray) { location = savedInstanceState.getParcelable(EXTRA_LOCATION);
locations.add((Location) geofence);
}
} }
setup(locations);
updateUI();
return view; return view;
} }
@OnClick(R.id.alarms_add) @OnClick({R.id.location_name, R.id.location_address})
void addAlarm(View view) { void addAlarm(View view) {
pickLocation(); if (location == null) {
startActivityForResult(PlacePicker.getIntent(getActivity()), REQUEST_LOCATION_REMINDER);
} else {
openMap();
}
}
@OnClick(R.id.location_more)
void locationOptions(View view) {
LocationDialog dialog = newLocationDialog(location);
dialog.setTargetFragment(this, REQUEST_LOCATION_DETAILS);
dialog.show(getFragmentManager(), FRAG_TAG_LOCATION_DIALOG);
} }
@Override @Override
protected int getLayout() { protected int getLayout() {
return R.layout.control_set_locations; return R.layout.location_row;
} }
@Override @Override
@ -103,104 +114,90 @@ public class LocationControlSet extends TaskEditControlFragment {
return TAG; return TAG;
} }
private void setup(Collection<Location> locations) { private void updateUI() {
if (locations.isEmpty()) { if (location == null) {
alertContainer.setVisibility(View.GONE); locationName.setText("");
addLocation.setVisibility(View.VISIBLE); locationOptions.setVisibility(View.GONE);
locationAddress.setVisibility(View.GONE);
} else { } else {
addLocation.setVisibility(View.GONE); locationOptions.setVisibility(View.VISIBLE);
alertContainer.setVisibility(View.VISIBLE); String name = location.getDisplayName();
alertContainer.removeAllViews(); String address = location.getAddress();
for (Location location : locations) { if (!Strings.isNullOrEmpty(address) && !address.equals(name)) {
addGeolocationReminder(location); locationAddress.setText(address);
locationAddress.setVisibility(View.VISIBLE);
} else {
locationAddress.setVisibility(View.GONE);
} }
SpannableString spannableString = new SpannableString(name);
spannableString.setSpan(
new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
openMap();
}
},
0,
name.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
locationName.setText(spannableString);
locationName.setMovementMethod(LinkMovementMethod.getInstance());
} }
} }
private void openMap() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(location.getGeoUri()));
startActivity(intent);
}
@Override @Override
public boolean hasChanges(Task original) { public boolean hasChanges(Task task) {
return !newHashSet(geofenceService.getGeofences(taskId)).equals(locations); return original == null ? location == null : !original.equals(location);
} }
@Override @Override
public void apply(Task task) { public void apply(Task task) {
if (geofenceService.synchronizeGeofences(task.getId(), locations)) { if (original != null) {
task.setModificationDate(DateUtilities.now()); geofenceApi.cancel(original);
locationDao.delete(original.geofence);
} }
if (location != null) {
Place place = location.place;
Geofence geofence = location.geofence;
geofence.setTask(taskId);
geofence.setPlace(place.getUid());
geofence.setId(locationDao.insert(geofence));
geofenceApi.register(Collections.singletonList(location));
}
task.setModificationDate(DateUtilities.now());
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putParcelableArrayList(EXTRA_GEOFENCES, newArrayList(locations)); outState.putParcelable(EXTRA_ORIGINAL, original);
outState.putParcelable(EXTRA_LOCATION, location);
} }
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_LOCATION_REMINDER) { if (requestCode == REQUEST_LOCATION_REMINDER) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
locations.clear(); location = PlacePicker.getPlace(data, preferences);
locations.add(PlacePicker.getPlace(data, preferences)); updateUI();
setup(locations);
} }
} else if (requestCode == REQUEST_LOCATION_DETAILS) { } else if (requestCode == REQUEST_LOCATION_DETAILS) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
Location original = data.getParcelableExtra(LocationDialog.EXTRA_ORIGINAL); location = data.getParcelableExtra(LocationDialog.EXTRA_LOCATION);
Location location = data.getParcelableExtra(LocationDialog.EXTRA_LOCATION); updateUI();
Timber.d("original: %s, updated: %s", original, location);
locations.remove(original);
if (location != null) {
locations.add(location);
}
setup(locations);
} }
} else { } else {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }
} }
private void pickLocation() {
startActivityForResult(PlacePicker.getIntent(getActivity()), REQUEST_LOCATION_REMINDER);
}
private void addGeolocationReminder(final Location location) {
final View alertItem = getActivity().getLayoutInflater().inflate(R.layout.location_row, null);
alertContainer.addView(alertItem);
String name = location.getDisplayName();
String address = location.getAddress();
if (!Strings.isNullOrEmpty(address) && !address.equals(name)) {
TextView addressView = alertItem.findViewById(R.id.location_address);
addressView.setText(address);
addressView.setVisibility(View.VISIBLE);
}
SpannableString spannableString = new SpannableString(name);
spannableString.setSpan(
new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(location.getGeoUri()));
startActivity(intent);
}
},
0,
name.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView nameView = alertItem.findViewById(R.id.location_name);
nameView.setText(spannableString);
nameView.setMovementMethod(LinkMovementMethod.getInstance());
alertItem
.findViewById(R.id.location_more)
.setOnClickListener(
v -> {
LocationDialog dialog = newLocationDialog(location);
dialog.setTargetFragment(this, REQUEST_LOCATION_DETAILS);
dialog.show(getFragmentManager(), FRAG_TAG_LOCATION_DIALOG);
});
}
@Override @Override
protected void inject(FragmentComponent component) { protected void inject(FragmentComponent component) {
component.inject(this); component.inject(this);

@ -1,48 +1,54 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false" android:baselineAligned="false">
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="100"
android:orientation="vertical">
<TextView
android:id="@+id/location_name"
style="@style/TaskEditTextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/asTextColor"/>
<TextView
android:id="@+id/location_address"
style="@style/TaskEditTextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?attr/asTextColorHint"
android:visibility="gone"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"/>
</LinearLayout>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android" <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/location_more" android:id="@+id/location_more"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_gravity="top|center" android:layout_gravity="top|center"
android:alpha="?attr/alpha_secondary" android:alpha="?attr/alpha_secondary"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_outline_more_vert_24px" android:src="@drawable/ic_outline_more_vert_24px"
android:tint="?attr/icon_tint"/> android:tint="?attr/icon_tint"/>
</LinearLayout> <TextView
android:id="@+id/location_name"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_toLeftOf="@id/location_more"
android:layout_toStartOf="@id/location_more"
android:clickable="true"
android:focusable="true"
android:hint="@string/add_location"
android:textColor="?attr/asTextColor"
android:textColorHint="@color/text_secondary"/>
<TextView
android:id="@+id/location_address"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@id/location_name"
android:layout_toLeftOf="@id/location_more"
android:layout_toStartOf="@id/location_more"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:maxLines="1"
android:singleLine="true"
android:textColor="?attr/asTextColorHint"
android:visibility="gone"/>
</RelativeLayout>

Loading…
Cancel
Save