List and account settings Kotlin conversions

pull/1052/head
Alex Baker 4 years ago
parent ae11547963
commit 320623045e

@ -1,190 +0,0 @@
package org.tasks.activities;
import static org.tasks.dialogs.IconPickerDialog.newIconPicker;
import static org.tasks.themes.DrawableUtil.getLeftDrawable;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.dialogs.ColorPalettePicker;
import org.tasks.dialogs.ColorPickerAdapter.Palette;
import org.tasks.dialogs.ColorWheelPicker;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.dialogs.IconPickerDialog.IconPickerCallback;
import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.themes.ColorProvider;
import org.tasks.themes.CustomIcons;
import org.tasks.themes.DrawableUtil;
import org.tasks.themes.ThemeColor;
public abstract class BaseListSettingsActivity extends ThemedInjectingAppCompatActivity
implements IconPickerCallback,
OnMenuItemClickListener,
ColorPalettePicker.ColorPickedCallback,
ColorWheelPicker.ColorPickedCallback {
private static final String EXTRA_SELECTED_THEME = "extra_selected_theme";
private static final String EXTRA_SELECTED_ICON = "extra_selected_icon";
private static final String FRAG_TAG_ICON_PICKER = "frag_tag_icon_picker";
private static final String FRAG_TAG_COLOR_PICKER = "frag_tag_color_picker";
protected int selectedColor = 0;
protected int selectedIcon = -1;
@BindView(R.id.clear)
View clear;
@BindView(R.id.color)
TextView color;
@BindView(R.id.icon)
TextView icon;
@BindView(R.id.toolbar)
protected Toolbar toolbar;
@Inject DialogBuilder dialogBuilder;
@Inject ColorProvider colorProvider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
ButterKnife.bind(this);
if (savedInstanceState != null) {
selectedColor = savedInstanceState.getInt(EXTRA_SELECTED_THEME);
selectedIcon = savedInstanceState.getInt(EXTRA_SELECTED_ICON);
}
toolbar.setTitle(getToolbarTitle());
toolbar.setNavigationIcon(getDrawable(R.drawable.ic_outline_save_24px));
toolbar.setNavigationOnClickListener(v -> save());
if (!isNew()) {
toolbar.inflateMenu(R.menu.menu_tag_settings);
}
toolbar.setOnMenuItemClickListener(this);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(EXTRA_SELECTED_THEME, selectedColor);
outState.putInt(EXTRA_SELECTED_ICON, selectedIcon);
}
@Override
public void onBackPressed() {
discard();
}
protected abstract int getLayout();
protected abstract boolean hasChanges();
protected abstract void save();
protected abstract boolean isNew();
protected abstract String getToolbarTitle();
protected abstract void delete();
protected void discard() {
if (!hasChanges()) {
finish();
} else {
dialogBuilder
.newDialog(R.string.discard_changes)
.setPositiveButton(R.string.discard, (dialog, which) -> finish())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
@OnClick(R.id.clear)
protected void clearColor() {
onColorPicked(0);
}
@OnClick(R.id.color_row)
protected void showThemePicker() {
ColorPalettePicker.Companion.newColorPalette(null, 0, selectedColor, Palette.COLORS)
.show(getSupportFragmentManager(), FRAG_TAG_COLOR_PICKER);
}
@OnClick(R.id.icon_row)
protected void showIconPicker() {
newIconPicker(selectedIcon).show(getSupportFragmentManager(), FRAG_TAG_ICON_PICKER);
}
@Override
public void onSelected(DialogInterface dialogInterface, int icon) {
this.selectedIcon = icon;
dialogInterface.dismiss();
updateTheme();
}
@Override
public void onColorPicked(int color) {
selectedColor = color;
updateTheme();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.delete) {
promptDelete();
return true;
}
return onOptionsItemSelected(item);
}
protected void promptDelete() {
dialogBuilder
.newDialog(R.string.delete_tag_confirmation, getToolbarTitle())
.setPositiveButton(R.string.delete, (dialog, which) -> delete())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
protected void updateTheme() {
ThemeColor themeColor;
if (selectedColor == 0) {
themeColor = this.themeColor;
DrawableUtil.setLeftDrawable(this, color, R.drawable.ic_outline_not_interested_24px);
getLeftDrawable(color).setTint(getColor(R.color.icon_tint_with_alpha));
clear.setVisibility(View.GONE);
} else {
themeColor = colorProvider.getThemeColor(selectedColor, true);
DrawableUtil.setLeftDrawable(this, color, R.drawable.color_picker);
Drawable leftDrawable = getLeftDrawable(color);
(leftDrawable instanceof LayerDrawable
? ((LayerDrawable) leftDrawable).getDrawable(0)
: leftDrawable)
.setTint(themeColor.getPrimaryColor());
clear.setVisibility(View.VISIBLE);
}
themeColor.apply(toolbar);
themeColor.applyToSystemBars(this);
Integer icon = CustomIcons.getIconResId(selectedIcon);
if (icon == null) {
icon = CustomIcons.getIconResId(CustomIcons.LIST);
}
DrawableUtil.setLeftDrawable(this, this.icon, icon);
getLeftDrawable(this.icon).setTint(getColor(R.color.icon_tint_with_alpha));
}
}

@ -0,0 +1,166 @@
package org.tasks.activities
import android.content.DialogInterface
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import org.tasks.R
import org.tasks.dialogs.ColorPalettePicker
import org.tasks.dialogs.ColorPalettePicker.Companion.newColorPalette
import org.tasks.dialogs.ColorPickerAdapter.Palette
import org.tasks.dialogs.ColorWheelPicker
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.IconPickerDialog
import org.tasks.dialogs.IconPickerDialog.IconPickerCallback
import org.tasks.injection.ThemedInjectingAppCompatActivity
import org.tasks.themes.ColorProvider
import org.tasks.themes.CustomIcons
import org.tasks.themes.CustomIcons.getIconResId
import org.tasks.themes.DrawableUtil
import org.tasks.themes.ThemeColor
import javax.inject.Inject
abstract class BaseListSettingsActivity : ThemedInjectingAppCompatActivity(), IconPickerCallback, Toolbar.OnMenuItemClickListener, ColorPalettePicker.ColorPickedCallback, ColorWheelPicker.ColorPickedCallback {
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var colorProvider: ColorProvider
protected var selectedColor = 0
protected var selectedIcon = -1
@BindView(R.id.clear)
lateinit var clear: View
@BindView(R.id.color)
lateinit var color: TextView
@BindView(R.id.icon)
lateinit var icon: TextView
@BindView(R.id.toolbar)
lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(layout)
ButterKnife.bind(this)
if (savedInstanceState != null) {
selectedColor = savedInstanceState.getInt(EXTRA_SELECTED_THEME)
selectedIcon = savedInstanceState.getInt(EXTRA_SELECTED_ICON)
}
toolbar.title = toolbarTitle
toolbar.navigationIcon = getDrawable(R.drawable.ic_outline_save_24px)
toolbar.setNavigationOnClickListener { save() }
if (!isNew) {
toolbar.inflateMenu(R.menu.menu_tag_settings)
}
toolbar.setOnMenuItemClickListener(this)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(EXTRA_SELECTED_THEME, selectedColor)
outState.putInt(EXTRA_SELECTED_ICON, selectedIcon)
}
override fun onBackPressed() {
discard()
}
protected abstract val layout: Int
protected abstract fun hasChanges(): Boolean
protected abstract fun save()
protected abstract val isNew: Boolean
protected abstract val toolbarTitle: String?
protected abstract fun delete()
protected open fun discard() {
if (!hasChanges()) {
finish()
} else {
dialogBuilder
.newDialog(R.string.discard_changes)
.setPositiveButton(R.string.discard) { _, _ -> finish() }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}
@OnClick(R.id.clear)
fun clearColor() {
onColorPicked(0)
}
@OnClick(R.id.color_row)
fun showThemePicker() {
newColorPalette(null, 0, selectedColor, Palette.COLORS)
.show(supportFragmentManager, FRAG_TAG_COLOR_PICKER)
}
@OnClick(R.id.icon_row)
fun showIconPicker() {
IconPickerDialog.newIconPicker(selectedIcon).show(supportFragmentManager, FRAG_TAG_ICON_PICKER)
}
override fun onSelected(dialogInterface: DialogInterface, icon: Int) {
selectedIcon = icon
dialogInterface.dismiss()
updateTheme()
}
override fun onColorPicked(color: Int) {
selectedColor = color
updateTheme()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
if (item.itemId == R.id.delete) {
promptDelete()
return true
}
return onOptionsItemSelected(item)
}
protected open fun promptDelete() {
dialogBuilder
.newDialog(R.string.delete_tag_confirmation, toolbarTitle)
.setPositiveButton(R.string.delete) { _, _ -> delete() }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
protected fun updateTheme() {
val themeColor: ThemeColor
if (selectedColor == 0) {
themeColor = this.themeColor
DrawableUtil.setLeftDrawable(this, color, R.drawable.ic_outline_not_interested_24px)
DrawableUtil.getLeftDrawable(color).setTint(getColor(R.color.icon_tint_with_alpha))
clear.visibility = View.GONE
} else {
themeColor = colorProvider.getThemeColor(selectedColor, true)
DrawableUtil.setLeftDrawable(this, color, R.drawable.color_picker)
val leftDrawable = DrawableUtil.getLeftDrawable(color)
(if (leftDrawable is LayerDrawable) leftDrawable.getDrawable(0) else leftDrawable)
.setTint(themeColor.primaryColor)
clear.visibility = View.VISIBLE
}
themeColor.apply(toolbar)
themeColor.applyToSystemBars(this)
var icon = getIconResId(selectedIcon)
if (icon == null) {
icon = getIconResId(CustomIcons.LIST)
}
DrawableUtil.setLeftDrawable(this, this.icon, icon!!)
DrawableUtil.getLeftDrawable(this.icon).setTint(getColor(R.color.icon_tint_with_alpha))
}
companion object {
private const val EXTRA_SELECTED_THEME = "extra_selected_theme"
private const val EXTRA_SELECTED_ICON = "extra_selected_icon"
private const val FRAG_TAG_ICON_PICKER = "frag_tag_icon_picker"
private const val FRAG_TAG_COLOR_PICKER = "frag_tag_color_picker"
}
}

@ -1,11 +1,13 @@
package org.tasks.activities
import androidx.hilt.lifecycle.ViewModelInject
import com.google.api.services.tasks.model.TaskList
import com.todoroo.astrid.gtasks.api.GtasksInvoker
import org.tasks.ui.CompletableViewModel
class CreateListViewModel : CompletableViewModel<TaskList?>() {
fun createList(invoker: GtasksInvoker, account: String?, name: String?) {
run { invoker.forAccount(account!!).createGtaskList(name) }
class CreateListViewModel @ViewModelInject constructor(
private val invoker: GtasksInvoker) : CompletableViewModel<TaskList>() {
fun createList(account: String, name: String) {
run { invoker.forAccount(account).createGtaskList(name)!! }
}
}

@ -1,12 +0,0 @@
package org.tasks.activities;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import org.tasks.data.GoogleTaskList;
import org.tasks.ui.ActionViewModel;
@SuppressWarnings("WeakerAccess")
public class DeleteListViewModel extends ActionViewModel {
void deleteList(GtasksInvoker invoker, GoogleTaskList list) {
run(() -> invoker.forAccount(list.getAccount()).deleteGtaskList(list.getRemoteId()));
}
}

@ -0,0 +1,13 @@
package org.tasks.activities
import androidx.hilt.lifecycle.ViewModelInject
import com.todoroo.astrid.gtasks.api.GtasksInvoker
import org.tasks.data.GoogleTaskList
import org.tasks.ui.ActionViewModel
class DeleteListViewModel @ViewModelInject constructor(
private val invoker: GtasksInvoker) : ActionViewModel() {
fun deleteList(list: GoogleTaskList) {
run { invoker.forAccount(list.account!!).deleteGtaskList(list.remoteId) }
}
}

@ -213,13 +213,11 @@ class FilterSettingsActivity : BaseListSettingsActivity() {
outState.putString(EXTRA_CRITERIA, CriterionInstance.serialize(criteria))
}
override fun isNew(): Boolean {
return filter == null
}
override val isNew: Boolean
get() = filter == null
override fun getToolbarTitle(): String {
return if (isNew) getString(R.string.FLA_new_filter) else filter!!.listingTitle
}
override val toolbarTitle: String?
get() = if (isNew) getString(R.string.FLA_new_filter) else filter!!.listingTitle
@OnTextChanged(R.id.name)
fun onTextChanged() {
@ -276,9 +274,8 @@ class FilterSettingsActivity : BaseListSettingsActivity() {
super.finish()
}
override fun getLayout(): Int {
return R.layout.filter_settings_activity
}
override val layout: Int
get() = R.layout.filter_settings_activity
override fun delete() {
filterDao.delete(filter!!.id)

@ -1,249 +0,0 @@
package org.tasks.activities;
import static org.tasks.Strings.isNullOrEmpty;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.lifecycle.ViewModelProvider;
import butterknife.BindView;
import com.google.android.material.textfield.TextInputEditText;
import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.activity.MainActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.gtasks.GtasksListService;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.service.TaskDeleter;
import dagger.hilt.android.AndroidEntryPoint;
import dagger.hilt.android.qualifiers.ApplicationContext;
import javax.inject.Inject;
import kotlin.Unit;
import org.tasks.R;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDaoBlocking;
import timber.log.Timber;
@AndroidEntryPoint
public class GoogleTaskListSettingsActivity extends BaseListSettingsActivity {
public static final String EXTRA_ACCOUNT = "extra_account";
public static final String EXTRA_STORE_DATA = "extra_store_data";
@Inject @ApplicationContext Context context;
@Inject GoogleTaskListDaoBlocking googleTaskListDao;
@Inject GtasksListService gtasksListService;
@Inject TaskDeleter taskDeleter;
@Inject GtasksInvoker gtasksInvoker;
@BindView(R.id.name)
TextInputEditText name;
@BindView(R.id.progress_bar)
ProgressBar progressView;
private boolean isNewList;
private GoogleTaskList gtasksList;
private CreateListViewModel createListViewModel;
private RenameListViewModel renameListViewModel;
private DeleteListViewModel deleteListViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
gtasksList = intent.getParcelableExtra(EXTRA_STORE_DATA);
super.onCreate(savedInstanceState);
ViewModelProvider provider = new ViewModelProvider(this);
createListViewModel = provider.get(CreateListViewModel.class);
renameListViewModel = provider.get(RenameListViewModel.class);
deleteListViewModel = provider.get(DeleteListViewModel.class);
if (gtasksList == null) {
isNewList = true;
gtasksList = new GoogleTaskList();
GoogleTaskAccount account = intent.getParcelableExtra(EXTRA_ACCOUNT);
gtasksList.setAccount(account.getAccount());
}
if (savedInstanceState == null) {
selectedColor = gtasksList.getColor();
selectedIcon = gtasksList.getIcon();
}
if (isNewList) {
name.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT);
} else {
name.setText(gtasksList.getTitle());
}
if (createListViewModel.inProgress()
|| renameListViewModel.inProgress()
|| deleteListViewModel.inProgress()) {
showProgressIndicator();
}
createListViewModel.observe(this, this::onListCreated, this::requestFailed);
renameListViewModel.observe(this, this::onListRenamed, this::requestFailed);
deleteListViewModel.observe(this, this::onListDeleted, this::requestFailed);
updateTheme();
}
@Override
protected boolean isNew() {
return gtasksList == null;
}
@Override
protected String getToolbarTitle() {
return isNew() ? getString(R.string.new_list) : gtasksList.getTitle();
}
private void showProgressIndicator() {
progressView.setVisibility(View.VISIBLE);
}
private void hideProgressIndicator() {
progressView.setVisibility(View.GONE);
}
private boolean requestInProgress() {
return progressView.getVisibility() == View.VISIBLE;
}
@Override
protected void save() {
if (requestInProgress()) {
return;
}
String newName = getNewName();
if (isNullOrEmpty(newName)) {
Toast.makeText(this, R.string.name_cannot_be_empty, Toast.LENGTH_LONG).show();
return;
}
if (isNewList) {
showProgressIndicator();
createListViewModel.createList(gtasksInvoker, gtasksList.getAccount(), newName);
} else if (nameChanged()) {
showProgressIndicator();
renameListViewModel.renameList(gtasksInvoker, gtasksList, newName);
} else {
if (colorChanged() || iconChanged()) {
gtasksList.setColor(selectedColor);
gtasksList.setIcon(selectedIcon);
googleTaskListDao.insertOrReplace(gtasksList);
setResult(
RESULT_OK,
new Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, new GtasksFilter(gtasksList)));
}
finish();
}
}
@Override
public void finish() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(name.getWindowToken(), 0);
super.finish();
}
@Override
protected int getLayout() {
return R.layout.activity_google_task_list_settings;
}
@Override
protected void promptDelete() {
if (!requestInProgress()) {
super.promptDelete();
}
}
@Override
protected void delete() {
showProgressIndicator();
deleteListViewModel.deleteList(gtasksInvoker, gtasksList);
}
@Override
protected void discard() {
if (!requestInProgress()) {
super.discard();
}
}
private String getNewName() {
return name.getText().toString().trim();
}
@Override
protected boolean hasChanges() {
if (isNewList) {
return selectedColor >= 0 || !isNullOrEmpty(getNewName());
}
return colorChanged() || nameChanged() || iconChanged();
}
private boolean colorChanged() {
return selectedColor != gtasksList.getColor();
}
private boolean iconChanged() {
return selectedIcon != gtasksList.getIcon();
}
private boolean nameChanged() {
return !getNewName().equals(gtasksList.getTitle());
}
private Unit onListCreated(TaskList taskList) {
gtasksList.setRemoteId(taskList.getId());
gtasksList.setTitle(taskList.getTitle());
gtasksList.setColor(selectedColor);
gtasksList.setIcon(selectedIcon);
gtasksList.setId(googleTaskListDao.insertOrReplace(gtasksList));
setResult(
RESULT_OK, new Intent().putExtra(MainActivity.OPEN_FILTER, new GtasksFilter(gtasksList)));
finish();
return Unit.INSTANCE;
}
private void onListDeleted(boolean deleted) {
if (deleted) {
taskDeleter.delete(gtasksList);
setResult(RESULT_OK, new Intent(TaskListFragment.ACTION_DELETED));
finish();
}
}
private Unit onListRenamed(TaskList taskList) {
gtasksList.setTitle(taskList.getTitle());
gtasksList.setColor(selectedColor);
gtasksList.setIcon(selectedIcon);
googleTaskListDao.insertOrReplace(gtasksList);
setResult(
RESULT_OK,
new Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, new GtasksFilter(gtasksList)));
finish();
return Unit.INSTANCE;
}
private Unit requestFailed(Throwable error) {
Timber.e(error);
hideProgressIndicator();
Toast.makeText(this, R.string.gtasks_GLA_errorIOAuth, Toast.LENGTH_LONG).show();
return Unit.INSTANCE;
}
}

@ -0,0 +1,214 @@
package org.tasks.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.ProgressBar
import android.widget.Toast
import androidx.activity.viewModels
import butterknife.BindView
import com.google.android.material.textfield.TextInputEditText
import com.google.api.services.tasks.model.TaskList
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.gtasks.GtasksListService
import com.todoroo.astrid.gtasks.api.GtasksInvoker
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskList
import org.tasks.data.GoogleTaskListDaoBlocking
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
@Inject @ApplicationContext lateinit var context: Context
@Inject lateinit var googleTaskListDao: GoogleTaskListDaoBlocking
@Inject lateinit var gtasksListService: GtasksListService
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var gtasksInvoker: GtasksInvoker
@BindView(R.id.name)
lateinit var name: TextInputEditText
@BindView(R.id.progress_bar)
lateinit var progressView: ProgressBar
private var isNewList = false
private lateinit var gtasksList: GoogleTaskList
private val createListViewModel: CreateListViewModel by viewModels()
private val renameListViewModel: RenameListViewModel by viewModels()
private val deleteListViewModel: DeleteListViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
gtasksList = intent.getParcelableExtra(EXTRA_STORE_DATA)
?: GoogleTaskList().apply {
isNewList = true
account = intent.getParcelableExtra<GoogleTaskAccount>(EXTRA_ACCOUNT)!!.account
}
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
selectedColor = gtasksList.getColor()!!
selectedIcon = gtasksList.getIcon()!!
}
if (isNewList) {
name.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT)
} else {
name.setText(gtasksList.title)
}
if (createListViewModel.inProgress
|| renameListViewModel.inProgress
|| deleteListViewModel.inProgress) {
showProgressIndicator()
}
createListViewModel.observe(this, this::onListCreated, this::requestFailed)
renameListViewModel.observe(this, this::onListRenamed, this::requestFailed)
deleteListViewModel.observe(this, this::onListDeleted, this::requestFailed)
updateTheme()
}
override val isNew: Boolean
get() = isNewList
override val toolbarTitle: String?
get() = if (isNew) getString(R.string.new_list) else gtasksList.title!!
private fun showProgressIndicator() {
progressView.visibility = View.VISIBLE
}
private fun hideProgressIndicator() {
progressView.visibility = View.GONE
}
private fun requestInProgress() = progressView.visibility == View.VISIBLE
override fun save() {
if (requestInProgress()) {
return
}
val newName = newName
if (isNullOrEmpty(newName)) {
Toast.makeText(this, R.string.name_cannot_be_empty, Toast.LENGTH_LONG).show()
return
}
when {
isNewList -> {
showProgressIndicator()
createListViewModel.createList(gtasksList.account!!, newName)
}
nameChanged() -> {
showProgressIndicator()
renameListViewModel.renameList(gtasksList, newName)
}
else -> {
if (colorChanged() || iconChanged()) {
gtasksList.setColor(selectedColor)
gtasksList.setIcon(selectedIcon)
googleTaskListDao.insertOrReplace(gtasksList)
setResult(
Activity.RESULT_OK,
Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, GtasksFilter(gtasksList)))
}
finish()
}
}
}
override fun finish() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(name.windowToken, 0)
super.finish()
}
override val layout: Int
get() = R.layout.activity_google_task_list_settings
override fun promptDelete() {
if (!requestInProgress()) {
super.promptDelete()
}
}
override fun delete() {
showProgressIndicator()
deleteListViewModel.deleteList(gtasksList)
}
override fun discard() {
if (!requestInProgress()) {
super.discard()
}
}
private val newName: String
get() = name.text.toString().trim { it <= ' ' }
override fun hasChanges(): Boolean {
return if (isNewList) {
selectedColor >= 0 || !isNullOrEmpty(newName)
} else colorChanged() || nameChanged() || iconChanged()
}
private fun colorChanged() = selectedColor != gtasksList.getColor()
private fun iconChanged() = selectedIcon != gtasksList.getIcon()
private fun nameChanged() = newName != gtasksList.title
private fun onListCreated(taskList: TaskList) {
gtasksList.remoteId = taskList.id
gtasksList.title = taskList.title
gtasksList.setColor(selectedColor)
gtasksList.setIcon(selectedIcon)
gtasksList.id = googleTaskListDao.insertOrReplace(gtasksList)
setResult(
Activity.RESULT_OK, Intent().putExtra(MainActivity.OPEN_FILTER, GtasksFilter(gtasksList)))
finish()
return
}
private fun onListDeleted(deleted: Boolean) {
if (deleted) {
taskDeleter.delete(gtasksList)
setResult(Activity.RESULT_OK, Intent(TaskListFragment.ACTION_DELETED))
finish()
}
}
private fun onListRenamed(taskList: TaskList) {
gtasksList.title = taskList.title
gtasksList.setColor(selectedColor)
gtasksList.setIcon(selectedIcon)
googleTaskListDao.insertOrReplace(gtasksList)
setResult(
Activity.RESULT_OK,
Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, GtasksFilter(gtasksList)))
finish()
return
}
private fun requestFailed(error: Throwable) {
Timber.e(error)
hideProgressIndicator()
Toast.makeText(this, R.string.gtasks_GLA_errorIOAuth, Toast.LENGTH_LONG).show()
return
}
companion object {
const val EXTRA_ACCOUNT = "extra_account"
const val EXTRA_STORE_DATA = "extra_store_data"
}
}

@ -59,7 +59,8 @@ class PlaceSettingsActivity : BaseListSettingsActivity(), MapFragment.MapFragmen
updateTheme()
}
override fun getLayout() = R.layout.activity_location_settings
override val layout: Int
get() = R.layout.activity_location_settings
override fun hasChanges() = name.text.toString() != place.displayName
|| selectedColor != place.color
@ -89,11 +90,11 @@ class PlaceSettingsActivity : BaseListSettingsActivity(), MapFragment.MapFragmen
finish()
}
override fun isNew() = false
override val isNew: Boolean
get() = false
override fun getToolbarTitle(): String? {
return place.address ?: place.displayName
}
override val toolbarTitle: String?
get() = place.address ?: place.displayName
override fun delete() {
locationDao.deleteGeofencesByPlace(place.uid!!)

@ -1,12 +1,14 @@
package org.tasks.activities
import androidx.hilt.lifecycle.ViewModelInject
import com.google.api.services.tasks.model.TaskList
import com.todoroo.astrid.gtasks.api.GtasksInvoker
import org.tasks.data.GoogleTaskList
import org.tasks.ui.CompletableViewModel
class RenameListViewModel : CompletableViewModel<TaskList?>() {
fun renameList(invoker: GtasksInvoker, list: GoogleTaskList, name: String?) {
run { invoker.forAccount(list.account!!).renameGtaskList(list.remoteId, name) }
class RenameListViewModel @ViewModelInject constructor(
private val invoker: GtasksInvoker) : CompletableViewModel<TaskList>() {
fun renameList(list: GoogleTaskList, name: String) {
run { invoker.forAccount(list.account!!).renameGtaskList(list.remoteId, name)!! }
}
}

@ -1,171 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package org.tasks.activities;
import static org.tasks.Strings.isNullOrEmpty;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.inputmethod.InputMethodManager;
import butterknife.BindView;
import butterknife.OnTextChanged;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.todoroo.astrid.activity.MainActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.TagFilter;
import com.todoroo.astrid.helper.UUIDHelper;
import dagger.hilt.android.AndroidEntryPoint;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.data.TagDaoBlocking;
import org.tasks.data.TagData;
import org.tasks.data.TagDataDaoBlocking;
@AndroidEntryPoint
public class TagSettingsActivity extends BaseListSettingsActivity {
public static final String TOKEN_AUTOPOPULATE_NAME = "autopopulateName"; // $NON-NLS-1$
public static final String EXTRA_TAG_DATA = "tagData"; // $NON-NLS-1$
private static final String EXTRA_TAG_UUID = "uuid"; // $NON-NLS-1$
@Inject TagDataDaoBlocking tagDataDao;
@Inject TagDaoBlocking tagDao;
@BindView(R.id.name)
TextInputEditText name;
@BindView(R.id.name_layout)
TextInputLayout nameLayout;
private boolean isNewTag;
private TagData tagData;
@Override
protected void onCreate(Bundle savedInstanceState) {
tagData = getIntent().getParcelableExtra(EXTRA_TAG_DATA);
super.onCreate(savedInstanceState);
if (tagData == null) {
isNewTag = true;
tagData = new TagData();
tagData.setRemoteId(UUIDHelper.newUUID());
}
if (savedInstanceState == null) {
selectedColor = tagData.getColor();
selectedIcon = tagData.getIcon();
}
name.setText(tagData.getName());
String autopopulateName = getIntent().getStringExtra(TOKEN_AUTOPOPULATE_NAME);
if (!isNullOrEmpty(autopopulateName)) {
name.setText(autopopulateName);
getIntent().removeExtra(TOKEN_AUTOPOPULATE_NAME);
} else if (isNewTag) {
name.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT);
}
updateTheme();
}
@Override
protected boolean isNew() {
return tagData == null;
}
@Override
protected String getToolbarTitle() {
return isNew() ? getString(R.string.new_tag) : tagData.getName();
}
@OnTextChanged(R.id.name)
void onTextChanged() {
nameLayout.setError(null);
}
private String getNewName() {
return name.getText().toString().trim();
}
private boolean clashes(String newName) {
return (isNewTag || !newName.equalsIgnoreCase(tagData.getName()))
&& tagDataDao.getTagByName(newName) != null;
}
@Override
protected void save() {
String newName = getNewName();
if (isNullOrEmpty(newName)) {
nameLayout.setError(getString(R.string.name_cannot_be_empty));
return;
}
if (clashes(newName)) {
nameLayout.setError(getString(R.string.tag_already_exists));
return;
}
if (isNewTag) {
tagData.setName(newName);
tagData.setColor(selectedColor);
tagData.setIcon(selectedIcon);
tagDataDao.createNew(tagData);
setResult(RESULT_OK, new Intent().putExtra(MainActivity.OPEN_FILTER, new TagFilter(tagData)));
} else if (hasChanges()) {
tagData.setName(newName);
tagData.setColor(selectedColor);
tagData.setIcon(selectedIcon);
tagDataDao.update(tagData);
tagDao.rename(tagData.getRemoteId(), newName);
setResult(
RESULT_OK,
new Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, new TagFilter(tagData)));
}
finish();
}
@Override
protected boolean hasChanges() {
if (isNewTag) {
return selectedColor >= 0 || selectedIcon >= 0 || !isNullOrEmpty(getNewName());
}
return !(selectedColor == tagData.getColor()
&& selectedIcon == tagData.getIcon()
&& getNewName().equals(tagData.getName()));
}
@Override
public void finish() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(name.getWindowToken(), 0);
super.finish();
}
@Override
protected int getLayout() {
return R.layout.activity_tag_settings;
}
@Override
protected void delete() {
if (tagData != null) {
String uuid = tagData.getRemoteId();
tagDataDao.delete(tagData);
setResult(
RESULT_OK,
new Intent(TaskListFragment.ACTION_DELETED).putExtra(EXTRA_TAG_UUID, uuid));
}
finish();
}
}

@ -0,0 +1,149 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package org.tasks.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import butterknife.BindView
import butterknife.OnTextChanged
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.api.TagFilter
import com.todoroo.astrid.helper.UUIDHelper
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.TagDaoBlocking
import org.tasks.data.TagData
import org.tasks.data.TagDataDaoBlocking
import javax.inject.Inject
@AndroidEntryPoint
class TagSettingsActivity : BaseListSettingsActivity() {
@Inject lateinit var tagDataDao: TagDataDaoBlocking
@Inject lateinit var tagDao: TagDaoBlocking
@BindView(R.id.name)
lateinit var name: TextInputEditText
@BindView(R.id.name_layout)
lateinit var nameLayout: TextInputLayout
private var isNewTag = false
private lateinit var tagData: TagData
override fun onCreate(savedInstanceState: Bundle?) {
tagData = intent.getParcelableExtra(EXTRA_TAG_DATA)
?: TagData().apply {
isNewTag = true
remoteId = UUIDHelper.newUUID()
}
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
selectedColor = tagData.getColor()!!
selectedIcon = tagData.getIcon()!!
}
name.setText(tagData.name)
val autopopulateName = intent.getStringExtra(TOKEN_AUTOPOPULATE_NAME)
if (!isNullOrEmpty(autopopulateName)) {
name.setText(autopopulateName)
intent.removeExtra(TOKEN_AUTOPOPULATE_NAME)
} else if (isNewTag) {
name.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT)
}
updateTheme()
}
override val isNew: Boolean
get() = isNewTag
override val toolbarTitle: String
get() = if (isNew) getString(R.string.new_tag) else tagData.name!!
@OnTextChanged(R.id.name)
fun onTextChanged() {
nameLayout.error = null
}
private val newName: String
get() = name.text.toString().trim { it <= ' ' }
private fun clashes(newName: String): Boolean {
return ((isNewTag || !newName.equals(tagData.name, ignoreCase = true))
&& tagDataDao.getTagByName(newName) != null)
}
override fun save() {
val newName = newName
if (isNullOrEmpty(newName)) {
nameLayout.error = getString(R.string.name_cannot_be_empty)
return
}
if (clashes(newName)) {
nameLayout.error = getString(R.string.tag_already_exists)
return
}
if (isNewTag) {
tagData.name = newName
tagData.setColor(selectedColor)
tagData.setIcon(selectedIcon)
tagDataDao.createNew(tagData)
setResult(Activity.RESULT_OK, Intent().putExtra(MainActivity.OPEN_FILTER, TagFilter(tagData)))
} else if (hasChanges()) {
tagData.name = newName
tagData.setColor(selectedColor)
tagData.setIcon(selectedIcon)
tagDataDao.update(tagData)
tagDao.rename(tagData.remoteId!!, newName)
setResult(
Activity.RESULT_OK,
Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, TagFilter(tagData)))
}
finish()
}
override fun hasChanges(): Boolean {
return if (isNewTag) {
selectedColor >= 0 || selectedIcon >= 0 || !isNullOrEmpty(newName)
} else {
selectedColor != tagData.getColor()
|| selectedIcon != tagData.getIcon()
|| newName != tagData.name
}
}
override fun finish() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(name.windowToken, 0)
super.finish()
}
override val layout: Int
get() = R.layout.activity_tag_settings
override fun delete() {
val uuid = tagData.remoteId
tagDataDao.delete(tagData)
setResult(
Activity.RESULT_OK,
Intent(TaskListFragment.ACTION_DELETED).putExtra(EXTRA_TAG_UUID, uuid))
finish()
}
companion object {
const val TOKEN_AUTOPOPULATE_NAME = "autopopulateName" // $NON-NLS-1$
const val EXTRA_TAG_DATA = "tagData" // $NON-NLS-1$
private const val EXTRA_TAG_UUID = "uuid" // $NON-NLS-1$
}
}

@ -1,9 +1,11 @@
package org.tasks.caldav
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.ui.CompletableViewModel
class AddCaldavAccountViewModel : CompletableViewModel<String>() {
fun addAccount(client: CaldavClient, url: String?, username: String?, password: String?) {
class AddCaldavAccountViewModel @ViewModelInject constructor(
private val client: CaldavClient) : CompletableViewModel<String>() {
fun addAccount(url: String, username: String, password: String) {
run { client.setForeground().forUrl(url, username, password).homeSet }
}
}

@ -220,7 +220,7 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
failed -> return
caldavAccount == null -> {
showProgressIndicator()
addAccount(url, username, password)
addAccount(url, username, password!!)
}
needsValidation() -> {
showProgressIndicator()
@ -235,10 +235,11 @@ abstract class BaseCaldavAccountSettingsActivity : ThemedInjectingAppCompatActiv
}
}
protected abstract fun addAccount(url: String?, username: String?, password: String?)
protected abstract fun updateAccount(url: String?, username: String?, password: String?)
protected abstract fun addAccount(url: String, username: String, password: String)
protected abstract fun updateAccount(url: String, username: String, password: String?)
protected abstract fun updateAccount()
protected abstract val helpUrl: String?
protected fun requestFailed(t: Throwable) {
hideProgressIndicator()
if (t is HttpException) {

@ -1,272 +0,0 @@
package org.tasks.caldav;
import static org.tasks.Strings.isNullOrEmpty;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import at.bitfire.dav4jvm.exception.HttpException;
import butterknife.BindView;
import butterknife.OnTextChanged;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.todoroo.astrid.activity.MainActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.helper.UUIDHelper;
import com.todoroo.astrid.service.TaskDeleter;
import java.net.ConnectException;
import javax.inject.Inject;
import kotlin.Unit;
import org.tasks.R;
import org.tasks.activities.BaseListSettingsActivity;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDaoBlocking;
import org.tasks.ui.DisplayableException;
public abstract class BaseCaldavCalendarSettingsActivity extends BaseListSettingsActivity {
public static final String EXTRA_CALDAV_CALENDAR = "extra_caldav_calendar";
public static final String EXTRA_CALDAV_ACCOUNT = "extra_caldav_account";
@Inject protected CaldavDaoBlocking caldavDao;
@Inject TaskDeleter taskDeleter;
@BindView(R.id.root_layout)
LinearLayout root;
@BindView(R.id.name)
TextInputEditText name;
@BindView(R.id.name_layout)
TextInputLayout nameLayout;
@BindView(R.id.progress_bar)
ProgressBar progressView;
private CaldavCalendar caldavCalendar;
private CaldavAccount caldavAccount;
@Override
protected int getLayout() {
return R.layout.activity_caldav_calendar_settings;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
caldavCalendar = intent.getParcelableExtra(EXTRA_CALDAV_CALENDAR);
super.onCreate(savedInstanceState);
if (caldavCalendar == null) {
caldavAccount = intent.getParcelableExtra(EXTRA_CALDAV_ACCOUNT);
} else {
caldavAccount = caldavDao.getAccountByUuid(caldavCalendar.getAccount());
}
caldavAccount =
caldavCalendar == null
? intent.getParcelableExtra(EXTRA_CALDAV_ACCOUNT)
: caldavDao.getAccountByUuid(caldavCalendar.getAccount());
if (savedInstanceState == null) {
if (caldavCalendar != null) {
name.setText(caldavCalendar.getName());
selectedColor = caldavCalendar.getColor();
selectedIcon = caldavCalendar.getIcon();
}
}
if (caldavCalendar == null) {
name.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT);
}
updateTheme();
}
@Override
protected boolean isNew() {
return caldavCalendar == null;
}
@Override
protected String getToolbarTitle() {
return isNew() ? getString(R.string.new_list) : caldavCalendar.getName();
}
@OnTextChanged(R.id.name)
void onNameChanged() {
nameLayout.setError(null);
}
@Override
protected void save() {
if (requestInProgress()) {
return;
}
String name = getNewName();
if (isNullOrEmpty(name)) {
nameLayout.setError(getString(R.string.name_cannot_be_empty));
return;
}
if (caldavCalendar == null) {
showProgressIndicator();
createCalendar(caldavAccount, name, selectedColor);
} else if (nameChanged() || colorChanged()) {
showProgressIndicator();
updateNameAndColor(caldavAccount, caldavCalendar, name, selectedColor);
} else if (iconChanged()) {
updateCalendar();
} else {
finish();
}
}
protected abstract void createCalendar(CaldavAccount caldavAccount, String name, int color);
protected abstract void updateNameAndColor(
CaldavAccount account, CaldavCalendar calendar, String name, int color);
protected abstract void deleteCalendar(
CaldavAccount caldavAccount, CaldavCalendar caldavCalendar);
private void showProgressIndicator() {
progressView.setVisibility(View.VISIBLE);
}
private void hideProgressIndicator() {
progressView.setVisibility(View.GONE);
}
private boolean requestInProgress() {
return progressView.getVisibility() == View.VISIBLE;
}
protected Unit requestFailed(Throwable t) {
hideProgressIndicator();
if (t instanceof HttpException) {
showSnackbar(t.getMessage());
} else if (t instanceof DisplayableException) {
showSnackbar(((DisplayableException) t).getResId());
} else if (t instanceof ConnectException) {
showSnackbar(R.string.network_error);
} else {
showSnackbar(R.string.error_adding_account, t.getMessage());
}
return Unit.INSTANCE;
}
private void showSnackbar(int resId, Object... formatArgs) {
showSnackbar(getString(resId, formatArgs));
}
private void showSnackbar(String message) {
Snackbar snackbar =
Snackbar.make(root, message, 8000)
.setTextColor(getColor(R.color.snackbar_text_color))
.setActionTextColor(getColor(R.color.snackbar_action_color));
snackbar
.getView()
.setBackgroundColor(getColor(R.color.snackbar_background));
snackbar.show();
}
protected Unit createSuccessful(String url) {
CaldavCalendar caldavCalendar = new CaldavCalendar();
caldavCalendar.setUuid(UUIDHelper.newUUID());
caldavCalendar.setAccount(caldavAccount.getUuid());
caldavCalendar.setUrl(url);
caldavCalendar.setName(getNewName());
caldavCalendar.setColor(selectedColor);
caldavCalendar.setIcon(selectedIcon);
caldavDao.insert(caldavCalendar);
setResult(
RESULT_OK,
new Intent().putExtra(MainActivity.OPEN_FILTER, new CaldavFilter(caldavCalendar)));
finish();
return Unit.INSTANCE;
}
protected Unit updateCalendar() {
caldavCalendar.setName(getNewName());
caldavCalendar.setColor(selectedColor);
caldavCalendar.setIcon(selectedIcon);
caldavDao.update(caldavCalendar);
setResult(
RESULT_OK,
new Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, new CaldavFilter(caldavCalendar)));
finish();
return Unit.INSTANCE;
}
@Override
protected boolean hasChanges() {
return caldavCalendar == null
? !isNullOrEmpty(getNewName()) || selectedColor != 0 || selectedIcon != -1
: nameChanged() || iconChanged() || colorChanged();
}
private boolean nameChanged() {
return !caldavCalendar.getName().equals(getNewName());
}
private boolean colorChanged() {
return selectedColor != caldavCalendar.getColor();
}
private boolean iconChanged() {
return selectedIcon != caldavCalendar.getIcon();
}
private String getNewName() {
return name.getText().toString().trim();
}
@Override
public void finish() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(name.getWindowToken(), 0);
super.finish();
}
@Override
protected void discard() {
if (!requestInProgress()) {
super.discard();
}
}
@Override
protected void promptDelete() {
if (!requestInProgress()) {
super.promptDelete();
}
}
@Override
protected void delete() {
showProgressIndicator();
deleteCalendar(caldavAccount, caldavCalendar);
}
protected void onDeleted(boolean deleted) {
if (deleted) {
taskDeleter.delete(caldavCalendar);
setResult(RESULT_OK, new Intent(TaskListFragment.ACTION_DELETED));
finish();
}
}
}

@ -0,0 +1,239 @@
package org.tasks.caldav
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
import android.widget.ProgressBar
import at.bitfire.dav4jvm.exception.HttpException
import butterknife.BindView
import butterknife.OnTextChanged
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.helper.UUIDHelper
import com.todoroo.astrid.service.TaskDeleter
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.activities.BaseListSettingsActivity
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDaoBlocking
import org.tasks.ui.DisplayableException
import java.net.ConnectException
import javax.inject.Inject
abstract class BaseCaldavCalendarSettingsActivity : BaseListSettingsActivity() {
@Inject lateinit var caldavDao: CaldavDaoBlocking
@Inject lateinit var taskDeleter: TaskDeleter
@BindView(R.id.root_layout)
lateinit var root: LinearLayout
@BindView(R.id.name)
lateinit var name: TextInputEditText
@BindView(R.id.name_layout)
lateinit var nameLayout: TextInputLayout
@BindView(R.id.progress_bar)
lateinit var progressView: ProgressBar
private var caldavCalendar: CaldavCalendar? = null
private lateinit var caldavAccount: CaldavAccount
override val layout: Int
get() = R.layout.activity_caldav_calendar_settings
override fun onCreate(savedInstanceState: Bundle?) {
val intent = intent
caldavCalendar = intent.getParcelableExtra(EXTRA_CALDAV_CALENDAR)
super.onCreate(savedInstanceState)
caldavAccount = if (caldavCalendar == null) {
intent.getParcelableExtra(EXTRA_CALDAV_ACCOUNT)!!
} else {
caldavDao.getAccountByUuid(caldavCalendar!!.account!!)!!
}
if (savedInstanceState == null) {
if (caldavCalendar != null) {
name.setText(caldavCalendar!!.name)
selectedColor = caldavCalendar!!.color
selectedIcon = caldavCalendar!!.getIcon()!!
}
}
if (caldavCalendar == null) {
name.requestFocus()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT)
}
updateTheme()
}
override val isNew: Boolean
get() = caldavCalendar == null
override val toolbarTitle: String
get() = if (isNew) getString(R.string.new_list) else caldavCalendar!!.name!!
@OnTextChanged(R.id.name)
fun onNameChanged() {
nameLayout.error = null
}
override fun save() {
if (requestInProgress()) {
return
}
val name = newName
if (isNullOrEmpty(name)) {
nameLayout.error = getString(R.string.name_cannot_be_empty)
return
}
if (caldavCalendar == null) {
showProgressIndicator()
createCalendar(caldavAccount, name, selectedColor)
} else if (nameChanged() || colorChanged()) {
showProgressIndicator()
updateNameAndColor(caldavAccount, caldavCalendar!!, name, selectedColor)
} else if (iconChanged()) {
updateCalendar()
} else {
finish()
}
}
protected abstract fun createCalendar(caldavAccount: CaldavAccount, name: String, color: Int)
protected abstract fun updateNameAndColor(
account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int)
protected abstract fun deleteCalendar(
caldavAccount: CaldavAccount, caldavCalendar: CaldavCalendar)
private fun showProgressIndicator() {
progressView.visibility = View.VISIBLE
}
private fun hideProgressIndicator() {
progressView.visibility = View.GONE
}
private fun requestInProgress(): Boolean {
return progressView.visibility == View.VISIBLE
}
protected fun requestFailed(t: Throwable) {
hideProgressIndicator()
when (t) {
is HttpException -> showSnackbar(t.message)
is DisplayableException -> showSnackbar(t.resId)
is ConnectException -> showSnackbar(R.string.network_error)
else -> showSnackbar(R.string.error_adding_account, t.message!!)
}
return
}
private fun showSnackbar(resId: Int, vararg formatArgs: Any) {
showSnackbar(getString(resId, *formatArgs))
}
private fun showSnackbar(message: String?) {
val snackbar = Snackbar.make(root, message!!, 8000)
.setTextColor(getColor(R.color.snackbar_text_color))
.setActionTextColor(getColor(R.color.snackbar_action_color))
snackbar
.view
.setBackgroundColor(getColor(R.color.snackbar_background))
snackbar.show()
}
protected fun createSuccessful(url: String?) {
val caldavCalendar = CaldavCalendar()
caldavCalendar.uuid = UUIDHelper.newUUID()
caldavCalendar.account = caldavAccount.uuid
caldavCalendar.url = url
caldavCalendar.name = newName
caldavCalendar.color = selectedColor
caldavCalendar.setIcon(selectedIcon)
caldavDao.insert(caldavCalendar)
setResult(
Activity.RESULT_OK,
Intent().putExtra(MainActivity.OPEN_FILTER, CaldavFilter(caldavCalendar)))
finish()
return
}
protected fun updateCalendar() {
caldavCalendar!!.name = newName
caldavCalendar!!.color = selectedColor
caldavCalendar!!.setIcon(selectedIcon)
caldavDao.update(caldavCalendar!!)
setResult(
Activity.RESULT_OK,
Intent(TaskListFragment.ACTION_RELOAD)
.putExtra(MainActivity.OPEN_FILTER, CaldavFilter(caldavCalendar)))
finish()
return
}
override fun hasChanges(): Boolean {
return if (caldavCalendar == null) !isNullOrEmpty(newName) || selectedColor != 0 || selectedIcon != -1 else nameChanged() || iconChanged() || colorChanged()
}
private fun nameChanged(): Boolean {
return caldavCalendar!!.name != newName
}
private fun colorChanged(): Boolean {
return selectedColor != caldavCalendar!!.color
}
private fun iconChanged(): Boolean {
return selectedIcon != caldavCalendar!!.getIcon()
}
private val newName: String
get() = name.text.toString().trim { it <= ' ' }
override fun finish() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(name.windowToken, 0)
super.finish()
}
override fun discard() {
if (!requestInProgress()) {
super.discard()
}
}
override fun promptDelete() {
if (!requestInProgress()) {
super.promptDelete()
}
}
override fun delete() {
showProgressIndicator()
deleteCalendar(caldavAccount, caldavCalendar!!)
}
protected fun onDeleted(deleted: Boolean) {
if (deleted) {
taskDeleter.delete(caldavCalendar!!)
setResult(Activity.RESULT_OK, Intent(TaskListFragment.ACTION_DELETED))
finish()
}
}
companion object {
const val EXTRA_CALDAV_CALENDAR = "extra_caldav_calendar"
const val EXTRA_CALDAV_ACCOUNT = "extra_caldav_account"
}
}

@ -9,19 +9,16 @@ import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.data.CaldavAccount
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class CaldavAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var client: CaldavClient
private val addCaldavAccountViewModel: AddCaldavAccountViewModel by viewModels()
private val updateCaldavAccountViewModel: UpdateCaldavAccountViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addCaldavAccountViewModel.observe(this, { addAccount(it) }, { requestFailed(it) })
updateCaldavAccountViewModel.observe(this, { updateAccount(it) }, { requestFailed(it) })
addCaldavAccountViewModel.observe(this, this::addAccount, this::requestFailed)
updateCaldavAccountViewModel.observe(this, this::updateAccount, this::requestFailed)
}
override val description: Int
@ -56,12 +53,12 @@ class CaldavAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolb
finish()
}
override fun addAccount(url: String?, username: String?, password: String?) {
addCaldavAccountViewModel.addAccount(client, url, username, password)
override fun addAccount(url: String, username: String, password: String) {
addCaldavAccountViewModel.addAccount(url, username, password)
}
override fun updateAccount(url: String?, username: String?, password: String?) {
updateCaldavAccountViewModel.updateCaldavAccount(client, url, username, password)
override fun updateAccount(url: String, username: String, password: String?) {
updateCaldavAccountViewModel.updateCaldavAccount(url, username, password)
}
override fun updateAccount() {

@ -1,54 +0,0 @@
package org.tasks.caldav;
import android.os.Bundle;
import androidx.lifecycle.ViewModelProvider;
import dagger.hilt.android.AndroidEntryPoint;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
@AndroidEntryPoint
public class CaldavCalendarSettingsActivity extends BaseCaldavCalendarSettingsActivity {
@Inject CaldavClient client;
private CreateCalendarViewModel createCalendarViewModel;
private DeleteCalendarViewModel deleteCalendarViewModel;
private UpdateCalendarViewModel updateCalendarViewModel;
@Override
protected int getLayout() {
return R.layout.activity_caldav_calendar_settings;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewModelProvider provider = new ViewModelProvider(this);
createCalendarViewModel = provider.get(CreateCalendarViewModel.class);
deleteCalendarViewModel = provider.get(DeleteCalendarViewModel.class);
updateCalendarViewModel = provider.get(UpdateCalendarViewModel.class);
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed);
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed);
updateCalendarViewModel.observe(this, ignored -> updateCalendar(), this::requestFailed);
}
@Override
protected void createCalendar(CaldavAccount caldavAccount, String name, int color) {
createCalendarViewModel.createCalendar(client, caldavAccount, name, color);
}
@Override
protected void updateNameAndColor(
CaldavAccount account, CaldavCalendar calendar, String name, int color) {
updateCalendarViewModel.updateCalendar(client, account, calendar, name, color);
}
@Override
protected void deleteCalendar(CaldavAccount caldavAccount, CaldavCalendar caldavCalendar) {
deleteCalendarViewModel.deleteCalendar(client, caldavAccount, caldavCalendar);
}
}

@ -0,0 +1,35 @@
package org.tasks.caldav
import android.os.Bundle
import androidx.activity.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
@AndroidEntryPoint
class CaldavCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
private val createCalendarViewModel: CreateCalendarViewModel by viewModels()
private val deleteCalendarViewModel: DeleteCalendarViewModel by viewModels()
private val updateCalendarViewModel: UpdateCalendarViewModel by viewModels()
override val layout: Int
get() = R.layout.activity_caldav_calendar_settings
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed)
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed)
updateCalendarViewModel.observe(this, { updateCalendar() }, this::requestFailed)
}
override fun createCalendar(caldavAccount: CaldavAccount, name: String, color: Int) =
createCalendarViewModel.createCalendar(caldavAccount, name, color)
override fun updateNameAndColor(
account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) =
updateCalendarViewModel.updateCalendar(account, calendar, name, color)
override fun deleteCalendar(caldavAccount: CaldavAccount, caldavCalendar: CaldavCalendar) =
deleteCalendarViewModel.deleteCalendar(caldavAccount, caldavCalendar)
}

@ -1,10 +1,12 @@
package org.tasks.caldav
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.ui.CompletableViewModel
class CreateCalendarViewModel : CompletableViewModel<String?>() {
fun createCalendar(client: CaldavClient, account: CaldavAccount?, name: String?, color: Int) {
class CreateCalendarViewModel @ViewModelInject constructor(
private val client: CaldavClient): CompletableViewModel<String?>() {
fun createCalendar(account: CaldavAccount, name: String, color: Int) {
run { client.forAccount(account).makeCollection(name, color) }
}
}

@ -1,12 +0,0 @@
package org.tasks.caldav;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.ui.ActionViewModel;
@SuppressWarnings("WeakerAccess")
public class DeleteCalendarViewModel extends ActionViewModel {
void deleteCalendar(CaldavClient client, CaldavAccount account, CaldavCalendar calendar) {
run(() -> client.forCalendar(account, calendar).deleteCollection());
}
}

@ -0,0 +1,13 @@
package org.tasks.caldav
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.ui.ActionViewModel
class DeleteCalendarViewModel @ViewModelInject constructor(
private val client: CaldavClient) : ActionViewModel() {
fun deleteCalendar(account: CaldavAccount, calendar: CaldavCalendar) {
run { client.forCalendar(account, calendar).deleteCollection() }
}
}

@ -10,12 +10,14 @@ import org.tasks.data.CaldavDao
@AndroidEntryPoint
class LocalListSettingsActivity : BaseCaldavCalendarSettingsActivity() {
override fun getLayout() = R.layout.activity_caldav_calendar_settings
override val layout: Int
get() = R.layout.activity_caldav_calendar_settings
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbar.menu.findItem(R.id.delete)?.isVisible = caldavDao.getCalendarsByAccount(CaldavDao.LOCAL).size > 1
toolbar.menu.findItem(R.id.delete)?.isVisible =
caldavDao.getCalendarsByAccount(CaldavDao.LOCAL).size > 1
}
override fun createCalendar(caldavAccount: CaldavAccount, name: String, color: Int) =

@ -1,12 +1,11 @@
package org.tasks.caldav
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.ui.CompletableViewModel
class UpdateCaldavAccountViewModel : CompletableViewModel<String>() {
fun updateCaldavAccount(
client: CaldavClient, url: String?, username: String?, password: String?) {
run {
client.forUrl(url, username, password).homeSet
}
class UpdateCaldavAccountViewModel @ViewModelInject constructor(
private val client: CaldavClient) : CompletableViewModel<String>() {
fun updateCaldavAccount(url: String, username: String, password: String?) {
run { client.forUrl(url, username, password).homeSet }
}
}

@ -1,12 +1,13 @@
package org.tasks.caldav
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.ui.CompletableViewModel
class UpdateCalendarViewModel : CompletableViewModel<String?>() {
fun updateCalendar(
client: CaldavClient, account: CaldavAccount?, calendar: CaldavCalendar?, name: String?, color: Int) {
class UpdateCalendarViewModel @ViewModelInject constructor(
private val client: CaldavClient) : CompletableViewModel<String?>() {
fun updateCalendar(account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) {
run { client.forCalendar(account, calendar).updateCollection(name, color) }
}
}

@ -1,11 +1,14 @@
package org.tasks.etesync
import androidx.core.util.Pair
import androidx.hilt.lifecycle.ViewModelInject
import com.etesync.journalmanager.UserInfoManager
import com.etesync.journalmanager.UserInfoManager.UserInfo
import org.tasks.ui.CompletableViewModel
class AddEteSyncAccountViewModel : CompletableViewModel<Pair<UserInfoManager.UserInfo, String>>() {
fun addAccount(client: EteSyncClient, url: String?, username: String?, password: String?) {
class AddEteSyncAccountViewModel @ViewModelInject constructor(
private val client: EteSyncClient): CompletableViewModel<Pair<UserInfo, String>>() {
fun addAccount(url: String, username: String, password: String) {
run {
client.setForeground()
val token = client.forUrl(url, username, null, null).getToken(password)

@ -1,10 +1,12 @@
package org.tasks.etesync
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.ui.CompletableViewModel
class CreateCalendarViewModel : CompletableViewModel<String?>() {
fun createCalendar(client: EteSyncClient, account: CaldavAccount?, name: String?, color: Int) {
run { client.forAccount(account!!).makeCollection(name, color) }
class CreateCalendarViewModel @ViewModelInject constructor(
private val client: EteSyncClient) : CompletableViewModel<String?>() {
fun createCalendar(account: CaldavAccount, name: String, color: Int) {
run { client.forAccount(account).makeCollection(name, color) }
}
}

@ -1,12 +1,14 @@
package org.tasks.etesync
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.ui.CompletableViewModel
class CreateUserInfoViewModel : CompletableViewModel<String?>() {
fun createUserInfo(client: EteSyncClient, caldavAccount: CaldavAccount?, derivedKey: String?) {
class CreateUserInfoViewModel @ViewModelInject constructor(
private val client: EteSyncClient): CompletableViewModel<String>() {
fun createUserInfo(caldavAccount: CaldavAccount, derivedKey: String) {
run {
client.forAccount(caldavAccount!!).createUserInfo(derivedKey)
client.forAccount(caldavAccount).createUserInfo(derivedKey)
derivedKey
}
}

@ -1,12 +0,0 @@
package org.tasks.etesync;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.ui.ActionViewModel;
@SuppressWarnings("WeakerAccess")
public class DeleteCalendarViewModel extends ActionViewModel {
void deleteCalendar(EteSyncClient client, CaldavAccount account, CaldavCalendar calendar) {
run(() -> client.forAccount(account).deleteCollection(calendar));
}
}

@ -0,0 +1,13 @@
package org.tasks.etesync
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.ui.ActionViewModel
class DeleteCalendarViewModel @ViewModelInject constructor(
private val client: EteSyncClient) : ActionViewModel() {
fun deleteCalendar(account: CaldavAccount, calendar: CaldavCalendar) {
run { client.forAccount(account).deleteCollection(calendar) }
}
}

@ -1,221 +0,0 @@
package org.tasks.etesync;
import static org.tasks.Strings.isNullOrEmpty;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
import androidx.lifecycle.ViewModelProvider;
import at.bitfire.dav4jvm.exception.HttpException;
import butterknife.ButterKnife;
import butterknife.OnTextChanged;
import com.etesync.journalmanager.Constants;
import com.etesync.journalmanager.Crypto;
import com.etesync.journalmanager.Crypto.CryptoManager;
import com.etesync.journalmanager.Exceptions.IntegrityException;
import com.etesync.journalmanager.Exceptions.VersionTooNewException;
import com.etesync.journalmanager.UserInfoManager.UserInfo;
import com.google.android.material.snackbar.Snackbar;
import dagger.hilt.android.AndroidEntryPoint;
import java.net.ConnectException;
import javax.inject.Inject;
import kotlin.Unit;
import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.databinding.ActivityEtesyncEncryptionSettingsBinding;
import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.security.KeyStoreEncryption;
import org.tasks.ui.DisplayableException;
import timber.log.Timber;
@AndroidEntryPoint
public class EncryptionSettingsActivity extends ThemedInjectingAppCompatActivity
implements OnMenuItemClickListener {
public static final String EXTRA_USER_INFO = "extra_user_info";
public static final String EXTRA_ACCOUNT = "extra_account";
public static final String EXTRA_DERIVED_KEY = "extra_derived_key";
@Inject EteSyncClient client;
@Inject KeyStoreEncryption encryption;
private ActivityEtesyncEncryptionSettingsBinding binding;
private UserInfo userInfo;
private CaldavAccount caldavAccount;
private CreateUserInfoViewModel createUserInfoViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
createUserInfoViewModel = new ViewModelProvider(this).get(CreateUserInfoViewModel.class);
binding = ActivityEtesyncEncryptionSettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
ButterKnife.bind(this);
Intent intent = getIntent();
caldavAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
userInfo = (UserInfo) intent.getSerializableExtra(EXTRA_USER_INFO);
if (userInfo == null) {
binding.description.setVisibility(View.VISIBLE);
binding.repeatEncryptionPasswordLayout.setVisibility(View.VISIBLE);
}
Toolbar toolbar = binding.toolbar.toolbar;
toolbar.setTitle(
caldavAccount == null ? getString(R.string.add_account) : caldavAccount.getName());
toolbar.setNavigationIcon(getDrawable(R.drawable.ic_outline_save_24px));
toolbar.setNavigationOnClickListener(v -> save());
toolbar.inflateMenu(R.menu.menu_help);
toolbar.setOnMenuItemClickListener(this);
themeColor.apply(toolbar);
createUserInfoViewModel.observe(this, this::returnDerivedKey, this::requestFailed);
if (createUserInfoViewModel.inProgress()) {
showProgressIndicator();
}
}
private void showProgressIndicator() {
binding.progressBar.progressBar.setVisibility(View.VISIBLE);
}
private void hideProgressIndicator() {
binding.progressBar.progressBar.setVisibility(View.GONE);
}
private boolean requestInProgress() {
return binding.progressBar.progressBar.getVisibility() == View.VISIBLE;
}
private Unit returnDerivedKey(String derivedKey) {
hideProgressIndicator();
Intent result = new Intent();
result.putExtra(EXTRA_DERIVED_KEY, derivedKey);
setResult(RESULT_OK, result);
finish();
return Unit.INSTANCE;
}
private void save() {
if (requestInProgress()) {
return;
}
String encryptionPassword = getNewEncryptionPassword();
String derivedKey = caldavAccount.getEncryptionPassword(encryption);
if (isNullOrEmpty(encryptionPassword) && isNullOrEmpty(derivedKey)) {
binding.encryptionPasswordLayout.setError(getString(R.string.encryption_password_required));
return;
}
if (userInfo == null) {
String repeatEncryptionPassword = binding.repeatEncryptionPassword.getText().toString().trim();
if (!encryptionPassword.equals(repeatEncryptionPassword)) {
binding.repeatEncryptionPasswordLayout.setError(getString(R.string.passwords_do_not_match));
return;
}
}
String key =
isNullOrEmpty(encryptionPassword)
? derivedKey
: Crypto.deriveKey(caldavAccount.getUsername(), encryptionPassword);
CryptoManager cryptoManager;
try {
int version = userInfo == null ? Constants.CURRENT_VERSION : userInfo.getVersion();
cryptoManager = new CryptoManager(version, key, "userInfo");
} catch (VersionTooNewException | IntegrityException e) {
requestFailed(e);
return;
}
if (userInfo == null) {
showProgressIndicator();
createUserInfoViewModel.createUserInfo(client, caldavAccount, key);
} else {
try {
userInfo.verify(cryptoManager);
returnDerivedKey(key);
} catch (IntegrityException e) {
binding.encryptionPasswordLayout.setError(getString(R.string.encryption_password_wrong));
}
}
}
protected Unit requestFailed(Throwable t) {
hideProgressIndicator();
if (t instanceof HttpException) {
showSnackbar(t.getMessage());
} else if (t instanceof DisplayableException) {
showSnackbar(((DisplayableException) t).getResId());
} else if (t instanceof ConnectException) {
showSnackbar(R.string.network_error);
} else {
Timber.e(t);
showSnackbar(R.string.error_adding_account, t.getMessage());
}
return Unit.INSTANCE;
}
private void showSnackbar(int resId, Object... formatArgs) {
showSnackbar(getString(resId, formatArgs));
}
private void showSnackbar(String message) {
newSnackbar(message).show();
}
private Snackbar newSnackbar(String message) {
Snackbar snackbar =
Snackbar.make(binding.rootLayout, message, 8000)
.setTextColor(getColor(R.color.snackbar_text_color))
.setActionTextColor(getColor(R.color.snackbar_action_color));
snackbar
.getView()
.setBackgroundColor(getColor(R.color.snackbar_background));
return snackbar;
}
@OnTextChanged(R.id.repeat_encryption_password)
void onRpeatEncryptionPasswordChanged() {
binding.repeatEncryptionPasswordLayout.setError(null);
}
@OnTextChanged(R.id.encryption_password)
void onEncryptionPasswordChanged() {
binding.encryptionPasswordLayout.setError(null);
}
private String getNewEncryptionPassword() {
return binding.encryptionPassword.getText().toString().trim();
}
@Override
public void finish() {
if (!requestInProgress()) {
super.finish();
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.menu_help) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://tasks.org/etesync")));
return true;
} else {
return onOptionsItemSelected(item);
}
}
}

@ -0,0 +1,186 @@
package org.tasks.etesync
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import at.bitfire.dav4jvm.exception.HttpException
import butterknife.ButterKnife
import butterknife.OnTextChanged
import com.etesync.journalmanager.Constants.Companion.CURRENT_VERSION
import com.etesync.journalmanager.Crypto.CryptoManager
import com.etesync.journalmanager.Crypto.deriveKey
import com.etesync.journalmanager.Exceptions.IntegrityException
import com.etesync.journalmanager.Exceptions.VersionTooNewException
import com.etesync.journalmanager.UserInfoManager
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.CaldavAccount
import org.tasks.databinding.ActivityEtesyncEncryptionSettingsBinding
import org.tasks.injection.ThemedInjectingAppCompatActivity
import org.tasks.security.KeyStoreEncryption
import org.tasks.ui.DisplayableException
import java.net.ConnectException
import javax.inject.Inject
@AndroidEntryPoint
class EncryptionSettingsActivity : ThemedInjectingAppCompatActivity(), Toolbar.OnMenuItemClickListener {
@Inject lateinit var encryption: KeyStoreEncryption
private lateinit var binding: ActivityEtesyncEncryptionSettingsBinding
private var userInfo: UserInfoManager.UserInfo? = null
private var caldavAccount: CaldavAccount? = null
private val createUserInfoViewModel: CreateUserInfoViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEtesyncEncryptionSettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
ButterKnife.bind(this)
val intent = intent
caldavAccount = intent.getParcelableExtra(EXTRA_ACCOUNT)
userInfo = intent.getSerializableExtra(EXTRA_USER_INFO) as UserInfoManager.UserInfo
if (userInfo == null) {
binding.description.visibility = View.VISIBLE
binding.repeatEncryptionPasswordLayout.visibility = View.VISIBLE
}
val toolbar = binding.toolbar.toolbar
toolbar.title = if (caldavAccount == null) getString(R.string.add_account) else caldavAccount!!.name
toolbar.navigationIcon = getDrawable(R.drawable.ic_outline_save_24px)
toolbar.setNavigationOnClickListener { save() }
toolbar.inflateMenu(R.menu.menu_help)
toolbar.setOnMenuItemClickListener(this)
themeColor.apply(toolbar)
createUserInfoViewModel.observe(this, this::returnDerivedKey, this::requestFailed)
if (createUserInfoViewModel.inProgress) {
showProgressIndicator()
}
}
private fun showProgressIndicator() {
binding.progressBar.progressBar.visibility = View.VISIBLE
}
private fun hideProgressIndicator() {
binding.progressBar.progressBar.visibility = View.GONE
}
private fun requestInProgress() = binding.progressBar.progressBar.visibility == View.VISIBLE
private fun returnDerivedKey(derivedKey: String) {
hideProgressIndicator()
val result = Intent()
result.putExtra(EXTRA_DERIVED_KEY, derivedKey)
setResult(Activity.RESULT_OK, result)
finish()
return
}
private fun save() {
if (requestInProgress()) {
return
}
val encryptionPassword = newEncryptionPassword
val derivedKey = caldavAccount!!.getEncryptionPassword(encryption)
if (isNullOrEmpty(encryptionPassword) && isNullOrEmpty(derivedKey)) {
binding.encryptionPasswordLayout.error = getString(R.string.encryption_password_required)
return
}
if (userInfo == null) {
val repeatEncryptionPassword = binding.repeatEncryptionPassword.text.toString().trim { it <= ' ' }
if (encryptionPassword != repeatEncryptionPassword) {
binding.repeatEncryptionPasswordLayout.error = getString(R.string.passwords_do_not_match)
return
}
}
val key = if (isNullOrEmpty(encryptionPassword)) derivedKey else deriveKey(caldavAccount!!.username!!, encryptionPassword)
val cryptoManager: CryptoManager
cryptoManager = try {
val version = if (userInfo == null) CURRENT_VERSION else userInfo!!.version!!.toInt()
CryptoManager(version, key, "userInfo")
} catch (e: VersionTooNewException) {
requestFailed(e)
return
} catch (e: IntegrityException) {
requestFailed(e)
return
}
if (userInfo == null) {
showProgressIndicator()
createUserInfoViewModel.createUserInfo(caldavAccount!!, key)
} else {
try {
userInfo!!.verify(cryptoManager)
returnDerivedKey(key)
} catch (e: IntegrityException) {
binding.encryptionPasswordLayout.error = getString(R.string.encryption_password_wrong)
}
}
}
private fun requestFailed(t: Throwable) {
hideProgressIndicator()
when (t) {
is HttpException -> showSnackbar(t.message)
is DisplayableException -> showSnackbar(t.resId)
is ConnectException -> showSnackbar(R.string.network_error)
else -> showSnackbar(R.string.error_adding_account, t.message!!)
}
}
private fun showSnackbar(resId: Int, vararg formatArgs: Any) =
showSnackbar(getString(resId, *formatArgs))
private fun showSnackbar(message: String?) =
newSnackbar(message).show()
private fun newSnackbar(message: String?): Snackbar {
val snackbar = Snackbar.make(binding.rootLayout, message!!, 8000)
.setTextColor(getColor(R.color.snackbar_text_color))
.setActionTextColor(getColor(R.color.snackbar_action_color))
snackbar
.view
.setBackgroundColor(getColor(R.color.snackbar_background))
return snackbar
}
@OnTextChanged(R.id.repeat_encryption_password)
fun onRpeatEncryptionPasswordChanged() {
binding.repeatEncryptionPasswordLayout.error = null
}
@OnTextChanged(R.id.encryption_password)
fun onEncryptionPasswordChanged() {
binding.encryptionPasswordLayout.error = null
}
private val newEncryptionPassword: String
get() = binding.encryptionPassword.text.toString().trim { it <= ' ' }
override fun finish() {
if (!requestInProgress()) {
super.finish()
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return if (item.itemId == R.id.menu_help) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://tasks.org/etesync")))
true
} else {
onOptionsItemSelected(item)
}
}
companion object {
const val EXTRA_USER_INFO = "extra_user_info"
const val EXTRA_ACCOUNT = "extra_account"
const val EXTRA_DERIVED_KEY = "extra_derived_key"
}
}

@ -41,8 +41,8 @@ class EteSyncAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Tool
override fun onResume() {
super.onResume()
if (!isFinishing) {
addAccountViewModel.observe(this, { addAccount(it) }, { requestFailed(it) })
updateAccountViewModel.observe(this, { updateAccount(it) }, { requestFailed(it) })
addAccountViewModel.observe(this, this::addAccount, this::requestFailed)
updateAccountViewModel.observe(this, this::updateAccount, this::requestFailed)
}
}
@ -116,13 +116,12 @@ class EteSyncAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Tool
return super.needsValidation() || isNullOrEmpty(caldavAccount!!.encryptionKey)
}
override fun addAccount(url: String?, username: String?, password: String?) {
addAccountViewModel.addAccount(eteSyncClient, url, username, password)
override fun addAccount(url: String, username: String, password: String) {
addAccountViewModel.addAccount(url, username, password)
}
override fun updateAccount(url: String?, username: String?, password: String?) {
override fun updateAccount(url: String, username: String, password: String?) {
updateAccountViewModel.updateAccount(
eteSyncClient,
url,
username,
if (PASSWORD_MASK == password) null else password,

@ -1,48 +0,0 @@
package org.tasks.etesync;
import android.os.Bundle;
import androidx.lifecycle.ViewModelProvider;
import dagger.hilt.android.AndroidEntryPoint;
import javax.inject.Inject;
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
@AndroidEntryPoint
public class EteSyncCalendarSettingsActivity extends BaseCaldavCalendarSettingsActivity {
@Inject EteSyncClient client;
private CreateCalendarViewModel createCalendarViewModel;
private DeleteCalendarViewModel deleteCalendarViewModel;
private UpdateCalendarViewModel updateCalendarViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewModelProvider provider = new ViewModelProvider(this);
createCalendarViewModel = provider.get(CreateCalendarViewModel.class);
deleteCalendarViewModel = provider.get(DeleteCalendarViewModel.class);
updateCalendarViewModel = provider.get(UpdateCalendarViewModel.class);
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed);
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed);
updateCalendarViewModel.observe(this, uid -> updateCalendar(), this::requestFailed);
}
@Override
protected void createCalendar(CaldavAccount caldavAccount, String name, int color) {
createCalendarViewModel.createCalendar(client, caldavAccount, name, color);
}
@Override
protected void updateNameAndColor(CaldavAccount account, CaldavCalendar calendar, String name,
int color) {
updateCalendarViewModel.updateCalendar(client, account, calendar, name, color);
}
@Override
protected void deleteCalendar(CaldavAccount caldavAccount, CaldavCalendar caldavCalendar) {
deleteCalendarViewModel.deleteCalendar(client, caldavAccount, caldavCalendar);
}
}

@ -0,0 +1,33 @@
package org.tasks.etesync
import android.os.Bundle
import androidx.activity.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
@AndroidEntryPoint
class EteSyncCalendarSettingsActivity : BaseCaldavCalendarSettingsActivity() {
private val createCalendarViewModel: CreateCalendarViewModel by viewModels()
private val deleteCalendarViewModel: DeleteCalendarViewModel by viewModels()
private val updateCalendarViewModel: UpdateCalendarViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createCalendarViewModel.observe(this, this::createSuccessful, this::requestFailed)
deleteCalendarViewModel.observe(this, this::onDeleted, this::requestFailed)
updateCalendarViewModel.observe(this, { updateCalendar() }, this::requestFailed)
}
override fun createCalendar(caldavAccount: CaldavAccount, name: String, color: Int) =
createCalendarViewModel.createCalendar(caldavAccount, name, color)
override fun updateNameAndColor(
account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) =
updateCalendarViewModel.updateCalendar(account, calendar, name, color)
override fun deleteCalendar(caldavAccount: CaldavAccount, caldavCalendar: CaldavCalendar) =
deleteCalendarViewModel.deleteCalendar(caldavAccount, caldavCalendar)
}

@ -1,16 +1,13 @@
package org.tasks.etesync
import androidx.hilt.lifecycle.ViewModelInject
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.ui.CompletableViewModel
class UpdateCalendarViewModel : CompletableViewModel<String?>() {
fun updateCalendar(
client: EteSyncClient,
account: CaldavAccount?,
calendar: CaldavCalendar?,
name: String?,
color: Int) {
run { client.forAccount(account!!).updateCollection(calendar!!, name, color) }
class UpdateCalendarViewModel @ViewModelInject constructor(
private val client: EteSyncClient): CompletableViewModel<String?>() {
fun updateCalendar(account: CaldavAccount, calendar: CaldavCalendar, name: String, color: Int) {
run { client.forAccount(account).updateCollection(calendar, name, color) }
}
}

@ -1,13 +1,14 @@
package org.tasks.etesync
import androidx.core.util.Pair
import com.etesync.journalmanager.UserInfoManager
import androidx.hilt.lifecycle.ViewModelInject
import com.etesync.journalmanager.UserInfoManager.UserInfo
import org.tasks.Strings.isNullOrEmpty
import org.tasks.ui.CompletableViewModel
class UpdateEteSyncAccountViewModel : CompletableViewModel<Pair<UserInfoManager.UserInfo, String>>() {
fun updateAccount(
client: EteSyncClient, url: String?, user: String?, pass: String?, token: String?) {
class UpdateEteSyncAccountViewModel @ViewModelInject constructor(
private val client: EteSyncClient) : CompletableViewModel<Pair<UserInfo, String>>() {
fun updateAccount(url: String, user: String, pass: String?, token: String) {
run {
client.setForeground()
if (isNullOrEmpty(pass)) {

@ -1,56 +0,0 @@
package org.tasks.ui;
import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Action;
import io.reactivex.schedulers.Schedulers;
@SuppressWarnings({"WeakerAccess", "RedundantSuppression"})
public class ActionViewModel extends ViewModel {
private final MutableLiveData<Boolean> completed = new MutableLiveData<>();
private final MutableLiveData<Throwable> error = new MutableLiveData<>();
private final CompositeDisposable disposables = new CompositeDisposable();
private boolean inProgress;
public void observe(
LifecycleOwner lifecycleOwner,
Observer<Boolean> completeObserver,
Observer<Throwable> errorObserver) {
completed.observe(lifecycleOwner, completeObserver);
error.observe(lifecycleOwner, errorObserver);
}
public boolean inProgress() {
return inProgress;
}
protected void run(Action action) {
assertMainThread();
if (!inProgress) {
inProgress = true;
disposables.add(
Completable.fromAction(action)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally(
() -> {
assertMainThread();
inProgress = false;
})
.subscribe(() -> completed.setValue(true), error::setValue));
}
}
@Override
protected void onCleared() {
disposables.clear();
}
}

@ -0,0 +1,46 @@
package org.tasks.ui
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.observe
import com.todoroo.andlib.utility.AndroidUtilities
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
open class ActionViewModel : ViewModel() {
private val completed = MutableLiveData<Boolean>()
private val error = MutableLiveData<Throwable>()
private val disposables = CompositeDisposable()
var inProgress = false
private set
fun observe(
lifecycleOwner: LifecycleOwner,
completeObserver: (Boolean) -> Unit,
errorObserver: (Throwable) -> Unit) {
completed.observe(lifecycleOwner, completeObserver)
error.observe(lifecycleOwner, errorObserver)
}
protected fun run(action: () -> Unit) {
AndroidUtilities.assertMainThread()
if (!inProgress) {
inProgress = true
disposables.add(
Completable.fromAction(action)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
AndroidUtilities.assertMainThread()
inProgress = false
}
.subscribe({ completed.setValue(true) }) { value: Throwable -> error.setValue(value) })
}
}
override fun onCleared() = disposables.clear()
}

@ -14,7 +14,9 @@ abstract class CompletableViewModel<T> : ViewModel() {
private val data = MutableLiveData<T>()
private val error = MutableLiveData<Throwable>()
private val disposables = CompositeDisposable()
private var inProgress = false
var inProgress = false
private set
fun observe(
lifecycleOwner: LifecycleOwner,
@ -24,10 +26,6 @@ abstract class CompletableViewModel<T> : ViewModel() {
error.observe(lifecycleOwner, errorObserver)
}
fun inProgress(): Boolean {
return inProgress
}
protected fun run(callable: () -> T) {
AndroidUtilities.assertMainThread()
if (!inProgress) {
@ -44,9 +42,7 @@ abstract class CompletableViewModel<T> : ViewModel() {
}
}
override fun onCleared() {
disposables.dispose()
}
override fun onCleared() = disposables.dispose()
fun removeObserver(owner: LifecycleOwner) {
data.removeObservers(owner)

Loading…
Cancel
Save