it.tidalwave.bluebill.mobile.android.observation.ObservationsActivity Maven / Gradle / Ivy
The newest version!
/***********************************************************************************************************************
*
* blueBill Mobile - Android - open source birding
* Copyright (C) 2009-2011 by Tidalwave s.a.s. (http://www.tidalwave.it)
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
***********************************************************************************************************************
*
* WWW: http://bluebill.tidalwave.it/mobile
* SCM: https://java.net/hg/bluebill-mobile~android-src
*
**********************************************************************************************************************/
package it.tidalwave.bluebill.mobile.android.observation;
import it.tidalwave.bluebill.mobile.android.util.CommonOptionsMenuController;
import it.tidalwave.bluebill.mobile.android.util.CommonOptionsMenuControllerProvider;
import it.tidalwave.netbeans.util.Locator;
import java.util.List;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import it.tidalwave.util.logging.Logger;
import it.tidalwave.util.thread.ThreadAssertions;
import it.tidalwave.util.thread.annotation.ThreadConfined;
import it.tidalwave.observation.Observation;
import it.tidalwave.observation.ObservationItem;
import it.tidalwave.mobile.util.DateUpdater;
import it.tidalwave.util.ui.UserNotification;
import it.tidalwave.util.ui.UserNotificationWithFeedback;
import it.tidalwave.bluebill.mobile.observation.ui.ObservationsView;
import it.tidalwave.bluebill.mobile.observation.ui.ReportUserNotificationWithFeedback;
import android.app.AlertDialog;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.app.ExpandableListActivity;
import android.app.TimePickerDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ImageButton;
import android.widget.TextView;
import it.tidalwave.ui.android.app.AndroidActivityHelper;
import it.tidalwave.mobile.android.annotation.AndroidWidget;
import it.tidalwave.mobile.android.util.ProgressDialogController;
import it.tidalwave.mobile.android.ui.AndroidUtilities;
import it.tidalwave.bluebill.mobile.android.R;
import it.tidalwave.mobile.util.ExceptionReporter;
import static it.tidalwave.util.thread.ThreadType.*;
/***********************************************************************************************************************
*
* The main activity for observations. It displays the current observation database and add / edit / etc operations can
* be performed from here.
*
* @stereotype View
* @stereotype Activity
*
* @author Fabrizio Giudici
* @version $Id$
*
**********************************************************************************************************************/
public class ObservationsActivity extends ExpandableListActivity implements ObservationsView
{
private static final String CLASS = ObservationsActivity.class.getName();
private static final Logger logger = Logger.getLogger(CLASS);
private static final int PROGRESS_DIALOG = 171717;
private final AndroidObservationsViewController controller;
private final AndroidActivityHelper activityHelper = new AndroidActivityHelper(this);
@Inject @AndroidWidget(R.id.tvFooter)
private TextView tvFooter;
@Inject @AndroidWidget(R.id.empty)
private View viEmpty;
private final CommonOptionsMenuController commonOptionsMenuController = Locator.find(CommonOptionsMenuControllerProvider.class).createCommonOptionsMenuController(this);
private final ProgressDialogController progressDialogController = new ProgressDialogController(this);
private Observation contextMenuObservation;
private ReportUserNotificationWithFeedback reportNotification;
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public ObservationsActivity()
{
controller = new AndroidObservationsViewController(this);
}
/*******************************************************************************************************************
*
* For tests.
*
******************************************************************************************************************/
protected ObservationsActivity (final @Nonnull AndroidObservationsViewController controller)
{
this.controller = controller;
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void setFooterText (final @Nonnull String string)
{
runOnUiThread(new Runnable()
{
public void run()
{
viEmpty.setVisibility(controller.getListAdapter().getGroupCount() == 0 ? View.VISIBLE : View.INVISIBLE);
tvFooter.setText(string);
}
});
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyNewObservationCommitted (final @Nonnull UserNotification notification)
{
activityHelper.showLightNotification(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyNewObservationCancelled (final @Nonnull UserNotification notification)
{
activityHelper.showLightNotification(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyAllObservationsDeleted (final @Nonnull UserNotification notification)
{
activityHelper.showLightNotification(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyObservationItemDeleted (final @Nonnull UserNotification notification)
{
activityHelper.showLightNotification(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyObservationDateTimeUpdated (final @Nonnull UserNotification notification)
{
activityHelper.showLightNotification(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void askForExportOptions (final @Nonnull ReportUserNotificationWithFeedback notification)
{
this.reportNotification = notification;
showDialog(R.id.export);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void askForShareOptions (final @Nonnull ReportUserNotificationWithFeedback notification)
{
this.reportNotification = notification;
showDialog(R.id.share);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void confirmToDeleteAllObservations (final @Nonnull UserNotificationWithFeedback notification)
{
activityHelper.showConfirmationDialog(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void confirmToDeleteAnObservationItem (final @Nonnull UserNotificationWithFeedback notification)
{
activityHelper.showConfirmationDialog(notification);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyExportCompleted (final @Nonnull UserNotificationWithFeedback userNotificationWithFeedback)
{
activityHelper.showNotificationDialog(userNotificationWithFeedback);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void notifyExportFailed (final @Nonnull UserNotificationWithFeedback userNotificationWithFeedback)
{
activityHelper.showErrorDialog(userNotificationWithFeedback);
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
public void highlightLatestObservation()
{
activityHelper.expandLastGroupAndCollapseOthers(getExpandableListView());
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override
public void onCreate (final @Nonnull Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_observations);
setListAdapter(controller.getListAdapter());
registerForContextMenu(getExpandableListView());
viEmpty = findViewById(R.id.empty);
tvFooter = (TextView)findViewById(R.id.tvFooter);
((ImageButton)findViewById(R.id.btFactSheet)).setOnClickListener(new OnClickListener()
{
public void onClick (final @Nonnull View view)
{
controller.browseToFactSheet();
}
});
((ImageButton)findViewById(R.id.btAdd)).setOnClickListener(new OnClickListener()
{
public void onClick (final @Nonnull View view)
{
controller.startNewObservationSequence();
}
});
}
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override
protected void onResume()
{
controller.getListAdapter().notifyDataSetChanged(); // e.g. we changed rendering options - FIXME: is this really needed?
super.onResume();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Override
public boolean onCreateOptionsMenu (final @Nonnull Menu menu)
{
getMenuInflater().inflate(R.menu.observations_options_menu, menu);
getMenuInflater().inflate(R.menu.common_options_menu, menu);
return true;
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Override
public void onCreateContextMenu (final @Nonnull ContextMenu menu,
final @Nonnull View view,
final @Nonnull ContextMenuInfo menuInfo)
{
final ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo)menuInfo;
final int type = ExpandableListView.getPackedPositionType(info.packedPosition);
int menuId = 0;
switch (type)
{
case ExpandableListView.PACKED_POSITION_TYPE_GROUP:
menuId = R.menu.observations_context_menu;
break;
case ExpandableListView.PACKED_POSITION_TYPE_CHILD:
menuId = R.menu.observation_items_context_menu;
break;
default:
return;
}
getMenuInflater().inflate(menuId, menu);
}
/*******************************************************************************************************************
*
* Adapts between the Android options menu callback and the controller.
*
******************************************************************************************************************/
@Override
public boolean onOptionsItemSelected (final @Nonnull MenuItem item)
{
switch (item.getItemId())
{
case R.id.clearObservations:
controller.deleteAllObservations();
return true;
case R.id.share:
controller.shareObservations();
return true;
case R.id.export:
controller.exportObservations();
return true;
default:
return commonOptionsMenuController.onOptionsItemSelected(item);
}
}
/*******************************************************************************************************************
*
* Adapts between the Android context menu callback and the controller.
*
******************************************************************************************************************/
@Override
public boolean onContextItemSelected (final @Nonnull MenuItem item)
{
final ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo)item.getMenuInfo();
final int type = ExpandableListView.getPackedPositionType(info.packedPosition);
final int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
switch (type)
{
case ExpandableListView.PACKED_POSITION_TYPE_CHILD:
final int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition);
final ObservationItem observationItem = controller.getListAdapter().getChild(groupPosition, childPosition);
switch (item.getItemId())
{
case R.id.deleteObservationItem:
controller.deleteObservationItem(observationItem);
break;
}
return true;
case ExpandableListView.PACKED_POSITION_TYPE_GROUP:
contextMenuObservation = controller.getListAdapter().getGroup(groupPosition);
switch (item.getItemId())
{
case R.id.editDate:
case R.id.editTime:
showDialog(item.getItemId()); // FIXME: call the controller, this is a V-C interaction
break;
}
return true;
}
return false;
}
/*******************************************************************************************************************
*
* Notifies that Android is ready to create and show a {@link Dialog} which has been previously requested with
* {@link #showDialog(int)}; creates and return the {@code Dialog} to show.
*
* @return the {@link Dialog}
*
******************************************************************************************************************/
@Override @Nonnull
protected Dialog onCreateDialog (final int id)
{
// FIXME: this is a user feedback. Would it be better if done with a QuestionAndFeedback?
final ActionListener changeDateTime = new ActionListener()
{
public void actionPerformed (final @Nonnull ActionEvent event)
{
final DateUpdater dateUpdater = (DateUpdater)event.getSource();
controller.updateObservationDate(contextMenuObservation, dateUpdater);
}
};
switch (id)
{
case R.id.editTime:
return activityHelper.createTimePickerDialog(changeDateTime);
case R.id.editDate:
return activityHelper.createDatePickerDialog(changeDateTime);
case R.id.export:
case R.id.share:
// FIXME: could be null after a restart!
return createReportDialog(reportNotification);
case PROGRESS_DIALOG:
return progressDialogController.getProgressDialog();
}
throw new RuntimeException("Unknown dialog id: " + id);
}
/*******************************************************************************************************************
*
* Specific Android stuff for preparing {@link DatePickerDialog} and {@link TimePickerDialog}.
*
******************************************************************************************************************/
@Override
protected void onPrepareDialog (final int id, final @Nonnull Dialog dialog)
{
switch (id)
{
case R.id.editTime:
AndroidUtilities.setDate((TimePickerDialog)dialog, contextMenuObservation.getDate());
break;
case R.id.editDate:
AndroidUtilities.setDate((DatePickerDialog)dialog, contextMenuObservation.getDate());
break;
}
}
/*******************************************************************************************************************
*
* Handles the Android notification which an activity has been completed. In our specific case, we handle the
* completion of the {@link AddObservationControlFlow}.
*
******************************************************************************************************************/
@Override
protected void onActivityResult (final int requestCode, final int resultCode, final @Nonnull Intent data)
{
AddObservationControlFlow.onActivityResult(controller, requestCode, resultCode, data);
}
/*******************************************************************************************************************
*
* Creates the dialog with the options for generating a report.
*
* @return the {@link Dialog}
*
******************************************************************************************************************/
@Nonnull @ThreadConfined(type=UI)
private Dialog createReportDialog (final @Nonnull ReportUserNotificationWithFeedback reportNotification)
{
ThreadAssertions.assertThread(UI);
// TODO: try to move to some extension of UserNotificationWithFeedback which is understood by AndroidUIHelper
final List options = reportNotification.getOptions();
final CharSequence[] choices = new CharSequence[options.size()];
for (int i = 0; i < options.size(); i++)
{
choices[i] = options.get(i);
}
final boolean[] choiceResults = new boolean[options.size()];
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(reportNotification.getCaption());
builder.setMultiChoiceItems(choices, choiceResults, new DialogInterface.OnMultiChoiceClickListener()
{
public void onClick (final @Nonnull DialogInterface dialogInterface,
final @Nonnegative int index,
final boolean value)
{
choiceResults[index] = value;
}
});
builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
{
public void onClick (final @Nonnull DialogInterface dialogInterface,
final int i)
{
reportNotification.setKmlAttachment(choiceResults[0]);
reportNotification.setCsvAttachment(choiceResults[1]);
reportNotification.setProgressListener(progressDialogController);
reportNotification.confirm();
showDialog(PROGRESS_DIALOG); // FIXME: should be called back by the controller
}
});
builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener()
{
public void onClick (final @Nonnull DialogInterface di, final int i)
{
try
{
reportNotification.cancel();
}
catch (Throwable t)
{
ExceptionReporter.reportException(t);
}
}
});
return builder.create();
}
}