1 /*
2  * Copyright (C) 2008 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 
17 package com.android.deskclock.timer;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Color;
24 import android.text.format.DateUtils;
25 import android.util.AttributeSet;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.widget.Button;
29 import android.widget.ImageButton;
30 import android.widget.ImageView;
31 import android.widget.LinearLayout;
32 
33 import com.android.deskclock.AnimatorUtils;
34 import com.android.deskclock.R;
35 import com.android.deskclock.Utils;
36 
37 import java.io.Serializable;
38 import java.util.Arrays;
39 
40 public class TimerSetupView extends LinearLayout implements Button.OnClickListener,
41         Button.OnLongClickListener {
42 
43     private final Button[] mNumbers = new Button[10];
44     private final int[] mInput = new int[6];
45     private int mInputPointer = -1;
46     private ImageView mCreate;
47     private ImageButton mDelete;
48     private TimerView mEnteredTime;
49     private View mDivider;
50 
51     private final int mColorAccent;
52     private final int mColorHairline;
53 
54     private final AnimatorListenerAdapter mHideCreateListener = new AnimatorListenerAdapter() {
55         @Override
56         public void onAnimationEnd(Animator animation) {
57             mCreate.setScaleX(1);
58             mCreate.setScaleY(1);
59             mCreate.setVisibility(INVISIBLE);
60         }
61     };
62 
63     private final AnimatorListenerAdapter mShowCreateListener = new AnimatorListenerAdapter() {
64         @Override
65         public void onAnimationStart(Animator animation) {
66             mCreate.setVisibility(VISIBLE);
67         }
68     };
69 
TimerSetupView(Context context)70     public TimerSetupView(Context context) {
71         this(context, null /* attrs */);
72     }
73 
TimerSetupView(Context context, AttributeSet attrs)74     public TimerSetupView(Context context, AttributeSet attrs) {
75         super(context, attrs);
76 
77         mColorAccent = Utils.obtainStyledColor(context, R.attr.colorAccent, Color.RED);
78         mColorHairline = context.getResources().getColor(R.color.hairline);
79 
80         LayoutInflater.from(context).inflate(R.layout.time_setup_view, this);
81     }
82 
83     @Override
onFinishInflate()84     protected void onFinishInflate() {
85         super.onFinishInflate();
86 
87         final View v1 = findViewById(R.id.first);
88         final View v2 = findViewById(R.id.second);
89         final View v3 = findViewById(R.id.third);
90         final View v4 = findViewById(R.id.fourth);
91 
92         mDivider = findViewById(R.id.divider);
93         mCreate = (ImageView) findViewById(R.id.timer_create);
94         mDelete = (ImageButton) findViewById(R.id.delete);
95         mDelete.setOnClickListener(this);
96         mDelete.setOnLongClickListener(this);
97         mEnteredTime = (TimerView) findViewById(R.id.timer_time_text);
98 
99         mNumbers[1] = (Button) v1.findViewById(R.id.key_left);
100         mNumbers[2] = (Button) v1.findViewById(R.id.key_middle);
101         mNumbers[3] = (Button) v1.findViewById(R.id.key_right);
102 
103         mNumbers[4] = (Button) v2.findViewById(R.id.key_left);
104         mNumbers[5] = (Button) v2.findViewById(R.id.key_middle);
105         mNumbers[6] = (Button) v2.findViewById(R.id.key_right);
106 
107         mNumbers[7] = (Button) v3.findViewById(R.id.key_left);
108         mNumbers[8] = (Button) v3.findViewById(R.id.key_middle);
109         mNumbers[9] = (Button) v3.findViewById(R.id.key_right);
110 
111         mNumbers[0] = (Button) v4.findViewById(R.id.key_middle);
112         v4.findViewById(R.id.key_left).setVisibility(INVISIBLE);
113         v4.findViewById(R.id.key_right).setVisibility(INVISIBLE);
114 
115         for (int i = 0; i < mNumbers.length; i++) {
116             mNumbers[i].setOnClickListener(this);
117             mNumbers[i].setText(String.valueOf(i));
118             mNumbers[i].setTextColor(Color.WHITE);
119             mNumbers[i].setTag(R.id.numbers_key, i);
120         }
121 
122         reset();
123     }
124 
125     @Override
onClick(View v)126     public void onClick(View v) {
127         final Integer n = (Integer) v.getTag(R.id.numbers_key);
128         // A number was pressed
129         if (n != null) {
130             // pressing "0" as the first digit does nothing
131             if (mInputPointer == -1 && n == 0) {
132                 return;
133             }
134 
135             // No space for more digits, so ignore input.
136             if (mInputPointer == mInput.length - 1) {
137                 return;
138             }
139 
140             // Append the new digit.
141             System.arraycopy(mInput, 0, mInput, 1, mInputPointer + 1);
142             mInput[0] = n;
143             mInputPointer++;
144             updateTime();
145 
146             // Update talkback to read the number being deleted
147             final Resources resources = getResources();
148             final String cd = resources.getString(R.string.timer_descriptive_delete, n.toString());
149             mDelete.setContentDescription(cd);
150         }
151 
152         // other keys
153         if (v == mDelete) {
154             if (mInputPointer < 0) {
155                 // Nothing exists to delete so return.
156                 return;
157             }
158 
159             System.arraycopy(mInput, 1, mInput, 0, mInputPointer);
160             mInput[mInputPointer] = 0;
161             mInputPointer--;
162             updateTime();
163 
164             // Update talkback to read the number being deleted or its original description.
165             final String number = mInputPointer < 0 ? "" : Integer.toString(mInput[mInputPointer]);
166             final String cd = getResources().getString(R.string.timer_descriptive_delete, number);
167             mDelete.setContentDescription(cd);
168         }
169 
170         updateStartButton();
171         updateDeleteButtonAndDivider();
172     }
173 
174     @Override
175     public boolean onLongClick(View v) {
176         if (v == mDelete) {
177             reset();
178             updateStartButton();
179             return true;
180         }
181         return false;
182     }
183 
184     public void reset() {
185         for (int i = 0; i < mInput.length; i ++) {
186             mInput[i] = 0;
187         }
188         mInputPointer = -1;
189         updateTime();
190         updateDeleteButtonAndDivider();
191         mCreate.setVisibility(INVISIBLE);
192     }
193 
194     public long getTimeInMillis() {
195         final int hoursInSeconds = mInput[5] * 36000 + mInput[4] * 3600;
196         final int minutesInSeconds = mInput[3] * 600 + mInput[2] * 60;
197         final int seconds = mInput[1] * 10 + mInput[0];
198         final int totalSeconds = hoursInSeconds + minutesInSeconds + seconds;
199 
200         return totalSeconds * DateUtils.SECOND_IN_MILLIS;
201     }
202 
203     /**
204      * @return an opaque representation of the state of timer setup
205      */
206     public Serializable getState() {
207         return Arrays.copyOf(mInput, mInput.length);
208     }
209 
210     /**
211      * @param state an opaque state of this view previously produced by {@link #getState()}
212      */
213     public void setState(Serializable state) {
214         final int[] input = (int[]) state;
215         if (input != null && mInput.length == input.length) {
216             for (int i = 0; i < mInput.length; i++) {
217                 mInput[i] = input[i];
218                 if (mInput[i] != 0) {
219                     mInputPointer = i;
220                 }
221             }
222             updateTime();
223             updateDeleteButtonAndDivider();
224             mCreate.setVisibility(getInputExists() ? VISIBLE : INVISIBLE);
225         }
226     }
227 
228     private void updateTime() {
229         final int seconds = mInput[1] * 10 + mInput[0];
230         mEnteredTime.setTime(mInput[5], mInput[4], mInput[3], mInput[2], seconds);
231     }
232 
233     private void updateStartButton() {
234         final boolean show = getInputExists();
235         final int finalVisibility = show ? VISIBLE : INVISIBLE;
236         if (mCreate.getVisibility() == finalVisibility) {
237             // Fab is not initialized yet or already shown/hidden
238             return;
239         }
240 
241         final int from = show ? 0 : 1;
242         final int to = show ? 1 : 0;
243         final Animator scaleAnimator = AnimatorUtils.getScaleAnimator(mCreate, from, to);
244         scaleAnimator.setDuration(AnimatorUtils.ANIM_DURATION_SHORT);
245         scaleAnimator.addListener(show ? mShowCreateListener : mHideCreateListener);
246         scaleAnimator.start();
247     }
248 
249     private void updateDeleteButtonAndDivider() {
250         final boolean enabled = getInputExists();
251         mDelete.setEnabled(enabled);
252         mDivider.setBackgroundColor(enabled ? mColorAccent : mColorHairline);
253     }
254 
255     private boolean getInputExists() {
256         return mInputPointer != -1;
257     }
258 }