mirror of https://github.com/tasks/tasks
Convert calendar picker to compose
parent
437d69d741
commit
4f267ad989
@ -1,128 +0,0 @@
|
||||
package org.tasks.calendars;
|
||||
|
||||
import static com.google.common.collect.Lists.transform;
|
||||
import static org.tasks.PermissionUtil.verifyPermissions;
|
||||
import static org.tasks.Strings.isNullOrEmpty;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import org.tasks.R;
|
||||
import org.tasks.dialogs.DialogBuilder;
|
||||
import org.tasks.preferences.FragmentPermissionRequestor;
|
||||
import org.tasks.preferences.PermissionChecker;
|
||||
import org.tasks.preferences.PermissionRequestor;
|
||||
import org.tasks.themes.Theme;
|
||||
import org.tasks.ui.SingleCheckedArrayAdapter;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class CalendarPicker extends DialogFragment {
|
||||
|
||||
public static final String EXTRA_CALENDAR_ID = "extra_calendar_id";
|
||||
public static final String EXTRA_CALENDAR_NAME = "extra_calendar_name";
|
||||
private static final String EXTRA_SELECTED = "extra_selected";
|
||||
private final List<String> calendarNames = new ArrayList<>();
|
||||
private final List<AndroidCalendar> calendars = new ArrayList<>();
|
||||
@Inject DialogBuilder dialogBuilder;
|
||||
@Inject CalendarProvider calendarProvider;
|
||||
@Inject PermissionChecker permissionChecker;
|
||||
@Inject FragmentPermissionRequestor permissionRequestor;
|
||||
@Inject Theme theme;
|
||||
private SingleCheckedArrayAdapter adapter;
|
||||
private ListView listView;
|
||||
|
||||
public static CalendarPicker newCalendarPicker(Fragment target, int rc, String selected) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(EXTRA_SELECTED, selected);
|
||||
CalendarPicker fragment = new CalendarPicker();
|
||||
fragment.setArguments(arguments);
|
||||
fragment.setTargetFragment(target, rc);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
theme.applyToContext(getActivity());
|
||||
|
||||
adapter =
|
||||
new SingleCheckedArrayAdapter(getActivity(), calendarNames) {
|
||||
@Override
|
||||
protected int getDrawable() {
|
||||
return R.drawable.ic_outline_event_24px;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDrawableColor(int position) {
|
||||
return calendars.get(position).getColor();
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog dialog =
|
||||
dialogBuilder
|
||||
.newDialog()
|
||||
.setSingleChoiceItems(
|
||||
adapter, -1, (d, which) -> {
|
||||
dismiss();
|
||||
AndroidCalendar calendar = calendars.get(which);
|
||||
Intent data = new Intent();
|
||||
data.putExtra(EXTRA_CALENDAR_ID, calendar.getId());
|
||||
data.putExtra(EXTRA_CALENDAR_NAME, calendar.getName());
|
||||
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
listView = dialog.getListView();
|
||||
if (permissionChecker.canAccessCalendars()) {
|
||||
loadCalendars();
|
||||
} else if (savedInstanceState == null) {
|
||||
permissionRequestor.requestCalendarPermissions();
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private void loadCalendars() {
|
||||
calendars.clear();
|
||||
calendarNames.clear();
|
||||
|
||||
calendars.addAll(calendarProvider.getCalendars());
|
||||
if (calendars.isEmpty()) {
|
||||
Toast.makeText(getActivity(), R.string.no_calendars_found, Toast.LENGTH_LONG).show();
|
||||
dismiss();
|
||||
} else {
|
||||
calendars.add(0, new AndroidCalendar(null, getString(R.string.dont_add_to_calendar), -1));
|
||||
calendarNames.addAll(transform(calendars, AndroidCalendar::getName));
|
||||
Bundle arguments = getArguments();
|
||||
String selected = arguments.getString(EXTRA_SELECTED);
|
||||
int selectedIndex = isNullOrEmpty(selected) ? 0 : calendarNames.indexOf(selected);
|
||||
adapter.notifyDataSetChanged();
|
||||
listView.setItemChecked(selectedIndex, true);
|
||||
listView.setSelection(selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == PermissionRequestor.REQUEST_CALENDAR) {
|
||||
if (verifyPermissions(grantResults)) {
|
||||
loadCalendars();
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
} else {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
package org.tasks.calendars
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Block
|
||||
import androidx.compose.material.icons.outlined.Check
|
||||
import androidx.compose.material.icons.outlined.Event
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.tasks.R
|
||||
import org.tasks.compose.collectAsStateLifecycleAware
|
||||
import org.tasks.dialogs.DialogBuilder
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CalendarPicker : DialogFragment() {
|
||||
private val viewModel: CalendarPickerViewModel by viewModels()
|
||||
|
||||
@Inject lateinit var dialogBuilder: DialogBuilder
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return dialogBuilder
|
||||
.newDialog()
|
||||
.setContent {
|
||||
val hasPermissions = rememberMultiplePermissionsState(
|
||||
permissions = listOf(Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR),
|
||||
onPermissionsResult = { result ->
|
||||
if (result.values.all { it }) {
|
||||
viewModel.loadCalendars()
|
||||
}
|
||||
}
|
||||
)
|
||||
if (hasPermissions.allPermissionsGranted) {
|
||||
CalendarList(
|
||||
calendars = viewModel.viewState.collectAsStateLifecycleAware().value.calendars,
|
||||
selected = arguments?.getString(EXTRA_SELECTED),
|
||||
onClick = { selectEntry(it) },
|
||||
)
|
||||
}
|
||||
LaunchedEffect(hasPermissions) {
|
||||
if (!hasPermissions.allPermissionsGranted) {
|
||||
hasPermissions.launchMultiplePermissionRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CalendarList(
|
||||
calendars: List<AndroidCalendar>,
|
||||
selected: String?,
|
||||
onClick: (AndroidCalendar?) -> Unit,
|
||||
) {
|
||||
MdcTheme {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(vertical = 12.dp)
|
||||
) {
|
||||
CalendarRow(
|
||||
icon = Icons.Outlined.Block,
|
||||
tint = MaterialTheme.colors.onSurface,
|
||||
text = stringResource(id = R.string.dont_add_to_calendar),
|
||||
selected = selected.isNullOrBlank(),
|
||||
onClick = { onClick(null) },
|
||||
)
|
||||
calendars.forEach {
|
||||
CalendarRow(
|
||||
icon = Icons.Outlined.Event,
|
||||
tint = Color(it.color),
|
||||
text = it.name,
|
||||
selected = selected == it.name,
|
||||
onClick = { onClick(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CalendarRow(
|
||||
icon: ImageVector,
|
||||
tint: Color,
|
||||
text: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = tint.copy(alpha = ContentAlpha.medium),
|
||||
modifier = Modifier.padding(start = 16.dp, end = 32.dp, top = 12.dp, bottom = 12.dp),
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.body1,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (selected) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Check,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.medium),
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectEntry(calendar: AndroidCalendar?) {
|
||||
val data = Intent()
|
||||
data.putExtra(EXTRA_CALENDAR_ID, calendar?.id)
|
||||
data.putExtra(EXTRA_CALENDAR_NAME, calendar?.name)
|
||||
targetFragment!!.onActivityResult(targetRequestCode, Activity.RESULT_OK, data)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_CALENDAR_ID = "extra_calendar_id"
|
||||
const val EXTRA_CALENDAR_NAME = "extra_calendar_name"
|
||||
private const val EXTRA_SELECTED = "extra_selected"
|
||||
|
||||
fun newCalendarPicker(target: Fragment?, rc: Int, selected: String?): CalendarPicker {
|
||||
val arguments = Bundle()
|
||||
arguments.putString(EXTRA_SELECTED, selected)
|
||||
val fragment = CalendarPicker()
|
||||
fragment.arguments = arguments
|
||||
fragment.setTargetFragment(target, rc)
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package org.tasks.calendars
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class CalendarPickerViewModel @Inject constructor(
|
||||
private val calendarProvider: CalendarProvider
|
||||
) : ViewModel() {
|
||||
|
||||
data class ViewState(
|
||||
val calendars: List<AndroidCalendar> = emptyList(),
|
||||
)
|
||||
|
||||
private val _viewState = MutableStateFlow(ViewState())
|
||||
val viewState: StateFlow<ViewState>
|
||||
get() = _viewState.asStateFlow()
|
||||
|
||||
fun loadCalendars() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
_viewState.update { it.copy(calendars = calendarProvider.calendars) }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
loadCalendars()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue