com.finchuk.clock2.alarms.ui.ExpandedAlarmViewHolder.java Source code

Java tutorial

Introduction

Here is the source code for com.finchuk.clock2.alarms.ui.ExpandedAlarmViewHolder.java

Source

/*
 * Copyright 2017 Oleksandr Finchuk
 *
 * This file is part of ClockPlus.
 *
 * ClockPlus 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.
 *
 * ClockPlus 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 ClockPlus.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.finchuk.clock2.alarms.ui;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Vibrator;
import android.support.annotation.IdRes;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ToggleButton;

import com.finchuk.clock2.alarms.Alarm;
import com.finchuk.clock2.alarms.misc.AlarmController;
import com.finchuk.clock2.alarms.misc.DaysOfWeek;
import com.finchuk.clock2.dialogs.RingtonePickerDialog;
import com.finchuk.clock2.dialogs.RingtonePickerDialogController;
import com.finchuk.clock2.list.OnListItemInteractionListener;
import com.finchuk.clock2.timepickers.Utils;
import com.finchuk.clock2.util.FragmentTagUtils;

import butterknife.Bind;
import butterknife.OnClick;

/**
 * Created by Oleksandr Finchuk on 07/06/2017.
 */
public class ExpandedAlarmViewHolder extends BaseAlarmViewHolder {
    private static final String TAG = "ExpandedAlarmViewHolder";

    @Bind(com.finchuk.clock2.R.id.ok)
    Button mOk;
    @Bind(com.finchuk.clock2.R.id.delete)
    Button mDelete;
    @Bind(com.finchuk.clock2.R.id.ringtone)
    Button mRingtone;
    @Bind(com.finchuk.clock2.R.id.vibrate)
    TempCheckableImageButton mVibrate;
    @Bind({ com.finchuk.clock2.R.id.day0, com.finchuk.clock2.R.id.day1, com.finchuk.clock2.R.id.day2,
            com.finchuk.clock2.R.id.day3, com.finchuk.clock2.R.id.day4, com.finchuk.clock2.R.id.day5,
            com.finchuk.clock2.R.id.day6 })
    ToggleButton[] mDays;

    private final ColorStateList mDayToggleColors;
    private final ColorStateList mVibrateColors;
    private final RingtonePickerDialogController mRingtonePickerController;

    public ExpandedAlarmViewHolder(ViewGroup parent, final OnListItemInteractionListener<Alarm> listener,
            AlarmController controller) {
        super(parent, com.finchuk.clock2.R.layout.item_expanded_alarm, listener, controller);
        // Manually bind listeners, or else you'd need to write a getter for the
        // OnListItemInteractionListener in the BaseViewHolder for use in method binding.
        mDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onListItemDeleted(getAlarm());
            }
        });
        mOk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Since changes are persisted as soon as they are made, there's really
                // nothing we have to persist here. Let the listener know we should
                // collapse this VH.
                // While this works, it also makes an update to the DB and thus reschedules
                // the alarm, so the snackbar will show up as well. We want to avoid that..
                //                listener.onListItemUpdate(getAlarm(), getAdapterPosition());
                // TODO: This only works because we know what the implementation looks like..
                // This is bad because we just made the proper function of this dependent
                // on the implementation.
                listener.onListItemClick(getAlarm(), getAdapterPosition());
            }
        });

        // https://code.google.com/p/android/issues/detail?id=177282
        // https://stackoverflow.com/questions/15673449/is-it-confirmed-that-i-cannot-use-themed-color-attribute-in-color-state-list-res
        // Programmatically create the ColorStateList for our day toggles using themed color
        // attributes, "since prior to M you can't create a themed ColorStateList from XML but you
        // can from code." (quote from google)
        // The first array level is analogous to an XML node defining an item with a state list.
        // The second level lists all the states considered by the item from the first level.
        // An empty list of states represents the default stateless item.
        int[][] states = { /*item 1*/{ /*states*/android.R.attr.state_checked }, /*item 2*/{ /*states*/ } };
        // TODO: Phase out Utils.getColorFromThemeAttr because it doesn't work for text colors.
        // WHereas getTextColorFromThemeAttr works for both regular colors and text colors.
        int[] dayToggleColors = {
                /*item 1*/Utils.getTextColorFromThemeAttr(getContext(), com.finchuk.clock2.R.attr.colorAccent),
                /*item 2*/Utils.getTextColorFromThemeAttr(getContext(), android.R.attr.textColorHint) };
        int[] vibrateColors = {
                /*item 1*/Utils.getTextColorFromThemeAttr(getContext(), com.finchuk.clock2.R.attr.colorAccent),
                /*item 2*/Utils.getTextColorFromThemeAttr(getContext(), com.finchuk.clock2.R.attr.themedIconTint) };
        mDayToggleColors = new ColorStateList(states, dayToggleColors);
        mVibrateColors = new ColorStateList(states, vibrateColors);

        mRingtonePickerController = new RingtonePickerDialogController(mFragmentManager,
                new RingtonePickerDialog.OnRingtoneSelectedListener() {
                    @Override
                    public void onRingtoneSelected(Uri ringtoneUri) {
                        Log.d(TAG, "Selected ringtone: " + ringtoneUri.toString());
                        final Alarm oldAlarm = getAlarm();
                        Alarm newAlarm = oldAlarm.toBuilder().ringtone(ringtoneUri.toString()).build();
                        oldAlarm.copyMutableFieldsTo(newAlarm);
                        persistUpdatedAlarm(newAlarm, false);
                    }
                });
    }

    @Override
    public void onBind(Alarm alarm) {
        super.onBind(alarm);
        mRingtonePickerController.tryRestoreCallback(makeTag(com.finchuk.clock2.R.id.ringtone));
        bindDays(alarm);
        bindRingtone();
        bindVibrate(alarm.vibrates());
    }

    @Override
    protected void bindLabel(boolean visible, String label) {
        super.bindLabel(true, label);
    }

    @OnClick(com.finchuk.clock2.R.id.ok)
    void save() {
        // TODO
    }

    //    @OnClick(R.id.delete)
    //    void delete() {
    //        // TODO
    //    }

    @OnClick(com.finchuk.clock2.R.id.ringtone)
    void showRingtonePickerDialog() {
        //        Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
        //        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
        //                .putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
        //                // The ringtone to show as selected when the dialog is opened
        //                .putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, getSelectedRingtoneUri())
        //                // Whether to show "Default" item in the list
        //                .putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
        //        // The ringtone that plays when default option is selected
        //        //.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, DEFAULT_TONE);
        //        // TODO: This is VERY BAD. Use a Controller/Presenter instead.
        //        // The result will be delivered to MainActivity, and then delegated to AlarmsFragment.
        //        ((Activity) getContext()).startActivityForResult(intent, AlarmsFragment.REQUEST_PICK_RINGTONE);

        mRingtonePickerController.show(getSelectedRingtoneUri(), makeTag(com.finchuk.clock2.R.id.ringtone));
    }

    @OnClick(com.finchuk.clock2.R.id.vibrate)
    void onVibrateToggled() {
        final boolean checked = mVibrate.isChecked();
        if (checked) {
            Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
            vibrator.vibrate(300);
        }
        final Alarm oldAlarm = getAlarm();
        Alarm newAlarm = oldAlarm.toBuilder().vibrates(checked).build();
        oldAlarm.copyMutableFieldsTo(newAlarm);
        persistUpdatedAlarm(newAlarm, false);
    }

    @OnClick({ com.finchuk.clock2.R.id.day0, com.finchuk.clock2.R.id.day1, com.finchuk.clock2.R.id.day2,
            com.finchuk.clock2.R.id.day3, com.finchuk.clock2.R.id.day4, com.finchuk.clock2.R.id.day5,
            com.finchuk.clock2.R.id.day6 })
    void onDayToggled(ToggleButton view) {
        final Alarm oldAlarm = getAlarm();
        Alarm newAlarm = oldAlarm.toBuilder().build();
        oldAlarm.copyMutableFieldsTo(newAlarm);
        // ---------------------------------------------------------------------------------
        // TOneverDO: precede copyMutableFieldsTo()
        int position = ((ViewGroup) view.getParent()).indexOfChild(view);
        int weekDayAtPosition = DaysOfWeek.getInstance(getContext()).weekDayAt(position);
        Log.d(TAG, "Day toggle #" + position + " checked changed. This is weekday #" + weekDayAtPosition
                + " relative to a week starting on Sunday");
        newAlarm.setRecurring(weekDayAtPosition, view.isChecked());
        // ---------------------------------------------------------------------------------
        persistUpdatedAlarm(newAlarm, true);
    }

    private void bindDays(Alarm alarm) {
        for (int i = 0; i < mDays.length; i++) {
            mDays[i].setTextColor(mDayToggleColors);
            int weekDay = DaysOfWeek.getInstance(getContext()).weekDayAt(i);
            String label = DaysOfWeek.getLabel(weekDay);
            mDays[i].setTextOn(label);
            mDays[i].setTextOff(label);
            mDays[i].setChecked(alarm.isRecurring(weekDay));
        }
    }

    private void bindRingtone() {
        int iconTint = Utils.getTextColorFromThemeAttr(getContext(), com.finchuk.clock2.R.attr.themedIconTint);

        Drawable ringtoneIcon = mRingtone.getCompoundDrawablesRelative()[0/*start*/];
        ringtoneIcon = DrawableCompat.wrap(ringtoneIcon.mutate());
        DrawableCompat.setTint(ringtoneIcon, iconTint);
        mRingtone.setCompoundDrawablesRelativeWithIntrinsicBounds(ringtoneIcon, null, null, null);

        String title = RingtoneManager.getRingtone(getContext(), getSelectedRingtoneUri()).getTitle(getContext());
        mRingtone.setText(title);
    }

    private void bindVibrate(boolean vibrates) {
        Utils.setTintList(mVibrate, mVibrate.getDrawable(), mVibrateColors);
        mVibrate.setChecked(vibrates);
    }

    private Uri getSelectedRingtoneUri() {
        // If showing an item for "Default" (@see EXTRA_RINGTONE_SHOW_DEFAULT), this can be one
        // of DEFAULT_RINGTONE_URI, DEFAULT_NOTIFICATION_URI, or DEFAULT_ALARM_ALERT_URI to have the
        // "Default" item checked.
        //
        // Otherwise, use RingtoneManager.getActualDefaultRingtoneUri() to get the "actual sound URI".
        //
        // Do not use RingtoneManager.getDefaultUri(), because that just returns one of
        // DEFAULT_RINGTONE_URI, DEFAULT_NOTIFICATION_URI, or DEFAULT_ALARM_ALERT_URI
        // depending on the type requested (i.e. what the docs calls "symbolic URI
        // which will resolved to the actual sound when played").
        String ringtone = getAlarm().ringtone();
        return ringtone.isEmpty()
                ? RingtoneManager.getActualDefaultRingtoneUri(getContext(), RingtoneManager.TYPE_ALARM)
                : Uri.parse(ringtone);
    }

    private String makeTag(@IdRes int viewId) {
        return FragmentTagUtils.makeTag(ExpandedAlarmViewHolder.class, viewId, getItemId());
    }
}