1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.deskclock;
18
19import android.app.Dialog;
20import android.app.DialogFragment;
21import android.app.Fragment;
22import android.app.FragmentManager;
23import android.app.FragmentTransaction;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.res.ColorStateList;
27import android.graphics.Color;
28import android.os.Bundle;
29import android.support.annotation.NonNull;
30import android.support.v7.app.AlertDialog;
31import android.support.v7.widget.AppCompatEditText;
32import android.text.Editable;
33import android.text.InputType;
34import android.text.TextUtils;
35import android.text.TextWatcher;
36import android.view.KeyEvent;
37import android.view.Window;
38import android.view.inputmethod.EditorInfo;
39import android.widget.TextView;
40
41import com.android.deskclock.data.DataModel;
42import com.android.deskclock.data.Timer;
43import com.android.deskclock.provider.Alarm;
44
45import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
46
47/**
48 * DialogFragment to edit label.
49 */
50public class LabelDialogFragment extends DialogFragment {
51
52    /**
53     * The tag that identifies instances of LabelDialogFragment in the fragment manager.
54     */
55    private static final String TAG = "label_dialog";
56
57    private static final String ARG_LABEL = "arg_label";
58    private static final String ARG_ALARM = "arg_alarm";
59    private static final String ARG_TIMER_ID = "arg_timer_id";
60    private static final String ARG_TAG = "arg_tag";
61
62    private AppCompatEditText mLabelBox;
63    private Alarm mAlarm;
64    private int mTimerId;
65    private String mTag;
66
67    public static LabelDialogFragment newInstance(Alarm alarm, String label, String tag) {
68        final Bundle args = new Bundle();
69        args.putString(ARG_LABEL, label);
70        args.putParcelable(ARG_ALARM, alarm);
71        args.putString(ARG_TAG, tag);
72
73        final LabelDialogFragment frag = new LabelDialogFragment();
74        frag.setArguments(args);
75        return frag;
76    }
77
78    public static LabelDialogFragment newInstance(Timer timer) {
79        final Bundle args = new Bundle();
80        args.putString(ARG_LABEL, timer.getLabel());
81        args.putInt(ARG_TIMER_ID, timer.getId());
82
83        final LabelDialogFragment frag = new LabelDialogFragment();
84        frag.setArguments(args);
85        return frag;
86    }
87
88    /**
89     * Replaces any existing LabelDialogFragment with the given {@code fragment}.
90     */
91    public static void show(FragmentManager manager, LabelDialogFragment fragment) {
92        if (manager == null || manager.isDestroyed()) {
93            return;
94        }
95
96        // Finish any outstanding fragment work.
97        manager.executePendingTransactions();
98
99        final FragmentTransaction tx = manager.beginTransaction();
100
101        // Remove existing instance of LabelDialogFragment if necessary.
102        final Fragment existing = manager.findFragmentByTag(TAG);
103        if (existing != null) {
104            tx.remove(existing);
105        }
106        tx.addToBackStack(null);
107
108        fragment.show(tx, TAG);
109    }
110
111    @Override
112    public void onSaveInstanceState(@NonNull Bundle outState) {
113        super.onSaveInstanceState(outState);
114        // As long as the label box exists, save its state.
115        if (mLabelBox != null) {
116            outState.putString(ARG_LABEL, mLabelBox.getText().toString());
117        }
118    }
119
120    @Override
121    public Dialog onCreateDialog(Bundle savedInstanceState) {
122        final Bundle args = getArguments() == null ? Bundle.EMPTY : getArguments();
123        mAlarm = args.getParcelable(ARG_ALARM);
124        mTimerId = args.getInt(ARG_TIMER_ID, -1);
125        mTag = args.getString(ARG_TAG);
126
127        String label = args.getString(ARG_LABEL);
128        if (savedInstanceState != null) {
129            label = savedInstanceState.getString(ARG_LABEL, label);
130        }
131
132        final AlertDialog dialog = new AlertDialog.Builder(getActivity())
133                .setPositiveButton(android.R.string.ok, new OkListener())
134                .setNegativeButton(android.R.string.cancel, null /* listener */)
135                .setMessage(R.string.label)
136                .create();
137        final Context context = dialog.getContext();
138
139        final int colorControlActivated =
140                ThemeUtils.resolveColor(context, R.attr.colorControlActivated);
141        final int colorControlNormal =
142                ThemeUtils.resolveColor(context, R.attr.colorControlNormal);
143
144        mLabelBox = new AppCompatEditText(context);
145        mLabelBox.setSupportBackgroundTintList(new ColorStateList(
146                new int[][] { { android.R.attr.state_activated }, {} },
147                new int[] { colorControlActivated, colorControlNormal }));
148        mLabelBox.setOnEditorActionListener(new ImeDoneListener());
149        mLabelBox.addTextChangedListener(new TextChangeListener());
150        mLabelBox.setSingleLine();
151        mLabelBox.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
152        mLabelBox.setText(label);
153        mLabelBox.selectAll();
154
155        // The line at the bottom of EditText is part of its background therefore the padding
156        // must be added to its container.
157        final int padding = context.getResources()
158                .getDimensionPixelSize(R.dimen.label_edittext_padding);
159        dialog.setView(mLabelBox, padding, 0, padding, 0);
160
161        final Window alertDialogWindow = dialog.getWindow();
162        if (alertDialogWindow != null) {
163            alertDialogWindow.setSoftInputMode(SOFT_INPUT_STATE_VISIBLE);
164        }
165        return dialog;
166    }
167
168    @Override
169    public void onDestroyView() {
170        super.onDestroyView();
171
172        // Stop callbacks from the IME since there is no view to process them.
173        mLabelBox.setOnEditorActionListener(null);
174    }
175
176    /**
177     * Sets the new label into the timer or alarm.
178     */
179    private void setLabel() {
180        String label = mLabelBox.getText().toString();
181        if (label.trim().isEmpty()) {
182            // Don't allow user to input label with only whitespace.
183            label = "";
184        }
185
186        if (mAlarm != null) {
187            ((AlarmLabelDialogHandler) getActivity()).onDialogLabelSet(mAlarm, label, mTag);
188        } else if (mTimerId >= 0) {
189            final Timer timer = DataModel.getDataModel().getTimer(mTimerId);
190            if (timer != null) {
191                DataModel.getDataModel().setTimerLabel(timer, label);
192            }
193        }
194    }
195
196    public interface AlarmLabelDialogHandler {
197        void onDialogLabelSet(Alarm alarm, String label, String tag);
198    }
199
200    /**
201     * Alters the UI to indicate when input is valid or invalid.
202     */
203    private class TextChangeListener implements TextWatcher {
204        @Override
205        public void onTextChanged(CharSequence s, int start, int before, int count) {
206            mLabelBox.setActivated(!TextUtils.isEmpty(s));
207        }
208
209        @Override
210        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
211        }
212
213        @Override
214        public void afterTextChanged(Editable editable) {
215        }
216    }
217
218    /**
219     * Handles completing the label edit from the IME keyboard.
220     */
221    private class ImeDoneListener implements TextView.OnEditorActionListener {
222        @Override
223        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
224            if (actionId == EditorInfo.IME_ACTION_DONE) {
225                setLabel();
226                dismissAllowingStateLoss();
227                return true;
228            }
229            return false;
230        }
231    }
232
233    /**
234     * Handles completing the label edit from the Ok button of the dialog.
235     */
236    private class OkListener implements DialogInterface.OnClickListener {
237        @Override
238        public void onClick(DialogInterface dialog, int which) {
239            setLabel();
240            dismiss();
241        }
242    }
243}
244