blob: 30ac61b4cd383e7daafc78a3279a58a47c3d04bb [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.todos;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.VisibleForTesting;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import io.v.todos.model.DataList;
import io.v.todos.model.ListMetadata;
import io.v.todos.model.ListSpec;
import io.v.todos.persistence.ListEventListener;
import io.v.todos.persistence.MainPersistence;
import io.v.todos.persistence.PersistenceFactory;
/**
* MainActivity for Vanadium TODOs
*
* This activity shows a list of todo lists.
* - Tap on a Todo List to launch its corresponding TodoListActivity.
* - Swipe Left on a Todo List to delete it.
* - Swipe Right in order to mark all of its Tasks as done.
*
* @author alexfandrianto
*/
public class MainActivity extends TodosAppActivity<MainPersistence, TodoListRecyclerAdapter> {
// Snackoos are the code name for the list of todos.
// These todos are backed up at the SNACKOOS child of the Firebase URL.
// We use mMainList to track a custom sorted list of the stored values.
static final String INTENT_SNACKOO_KEY = "snackoo key";
private DataList<ListMetadata> mMainList = new DataList<>();
private RecyclerView mRecyclerView;
private static final int BLE_LOCATION_PERMISSIONS_REQUEST_CODE = 1;
private PersistenceInitializer<MainPersistence> mPersistenceInitializer;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mEmptyView.setText(R.string.no_lists);
// Set up the todo list adapter
mAdapter = new TodoListRecyclerAdapter(mMainList, new View.OnClickListener() {
@Override
public void onClick(View view) {
String fbKey = (String)view.getTag();
enterList(fbKey);
}
});
mRecyclerView = (RecyclerView)findViewById(R.id.recycler);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
new ItemTouchHelper(new SwipeableTouchHelperCallback(0, ItemTouchHelper.LEFT) {
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) {
String todoListKey = (String)viewHolder.itemView.getTag();
if (direction == ItemTouchHelper.LEFT) {
mPersistence.deleteTodoList(todoListKey);
}
}
}).attachToRecyclerView(mRecyclerView);
mPersistenceInitializer = new PersistenceInitializer<MainPersistence>(this) {
@Override
protected MainPersistence initPersistence() throws Exception {
return PersistenceFactory.getMainPersistence(mActivity, savedInstanceState,
createMainListener());
}
@Override
protected void onSuccess(MainPersistence persistence) {
mPersistence = persistence;
setEmptyVisiblity();
}
};
// mPersistenceInitializer.execute is invoked once we request location permissions from the
// app user.
requestLocationPermissions();
}
// Creates a listener for this activity. Visible to tests to allow them to invoke the listener
// methods directly.
@VisibleForTesting
ListEventListener<ListMetadata> createMainListener() {
return new ListEventListener<ListMetadata>() {
@Override
public void onItemAdd(ListMetadata item) {
int position = mMainList.insertInOrder(item);
mAdapter.notifyItemInserted(position);
setEmptyVisiblity();
}
@Override
public void onItemUpdate(final ListMetadata item) {
int start = mMainList.findIndexByKey(item.key);
int end = mMainList.updateInOrder(item);
if (start != end) {
mAdapter.notifyItemMoved(start, end);
}
// The change animation involves a cross-fade that, if interrupted
// while another for the same item is already in progress, interacts
// badly with ItemTouchHelper's swipe animator. The effect would be
// a flicker of the intermediate ListMetadata view, then it fading
// out to the latest view but X-translated off the screen due to the
// swipe animator.
//
// We could queue up the next change after the current one, but it's
// probably better just to rebind.
TodoListViewHolder vh = (TodoListViewHolder) mRecyclerView
.findViewHolderForAdapterPosition(end);
if (vh != null && vh.itemView.getAlpha() < 1) {
mAdapter.bindViewHolder(vh, end);
} else {
mAdapter.notifyItemChanged(end);
}
}
@Override
public void onItemDelete(String key) {
int position = mMainList.removeByKey(key);
mAdapter.notifyItemRemoved(position);
setEmptyVisiblity();
}
};
}
public void initiateItemAdd(View view) {
UIUtil.showAddDialog(this, "New Todo List", new UIUtil.DialogResponseListener() {
@Override
public void handleResponse(String response) {
String newKey = mPersistence.addTodoList(new ListSpec(response));
enterList(newKey);
}
});
}
private void enterList(final String listKey) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(MainActivity.this, TodoListActivity.class);
intent.putExtra(INTENT_SNACKOO_KEY, listKey);
startActivity(intent);
overridePendingTransition(R.anim.right_slide_in, R.anim.left_slide_out);
}
}, 100); // Use a short delay to allow the keyboard to disappear.
}
// The following methods are boilerplate for handling the Menu in the top right corner.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_debug) {
sharePersistenceDebugDetails();
return true;
}
return super.onOptionsItemSelected(item);
}
// TODO(suharshs): Move this into the BLE Driver code so that all app developers don't have to
// request this.
public void requestLocationPermissions() {
// Request location permissions at run-time to allow BLE scanning.
// As per: https://developer.android.com/training/permissions/requesting.html.
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
BLE_LOCATION_PERMISSIONS_REQUEST_CODE);
} else {
mPersistenceInitializer.execute(PersistenceFactory.mightGetMainPersistenceBlock());
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case BLE_LOCATION_PERMISSIONS_REQUEST_CODE: {
mPersistenceInitializer.execute(PersistenceFactory.mightGetMainPersistenceBlock());
return;
}
}
}
}