com.makotogo.mobile.hoursdroid.HoursDetailFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.makotogo.mobile.hoursdroid.HoursDetailFragment.java

Source

/*
 *     Copyright 2016 Makoto Consulting Group, Inc.
 *
 *     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.
 *
 */

package com.makotogo.mobile.hoursdroid;

import android.app.Activity;
import android.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.res.ResourcesCompat;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.makotogo.mobile.framework.AbstractArrayAdapter;
import com.makotogo.mobile.framework.AbstractFragment;
import com.makotogo.mobile.framework.ViewBinder;
import com.makotogo.mobile.hoursdroid.model.DataStore;
import com.makotogo.mobile.hoursdroid.model.Hours;
import com.makotogo.mobile.hoursdroid.model.Project;
import com.makotogo.mobile.hoursdroid.util.ApplicationOptions;
import com.makotogo.mobile.hoursdroid.util.RoundingUtils;

import org.joda.time.LocalDateTime;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

import java.util.Date;
import java.util.List;

/**
 * Created by sperry on 1/13/16.
 */
public class HoursDetailFragment extends AbstractFragment {

    public static final int REQUEST_CODE_MANAGE_PROJECTS = 200;
    // Logging
    private static final String TAG = HoursDetailFragment.class.getSimpleName();
    // Request IDs that will be used to identify which dialog is coming back with
    /// a result for us.
    private static final int REQUEST_BEGIN_DATE_PICKER = 100;
    private static final int REQUEST_END_DATE_PICKER = 110;
    private static final int REQUEST_BREAK = 120;
    // Don'tcha hate repeating yourself??
    private static final String DATE_FORMAT_PATTERN = "M/d/yyyy h:mm a";

    private static PeriodFormatter sPeriodFormatter = new PeriodFormatterBuilder().printZeroNever().appendDays()
            .appendSuffix("d").appendSeparator(", ").appendHours().appendSuffix("h").appendSeparator(": ")
            .appendMinutes().appendSuffix("m")
            //            .appendSeparator(": ")
            //            .appendSeconds().appendSuffix("s")
            .toFormatter();

    /**
     * The Hours object we are editing. It can easily be reconstituted from fragment
     * arguments Bundle, so we don't need to save it, but having it here as a class
     * variable is oh-so convenient.
     */
    private transient Hours mHours;

    @Override
    protected void processFragmentArguments() {
        mHours = (Hours) getArguments().getSerializable(FragmentFactory.FRAG_ARG_HOURS);
        if (mHours == null) {
            throw new RuntimeException("Fragment argument (Hours) cannot be null!");
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    /**
     * Called by the Framework as part of the View creation process.
     *
     * @param layoutInflater
     * @param container
     * @param savedInstanceState
     * @return
     */
    @Override
    protected View configureUI(LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
        final String METHOD = "configureUI(...): ";
        View view = layoutInflater.inflate(R.layout.fragment_hours_detail, container, false);
        // Project Spinner
        configureProjectSpinner(view);
        // Begin Date
        configureBeginDate(view);
        // End Date
        configureEndDate(view);
        // Break Time
        configureBreakTime(view);
        // Total Time
        configureTotalTime(view);
        // Billed
        configureBilled(view);
        // Description
        configureDescription(view);
        // Save Button
        configureSaveButton(view);

        return view;
    }

    @Override
    public void saveInstanceState(Bundle outState) {
        // Nothing to do
    }

    @Override
    public void restoreInstanceState(Bundle savedInstanceState) {
        // Nothing to do
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        final String METHOD = "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + "): ";
        if (resultCode == Activity.RESULT_OK) {
            // Figure out which Result code we are dealing with. This method
            /// handles the results of all dialog fragments used to set the
            /// model data.
            switch (requestCode) {
            case REQUEST_BEGIN_DATE_PICKER:
                Date beginDate = (Date) data.getSerializableExtra(DateTimePickerFragment.RESULT_DATE_TIME);
                LocalDateTime ldtBeginDate = new LocalDateTime(beginDate.getTime());
                if (ldtBeginDate.isBefore(new LocalDateTime(mHours.getEnd().getTime()))) {
                    mHours.setBegin(beginDate);
                    updateUI();
                } else {
                    String message = "End date must be after begin date";
                    Log.e(TAG, METHOD + message);
                    Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
                }
                break;
            case REQUEST_END_DATE_PICKER:
                Date endDate = (Date) data.getSerializableExtra(DateTimePickerFragment.RESULT_DATE_TIME);
                LocalDateTime ldtEndDate = new LocalDateTime(endDate.getTime());
                if (ldtEndDate.isAfter(new LocalDateTime(mHours.getBegin().getTime()))) {
                    mHours.setEnd(endDate);
                    updateUI();
                } else {
                    String message = "End date must be after begin date";
                    Log.e(TAG, METHOD + message);
                    Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
                }
                break;
            case REQUEST_BREAK:
                Integer breakTimeInMinutes = (Integer) data
                        .getSerializableExtra(NumberPickerFragment.RESULT_MINUTES);
                mHours.setBreak(renderBreakForStorage(breakTimeInMinutes));
                updateUI();
                break;
            case REQUEST_CODE_MANAGE_PROJECTS:
                Project project = (Project) data.getSerializableExtra(ProjectListActivity.RESULT_PROJECT);
                mHours.setProject(project);
                updateUI();
                break;
            default:
                break;
            }
        }
    }

    @Override
    protected void updateUI() {
        updateBegin();
        updateEnd();
        updateBreak();
        updateTotal();
        updateBilled();
        updateProjectSpinner();
    }

    private void updateProjectSpinner() {
        DataStore dataStore = DataStore.instance(getActivity());
        List<Project> projects = dataStore.getProjects(mHours.getJob());
        projects.add(Project.MANAGE_PROJECTS);
        AbstractArrayAdapter<Project> projectListAdapter = getProjectListAdapter();
        if (projectListAdapter != null) {
            projectListAdapter.clear();
            projectListAdapter.addAll(projects);
            projectListAdapter.notifyDataSetChanged();
            int selectedIndex = 0;
            // Figure out which selection corresponds to the active project
            for (int aa = 0; aa < projectListAdapter.getCount(); aa++) {
                if (projectListAdapter.getItem(aa).equals(mHours.getProject())) {
                    selectedIndex = aa;
                    break;
                }
            }
            // Select the active project
            getProjectSpinner().setSelection(selectedIndex);
        }
    }

    private void updateBegin() {
        if (mHours.getBegin() != null) {
            LocalDateTime beginDateTime = new LocalDateTime(mHours.getBegin().getTime());
            ((TextView) getView().findViewById(R.id.textview_hours_detail_begin_date))
                    .setText(beginDateTime.toString(DATE_FORMAT_PATTERN));
        }
    }

    private void updateEnd() {
        if (mHours.getEnd() != null) {
            LocalDateTime endDateTime = new LocalDateTime(mHours.getEnd().getTime());
            ((TextView) getView().findViewById(R.id.textview_hours_detail_end_date))
                    .setText(endDateTime.toString(DATE_FORMAT_PATTERN));
        }
    }

    private void updateBreak() {
        long breakMillis = 0L;
        if (mHours.getBreak() != null) {
            breakMillis = mHours.getBreak();
        }
        ((TextView) getView().findViewById(R.id.textview_hours_detail_break))
                .setText(renderTimePeriodForDisplay(breakMillis));
    }

    private void updateTotal() {
        long totalMillis = 0L;
        if (mHours.getEnd() != null) {
            long beginMillis = (mHours.getBegin() == null) ? 0L : mHours.getBegin().getTime();
            long endMillis = (mHours.getEnd() == null) ? 0L : mHours.getEnd().getTime();
            long breakMillis = (mHours.getBreak() == null) ? 0L : mHours.getBreak();
            totalMillis = endMillis - beginMillis - breakMillis;
            totalMillis = RoundingUtils.applyRoundingIfNecessary(getActivity(), totalMillis);
        }
        Log.d(TAG, "Updating millis with value => " + totalMillis);
        ((TextView) getView().findViewById(R.id.textview_hours_detail_total))
                .setText(renderTimePeriodForDisplay(totalMillis));
    }

    private void updateBilled() {
        // Nothing to do
    }

    @Override
    protected boolean validate(View view) {
        return true;
    }

    private void configureProjectSpinner(View view) {
        final Spinner projectSpinner = (Spinner) view.findViewById(R.id.spinner_hours_detail_project);
        projectSpinner.setEnabled(isThisHoursRecordNotActive());
        if (isThisHoursRecordActive()) {
            projectSpinner.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.no_border, null));
        } else {
            projectSpinner
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.rounded_border, null));
        }
        projectSpinner.setAdapter(new AbstractArrayAdapter(getActivity(), R.layout.project_list_row) {
            @Override
            protected ViewBinder<Project> createViewBinder() {
                return new ProjectViewBinder();
            }
        });
        projectSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                Project project = (Project) projectSpinner.getAdapter().getItem(position);
                if (project == Project.MANAGE_PROJECTS) {
                    // Launch the Project List Screen
                    Intent intent = new Intent(getActivity(), ProjectListActivity.class);
                    intent.putExtra(ProjectListActivity.EXTRA_JOB, mHours.getJob());
                    //Toast.makeText(getActivity(), "Launching ProjectListActivity (eventually)...", Toast.LENGTH_LONG).show();
                    startActivityForResult(intent, REQUEST_CODE_MANAGE_PROJECTS);
                } else {
                    // Active project has changed. Update the UI.
                    mHours.setProject(project);
                    updateUI();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                // Nothing to do
            }
        });
    }

    private Spinner getProjectSpinner() {
        View view = getView();
        if (view == null) {
            throw new RuntimeException("View has not yet been configured. Cannot invoke getProjectSpinner()!");
        }
        return (Spinner) view.findViewById(R.id.spinner_hours_detail_project);
    }

    private AbstractArrayAdapter<Project> getProjectListAdapter() {
        AbstractArrayAdapter<Project> ret;
        if (getProjectSpinner() == null) {
            throw new RuntimeException("Project Spinner has not been configured!");
        }
        ret = (AbstractArrayAdapter<Project>) getProjectSpinner().getAdapter();
        return ret;
    }

    private void configureBeginDate(View view) {
        TextView beginDateTextView = (TextView) view.findViewById(R.id.textview_hours_detail_begin_date);
        beginDateTextView.setEnabled(isThisHoursRecordNotActive());
        if (isThisHoursRecordActive()) {
            beginDateTextView
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.no_border, null));
        } else {
            beginDateTextView
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.rounded_border, null));
        }
        beginDateTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                // If there is already a Date displayed, use that.
                Date dateToUse = (mHours.getBegin() == null) ? new Date() : mHours.getBegin();
                DateTimePickerFragment datePickerFragment = FragmentFactory.createDatePickerFragment(dateToUse,
                        "Begin", DateTimePickerFragment.BOTH);
                datePickerFragment.setTargetFragment(HoursDetailFragment.this, REQUEST_BEGIN_DATE_PICKER);
                datePickerFragment.show(fragmentManager, DateTimePickerFragment.DIALOG_TAG);
            }
        });
        if (mHours.getBegin() != null) {
            LocalDateTime beginDateTime = new LocalDateTime(mHours.getBegin().getTime());
            beginDateTextView.setText(beginDateTime.toString(DATE_FORMAT_PATTERN));
        }
    }

    private void configureEndDate(View view) {
        TextView endDateTextView = (TextView) view.findViewById(R.id.textview_hours_detail_end_date);
        endDateTextView.setEnabled(isThisHoursRecordNotActive());
        if (isThisHoursRecordActive()) {
            endDateTextView.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.no_border, null));
        } else {
            endDateTextView
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.rounded_border, null));
        }
        endDateTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                // If there is already a Date displayed, use that.
                Date dateToUse = (mHours.getEnd() == null) ? new Date() : mHours.getEnd();
                DateTimePickerFragment datePickerFragment = FragmentFactory.createDatePickerFragment(dateToUse,
                        "End", DateTimePickerFragment.BOTH);
                datePickerFragment.setTargetFragment(HoursDetailFragment.this, REQUEST_END_DATE_PICKER);
                datePickerFragment.show(fragmentManager, DateTimePickerFragment.DIALOG_TAG);
            }
        });
        if (mHours.getEnd() != null) {
            LocalDateTime endDateTime = new LocalDateTime(mHours.getEnd().getTime());
            endDateTextView.setText(endDateTime.toString(DATE_FORMAT_PATTERN));
        }
    }

    private void configureBreakTime(View view) {
        TextView breakTimeTextView = (TextView) view.findViewById(R.id.textview_hours_detail_break);
        breakTimeTextView.setEnabled(isThisHoursRecordNotActive());
        if (isThisHoursRecordActive()) {
            breakTimeTextView
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.no_border, null));
        } else {
            breakTimeTextView
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.rounded_border, null));
        }
        breakTimeTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FragmentManager fragmentManager = getFragmentManager();
                Integer minutes = renderStorageBreakForEdit(mHours.getBreak().intValue());
                Log.d(TAG, "Number of minutes: " + minutes);
                // Max minutes can at most be number of minutes diff between end and begin
                Integer maxMinutes = (int) (mHours.getEnd().getTime() - mHours.getBegin().getTime()) / 60000;
                NumberPickerFragment numberPickerFragment = FragmentFactory.createNumberPickerFragment(minutes,
                        maxMinutes, "Break Time");
                numberPickerFragment.setTargetFragment(HoursDetailFragment.this, REQUEST_BREAK);
                numberPickerFragment.show(fragmentManager, NumberPickerFragment.DIALOG_TAG);
            }
        });
        breakTimeTextView.setText(renderTimePeriodForDisplay(mHours.getBreak()));
    }

    private void configureTotalTime(View view) {
        TextView totalTimeTextView = (TextView) view.findViewById(R.id.textview_hours_detail_total);
        if (isThisHoursRecordNotActive()) {
            long elapsedTime = mHours.getEnd().getTime() - mHours.getBegin().getTime() - mHours.getBreak();
            elapsedTime = RoundingUtils.applyRoundingIfNecessary(getActivity(), elapsedTime);
            Period period = new Period(elapsedTime);
            totalTimeTextView.setText(sPeriodFormatter.print(period));
        }
    }

    private void configureBilled(View view) {
        CheckBox billedCheckBox = (CheckBox) view.findViewById(R.id.checkbox_hours_detail_billed);
        billedCheckBox.setEnabled(isThisHoursRecordNotActive());
        billedCheckBox.setChecked(mHours.isBilled());
        if (isThisHoursRecordActive()) {
            billedCheckBox.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.no_border, null));
        } else {
            billedCheckBox
                    .setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.rounded_border, null));
        }
        billedCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mHours.setBilled(isChecked);
            }
        });
    }

    private void configureDescription(View view) {
        EditText descriptionEditText = (EditText) view.findViewById(R.id.edittext_hours_detail_description);
        descriptionEditText.setText(mHours.getDescription());
        descriptionEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                // Nothing to do
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                mHours.setDescription(s.toString());
            }

            @Override
            public void afterTextChanged(Editable s) {
                // Nothing to do
            }
        });
    }

    private void configureSaveButton(View view) {
        Button saveButton = (Button) view.findViewById(R.id.button_hours_detail_save);
        saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String METHOD = "configureSaveButton(View)->onClick(View): ";
                // Validate and save the Hours record
                if (validate(HoursDetailFragment.this.getView())) {
                    DataStore dataStore = DataStore.instance(getActivity());
                    if (dataStore.update(mHours) > 0) {
                        if (ApplicationOptions.instance(getActivity()).showNotifications()) {
                            Toast.makeText(getActivity(), "Your changes have been saved.", Toast.LENGTH_SHORT)
                                    .show();
                        }
                        getActivity().finish();
                    }
                } else {
                    Log.w(TAG, METHOD + "Validation errors prevented saving the Hours record.");
                }
            }
        });
    }

    private String renderTimePeriodForDisplay(long breakTimeInMillis) {
        Period period = new Period(breakTimeInMillis);
        return sPeriodFormatter.print(period);
    }

    private Long renderBreakForStorage(long breakTimeInMinutes) {
        Long breakTimeInMillis = breakTimeInMinutes * 60000L;
        return breakTimeInMillis;
    }

    private Integer renderStorageBreakForEdit(long breakTimeInMillis) {
        Long breakTimeInMinutes = breakTimeInMillis / 60000;
        return breakTimeInMinutes.intValue();
    }

    private boolean isThisHoursRecordNotActive() {
        return !isThisHoursRecordActive();
    }

    private boolean isThisHoursRecordActive() {
        // If the End time is null, then this record is active.
        return mHours.getEnd() == null;
    }

}