Convert LocationControlSet to compose

pull/1924/head
Alex Baker 2 years ago
parent 08069d0a3d
commit 8f4db8374d

@ -0,0 +1,20 @@
package org.tasks.compose
import androidx.compose.material.ContentAlpha
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@Composable
fun DisabledText(
text: String,
modifier: Modifier = Modifier
) {
Text(
text = text,
style = MaterialTheme.typography.body1,
modifier = modifier.alpha(alpha = ContentAlpha.disabled)
)
}

@ -3,10 +3,15 @@ package org.tasks.preferences;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastQ;
import static java.util.Arrays.asList;
import android.Manifest.permission;
import android.content.Context;
import android.content.pm.PackageManager;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.qualifiers.ApplicationContext;
@ -34,9 +39,7 @@ public class PermissionChecker {
}
public boolean canAccessBackgroundLocation() {
return atLeastQ()
? canAccessForegroundLocation() && checkPermissions(permission.ACCESS_BACKGROUND_LOCATION)
: canAccessForegroundLocation();
return checkPermissions(backgroundPermissions().toArray(new String[0]));
}
private boolean checkPermissions(String... permissions) {
@ -48,4 +51,10 @@ public class PermissionChecker {
}
return true;
}
public static List<String> backgroundPermissions() {
return atLeastQ()
? asList(permission.ACCESS_FINE_LOCATION, permission.ACCESS_BACKGROUND_LOCATION)
: Collections.singletonList(permission.ACCESS_FINE_LOCATION);
}
}

@ -4,21 +4,30 @@ import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Parcelable
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ClickableSpan
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material.icons.outlined.NotificationsOff
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.util.Pair
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.compose.DisabledText
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.data.Geofence
import org.tasks.data.Location
import org.tasks.data.Place
import org.tasks.databinding.LocationRowBinding
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.GeofenceDialog
import org.tasks.extensions.Context.openUri
@ -27,78 +36,37 @@ import org.tasks.location.LocationPickerActivity
import org.tasks.preferences.Device
import org.tasks.preferences.FragmentPermissionRequestor
import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.PermissionChecker.backgroundPermissions
import org.tasks.preferences.Preferences
import javax.inject.Inject
@AndroidEntryPoint
class LocationControlSet : TaskEditControlFragment() {
class LocationControlSet : TaskEditControlComposeFragment() {
@Inject lateinit var preferences: Preferences
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var device: Device
@Inject lateinit var permissionRequestor: FragmentPermissionRequestor
@Inject lateinit var permissionChecker: PermissionChecker
private lateinit var locationName: TextView
private lateinit var locationAddress: TextView
private lateinit var geofenceOptions: ImageView
override fun onResume() {
super.onResume()
updateUi()
}
private fun setLocation(location: Location?) {
viewModel.selectedLocation = location
updateUi()
}
private fun updateUi() {
val location = viewModel.selectedLocation
if (location == null) {
locationName.text = ""
geofenceOptions.visibility = View.GONE
locationAddress.visibility = View.GONE
} else {
geofenceOptions.visibility = View.VISIBLE
geofenceOptions.setImageResource(
if (permissionChecker.canAccessBackgroundLocation()
&& (location.isArrival || location.isDeparture)) R.drawable.ic_outline_notifications_24px else R.drawable.ic_outline_notifications_off_24px)
val name = location.displayName
val address = location.displayAddress
if (!isNullOrEmpty(address) && address != name) {
locationAddress.text = address
locationAddress.visibility = View.VISIBLE
} else {
locationAddress.visibility = View.GONE
}
val spannableString = SpannableString(name)
spannableString.setSpan(
object : ClickableSpan() {
override fun onClick(view: View) {}
},
0,
name.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
locationName.text = spannableString
}
viewModel.selectedLocation.value = location
}
override fun onRowClick() {
val location = viewModel.selectedLocation
val location = viewModel.selectedLocation.value
if (location == null) {
chooseLocation()
} else {
val options: MutableList<Pair<Int, () -> Unit>> = ArrayList()
options.add(Pair.create(R.string.open_map, { location.open(activity) }))
options.add(Pair.create(R.string.open_map) { location.open(activity) })
if (!isNullOrEmpty(location.phone)) {
options.add(Pair.create(R.string.action_call, { call() }))
options.add(Pair.create(R.string.action_call) { call() })
}
if (!isNullOrEmpty(location.url)) {
options.add(Pair.create(R.string.visit_website, { openWebsite() }))
options.add(Pair.create(R.string.visit_website) { openWebsite() })
}
options.add(Pair.create(R.string.choose_new_location, { chooseLocation() }))
options.add(Pair.create(R.string.delete, { setLocation(null) }))
options.add(Pair.create(R.string.choose_new_location) { chooseLocation() })
options.add(Pair.create(R.string.delete) { setLocation(null) })
val items = options.map { requireContext().getString(it.first!!) }
dialogBuilder
.newDialog(location.displayName)
@ -111,36 +79,46 @@ class LocationControlSet : TaskEditControlFragment() {
private fun chooseLocation() {
val intent = Intent(activity, LocationPickerActivity::class.java)
viewModel.selectedLocation?.let {
viewModel.selectedLocation.value?.let {
intent.putExtra(LocationPickerActivity.EXTRA_PLACE, it.place as Parcelable)
}
startActivityForResult(intent, REQUEST_LOCATION_REMINDER)
}
private fun geofenceOptions() {
if (permissionChecker.canAccessBackgroundLocation()) {
showGeofenceOptions()
} else {
newLocationPermissionDialog(this, REQUEST_LOCATION_PERMISSIONS)
.show(parentFragmentManager, FRAG_TAG_REQUEST_LOCATION)
}
}
private fun showGeofenceOptions() {
val dialog = GeofenceDialog.newGeofenceDialog(viewModel.selectedLocation)
val dialog = GeofenceDialog.newGeofenceDialog(viewModel.selectedLocation.value)
dialog.setTargetFragment(this, REQUEST_GEOFENCE_DETAILS)
dialog.show(parentFragmentManager, FRAG_TAG_LOCATION_DIALOG)
}
override fun bind(parent: ViewGroup?) =
LocationRowBinding.inflate(layoutInflater, parent, true).let {
locationName = it.locationName
locationAddress = it.locationAddress
geofenceOptions = it.geofenceOptions.apply {
setOnClickListener { geofenceOptions() }
}
it.root
@OptIn(ExperimentalPermissionsApi::class)
@Composable
override fun Body() {
val location = viewModel.selectedLocation.collectAsStateLifecycleAware().value
val hasPermissions =
rememberMultiplePermissionsState(permissions = backgroundPermissions())
.allPermissionsGranted
if (location == null) {
DisabledText(
text = stringResource(id = R.string.add_location),
modifier = Modifier.padding(vertical = 20.dp)
)
} else {
LocationRow(
name = location.displayName,
address = location.displayAddress,
onClick = {
if (hasPermissions) {
showGeofenceOptions()
} else {
newLocationPermissionDialog(this, REQUEST_LOCATION_PERMISSIONS)
.show(parentFragmentManager, FRAG_TAG_REQUEST_LOCATION)
}
},
geofenceOn = hasPermissions && (location.isArrival || location.isDeparture)
)
}
}
override val icon = R.drawable.ic_outline_place_24px
@ -149,11 +127,11 @@ class LocationControlSet : TaskEditControlFragment() {
override val isClickable = true
private fun openWebsite() {
viewModel.selectedLocation?.let { context?.openUri(it.url) }
viewModel.selectedLocation.value?.let { context?.openUri(it.url) }
}
private fun call() {
viewModel.selectedLocation?.let {
viewModel.selectedLocation.value?.let {
startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + it.phone)))
}
}
@ -166,7 +144,7 @@ class LocationControlSet : TaskEditControlFragment() {
} else if (requestCode == REQUEST_LOCATION_REMINDER) {
if (resultCode == Activity.RESULT_OK) {
val place: Place = data!!.getParcelableExtra(LocationPickerActivity.EXTRA_PLACE)!!
val location = viewModel.selectedLocation
val location = viewModel.selectedLocation.value
val geofence = if (location == null) {
Geofence(place.uid, preferences)
} else {
@ -183,7 +161,7 @@ class LocationControlSet : TaskEditControlFragment() {
if (resultCode == Activity.RESULT_OK) {
setLocation(Location(
data?.getParcelableExtra(GeofenceDialog.EXTRA_GEOFENCE) ?: return,
viewModel.selectedLocation?.place ?: return
viewModel.selectedLocation.value?.place ?: return
))
}
} else {
@ -199,4 +177,39 @@ class LocationControlSet : TaskEditControlFragment() {
private const val FRAG_TAG_LOCATION_DIALOG = "location_dialog"
private const val FRAG_TAG_REQUEST_LOCATION = "request_location"
}
}
}
@Composable
fun LocationRow(
name: String,
address: String?,
geofenceOn: Boolean,
onClick: () -> Unit,
) {
Row {
Column(
modifier = Modifier
.weight(1f)
.padding(vertical = 20.dp)
) {
Text(text = name)
address?.takeIf { it.isNotBlank() && it != name }?.let {
Text(text = address)
}
}
IconButton(
onClick = onClick,
modifier = Modifier.padding(top = 8.dp /* + 12dp from icon */)
) {
Icon(
imageVector = if (geofenceOn) {
Icons.Outlined.Notifications
} else {
Icons.Outlined.NotificationsOff
},
contentDescription = null
)
}
}
}

@ -251,10 +251,10 @@ class TaskEditViewModel @Inject constructor(
var originalLocation: Location? = null
private set(value) {
field = value
selectedLocation = value
selectedLocation.value = value
}
var selectedLocation: Location? = null
var selectedLocation = MutableStateFlow<Location?>(null)
private lateinit var originalTags: List<TagData>
@ -311,7 +311,7 @@ class TaskEditViewModel @Inject constructor(
task.elapsedSeconds != elapsedSeconds ||
task.estimatedSeconds != estimatedSeconds ||
originalList != selectedList.value ||
originalLocation != selectedLocation ||
originalLocation != selectedLocation.value ||
originalTags.toHashSet() != selectedTags.value.toHashSet() ||
newSubtasks.isNotEmpty() ||
getRingFlags() != when {
@ -355,14 +355,14 @@ class TaskEditViewModel @Inject constructor(
taskMover.move(listOf(task.id), selectedList.value!!)
}
if ((isNew && selectedLocation != null) || originalLocation != selectedLocation) {
if ((isNew && selectedLocation.value != null) || originalLocation != selectedLocation.value) {
originalLocation?.let { location ->
if (location.geofence.id > 0) {
locationDao.delete(location.geofence)
geofenceApi.update(location.place)
}
}
selectedLocation?.let { location ->
selectedLocation.value?.let { location ->
val place = location.place
val geofence = location.geofence
geofence.task = task.id

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false">
<ImageView
android:id="@+id/geofence_options"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="top|center"
android:alpha="@dimen/alpha_secondary"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_outline_notifications_off_24px"
app:tint="@color/icon_tint"
android:visibility="gone"/>
<TextView
android:id="@+id/location_name"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/geofence_options"
android:gravity="start"
android:hint="@string/add_location"
android:textAlignment="viewStart"/>
<TextView
android:id="@+id/location_address"
style="@style/TaskEditTextPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@id/location_name"
android:layout_toStartOf="@id/geofence_options"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="?android:textColorSecondary"
android:visibility="gone"/>
</RelativeLayout>
Loading…
Cancel
Save