cz.maresmar.sfm.view.menu.MenuDetailsFragment.java Source code

Java tutorial

Introduction

Here is the source code for cz.maresmar.sfm.view.menu.MenuDetailsFragment.java

Source

/*
 * SmartFoodMenu - Android application for canteens extendable with plugins
 *
 * Copyright  2016-2018  Martin Mare <mmrmartin[at]gmail[dot]com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package cz.maresmar.sfm.view.menu;

import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
import android.widget.TextView;

import cz.maresmar.sfm.Assert;
import cz.maresmar.sfm.BuildConfig;
import cz.maresmar.sfm.R;
import cz.maresmar.sfm.provider.ProviderContract;
import cz.maresmar.sfm.utils.ActionUtils;
import cz.maresmar.sfm.utils.MenuUtils;
import timber.log.Timber;

/**
 * Fragment that allows full editing of orders (internally actions connected to menu entry) and showing
 * of menu details
 */
public class MenuDetailsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
    private static final String ARG_USER_URI = "userUri";
    private static final String ARG_MENU_RELATIVE_ID = "menuRelativeId";
    private static final String ARG_PORTAL_ID = "portalId";

    private static final int MENU_LOADER_ID = 1;

    private Uri mUserUri;
    private long mMenuRelativeId;
    private long mPortalId;

    private TextView mDateText, mPriceText, mTextText, mPortalName;
    private Button mChangedReservedEditText, mChangedOfferedEditText;
    private TextView mToSyncReservedText, mToSyncOfferedText;
    private TextView mSyncedReservedText, mSyncedOfferedText;
    private TextView mTakenAmountText, mToTakeText, mToOrderText, mLastChangeText;
    private View mToTakeGroup, mOfferGroup;

    private int mMaxReserved = Integer.MAX_VALUE;
    private int mMinReserved = 0;
    private int mSyncedTaken = 0;
    private int mSyncedReserved = -1;

    /**
     * Creates new instance of fragment with specific menu entry
     *
     * @param userUri        User Uri prefix
     * @param menuRelativeId Relative ID of menu entry
     * @param portalId       ID of portal
     * @return A new instance of fragment
     */
    public static MenuDetailsFragment newInstance(Uri userUri, long menuRelativeId, long portalId) {
        MenuDetailsFragment fragment = new MenuDetailsFragment();
        Bundle args = new Bundle();
        args.putParcelable(ARG_USER_URI, userUri);
        args.putLong(ARG_MENU_RELATIVE_ID, menuRelativeId);
        args.putLong(ARG_PORTAL_ID, portalId);
        fragment.setArguments(args);
        return fragment;
    }

    // -------------------------------------------------------------------------------------------
    // Fragment lifecycle
    // -------------------------------------------------------------------------------------------

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mUserUri = getArguments().getParcelable(ARG_USER_URI);
            mMenuRelativeId = getArguments().getLong(ARG_MENU_RELATIVE_ID);
            mPortalId = getArguments().getLong(ARG_PORTAL_ID);
        }

        getLoaderManager().initLoader(MENU_LOADER_ID, null, this);
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_menu_details, container, false);

        mDateText = view.findViewById(R.id.menu_date_text);
        mPriceText = view.findViewById(R.id.menu_price_text);
        mTextText = view.findViewById(R.id.menu_text_text);
        mPortalName = view.findViewById(R.id.menu_portal_text);

        mChangedReservedEditText = view.findViewById(R.id.menu_reserve_button);
        mChangedReservedEditText
                .setOnClickListener(v -> showPickerDialog(R.string.menu_detail_reserved, mMinReserved, mMaxReserved,
                        Integer.valueOf(mChangedReservedEditText.getText().toString()), newVal -> {
                            mChangedReservedEditText.setText(String.valueOf(newVal));
                            saveChanges();
                        }));

        mChangedOfferedEditText = view.findViewById(R.id.menu_offer_button);
        mChangedOfferedEditText.setOnClickListener(v -> {
            int reserved = Integer.valueOf(mChangedReservedEditText.getText().toString());
            showPickerDialog(R.string.menu_detail_offered, 0,
                    Math.min(mSyncedReserved - mSyncedTaken, reserved - mSyncedTaken),
                    Integer.valueOf(mChangedOfferedEditText.getText().toString()), newVal -> {
                        mChangedOfferedEditText.setText(String.valueOf(newVal));
                        saveChanges();
                    });
        });

        mToSyncReservedText = view.findViewById(R.id.menu_to_sync_reserved_text);
        mToSyncOfferedText = view.findViewById(R.id.menu_to_sync_offered_text);

        mSyncedReservedText = view.findViewById(R.id.menu_synced_reserved_text);
        mSyncedOfferedText = view.findViewById(R.id.menu_synced_offered_text);

        mTakenAmountText = view.findViewById(R.id.menu_taken_text);
        mToTakeText = view.findViewById(R.id.menu_to_take_text);
        mToOrderText = view.findViewById(R.id.menu_to_order_text);

        mLastChangeText = view.findViewById(R.id.menu_last_change_text);

        mToTakeGroup = view.findViewById(R.id.menu_to_take_group);
        mOfferGroup = view.findViewById(R.id.menu_offered_group);

        return view;
    }

    // -------------------------------------------------------------------------------------------
    // Loader callbacks
    // -------------------------------------------------------------------------------------------

    @NonNull
    @Override
    public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
        switch (id) {
        case MENU_LOADER_ID: {
            Uri menuUri = Uri.withAppendedPath(mUserUri, ProviderContract.MENU_ENTRY_PATH);
            return new CursorLoader(getContext(), menuUri, new String[] { ProviderContract.MenuEntry.STATUS,
                    ProviderContract.MenuEntry.PORTAL_FEATURES, ProviderContract.MenuEntry.DATE,
                    ProviderContract.MenuEntry.PRICE, ProviderContract.MenuEntry.TEXT,
                    ProviderContract.MenuEntry.PORTAL_NAME, ProviderContract.MenuEntry.SYNCED_RESERVED_AMOUNT,
                    ProviderContract.MenuEntry.SYNCED_OFFERED_AMOUNT,
                    ProviderContract.MenuEntry.LOCAL_RESERVED_AMOUNT,
                    ProviderContract.MenuEntry.LOCAL_OFFERED_AMOUNT,
                    ProviderContract.MenuEntry.EDIT_RESERVED_AMOUNT, ProviderContract.MenuEntry.EDIT_OFFERED_AMOUNT,
                    ProviderContract.MenuEntry.SYNCED_TAKEN_AMOUNT, ProviderContract.MenuEntry.REMAINING_TO_TAKE,
                    ProviderContract.MenuEntry.REMAINING_TO_ORDER, ProviderContract.MenuEntry.LAST_ACTION_CHANGE,
                    ProviderContract.MenuEntry.LABEL },
                    ProviderContract.MenuEntry.ME_RELATIVE_ID + " = " + mMenuRelativeId + " AND "
                            + ProviderContract.MenuEntry.PORTAL_ID + " = " + mPortalId,
                    null, null);
        }
        default:
            throw new UnsupportedOperationException("Unknown loader id: " + id);
        }
    }

    @Override
    public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
        case MENU_LOADER_ID: {
            Timber.d("Menu data loaded");

            if (BuildConfig.DEBUG) {
                Assert.isOne(cursor.getCount());
            }

            cursor.moveToFirst();

            @ProviderContract.MenuStatus
            int status = cursor.getInt(0);
            @ProviderContract.PortalFeatures
            int features = cursor.getInt(1);

            // Menu detail text
            final long rawDate = cursor.getLong(2);
            mDateText.setText(MenuUtils.getDateStr(mDateText.getContext(), rawDate));
            final int rawPrice = cursor.getInt(3);
            mPriceText.setText(MenuUtils.getPriceStr(mPriceText.getContext(), rawPrice));
            mTextText.setText(cursor.getString(4));
            mPortalName.setText(cursor.getString(5));

            // Taken amount handling
            mSyncedTaken = cursor.getInt(12);
            mTakenAmountText.setText(String.valueOf(mSyncedTaken));

            // Amount table
            // Synced
            mSyncedReserved = cursor.getInt(6);
            int syncedOffered = cursor.getInt(7);

            mSyncedReservedText.setText(String.valueOf(mSyncedReserved));
            if ((status & ProviderContract.MENU_STATUS_CANCELABLE) == ProviderContract.MENU_STATUS_CANCELABLE) {
                mMinReserved = mSyncedTaken;
            } else {
                mMinReserved = mSyncedReserved;
            }
            mSyncedOfferedText.setText(String.valueOf(syncedOffered));

            int reserved = mSyncedReserved;
            int offered = syncedOffered;

            // To sync
            if (!cursor.isNull(8)) {
                reserved = cursor.getInt(8);
                offered = cursor.getInt(9);
                mToSyncReservedText.setText(String.valueOf(reserved));
                mToSyncOfferedText.setText(String.valueOf(offered));
            } else {
                mToSyncReservedText.setText(R.string.menu_detail_empty);
                mToSyncOfferedText.setText(R.string.menu_detail_empty);
            }
            // Change
            if (!cursor.isNull(10)) {
                reserved = cursor.getInt(10);
                offered = cursor.getInt(11);
            }
            mChangedReservedEditText.setText(String.valueOf(reserved));
            mChangedOfferedEditText.setText(String.valueOf(offered));

            // To take handling
            final int toTake = cursor.getInt(13);
            if (toTake != ProviderContract.NO_INFO) {
                mToTakeText.setText(String.valueOf(toTake));
                mToTakeGroup.setVisibility(View.VISIBLE);
            } else {
                mToTakeText.setText(R.string.menu_detail_not_available);
                mToTakeGroup.setVisibility(View.GONE);
            }

            // To order handling
            final int toOrder = cursor.getInt(14);
            if (toOrder != ProviderContract.NO_INFO) {
                mToOrderText.setText(String.valueOf(Math.max(0, toOrder - syncedOffered)));
                if ((features
                        & ProviderContract.FEATURE_MULTIPLE_ORDERS) == ProviderContract.FEATURE_MULTIPLE_ORDERS) {
                    mMaxReserved = mSyncedReserved + Math.max(0, toOrder - syncedOffered);
                } else {
                    mMaxReserved = Math.min(1, mSyncedReserved + Math.max(0, toOrder - syncedOffered));
                }
            } else {
                if ((status & ProviderContract.MENU_STATUS_ORDERABLE) == ProviderContract.MENU_STATUS_ORDERABLE) {
                    mToOrderText.setText(R.string.menu_detail_unlimited);
                    if ((features
                            & ProviderContract.FEATURE_MULTIPLE_ORDERS) == ProviderContract.FEATURE_MULTIPLE_ORDERS) {
                        mMaxReserved = Integer.MAX_VALUE;
                    } else {
                        mMaxReserved = 1;
                    }
                } else {
                    mToOrderText.setText("0");
                    mMaxReserved = mSyncedReserved;
                }
            }

            // Last change
            final long rawLastChange = cursor.getLong(15);
            if (rawLastChange != ProviderContract.NO_INFO) {
                mLastChangeText.setText(MenuUtils.getDateTimeStr(mLastChangeText.getContext(), rawLastChange));
            } else {
                mLastChangeText.setText(R.string.menu_detail_not_available);
            }

            // Reserved change button
            if (rawDate >= MenuUtils.getTodayDate()) {
                mChangedReservedEditText.setEnabled(mMinReserved != mMaxReserved);
            } else {
                mChangedReservedEditText.setEnabled(false);
            }

            // Offer change button
            if ((status & ProviderContract.FEATURE_FOOD_STOCK) == ProviderContract.FEATURE_FOOD_STOCK) {
                mChangedOfferedEditText.setEnabled(rawDate >= MenuUtils.getTodayDate() && 0 != mSyncedReserved);
                mOfferGroup.setVisibility(View.VISIBLE);
            } else {
                mChangedOfferedEditText.setEnabled(false);
                mOfferGroup.setVisibility(View.INVISIBLE);
            }

            // Label
            getActivity().setTitle(cursor.getString(16));

            break;
        }
        default:
            throw new UnsupportedOperationException("Unknown loader id: " + loader.getId());
        }
    }

    @Override
    public void onLoaderReset(@NonNull Loader<Cursor> loader) {
        switch (loader.getId()) {
        case MENU_LOADER_ID:
            Timber.e("Menu data with user %s is no longer valid", mUserUri);
            break;
        default:
            throw new UnsupportedOperationException("Unknown loader id: " + loader.getId());
        }
    }

    private void showPickerDialog(@StringRes int title, int min, int max, int value, IntConsumer newValueConsumer) {
        // Prepare number picker
        NumberPicker picker = new NumberPicker(getContext());
        picker.setMinValue(min);
        picker.setMaxValue(max);
        picker.setValue(value);
        picker.setWrapSelectorWheel(false);

        FrameLayout layout = new FrameLayout(getContext());
        layout.addView(picker, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
                FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER));

        // Show alert
        new AlertDialog.Builder(getContext()).setTitle(title).setMessage(R.string.menu_detail_pick_new_value)
                .setView(layout).setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
                    // do something with picker.getValue()
                    newValueConsumer.accept(picker.getValue());
                }).setNegativeButton(android.R.string.cancel, null).show();
    }

    public void saveChanges() {
        try {
            int changeReserved = Integer.parseInt(mChangedReservedEditText.getText().toString());
            int changeOffered = Integer.parseInt(mChangedOfferedEditText.getText().toString());

            AsyncTask.execute(() -> ActionUtils.makeEdit(getContext(), mUserUri, mMenuRelativeId, mPortalId,
                    changeReserved, changeOffered));
        } catch (NumberFormatException e) {
            Timber.w(e, "Cannot save changes");
        }
    }
}

/**
 * {@link java.util.function.IntConsumer} for older Androids
 */
interface IntConsumer {
    /**
     * {@link java.util.function.IntConsumer#accept(int)} for older Androids
     */
    void accept(int value);
}