All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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();
      }
  }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy