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 17 package com.android.deskclock; 18 19 import android.app.Dialog; 20 import android.app.DialogFragment; 21 import android.app.Fragment; 22 import android.app.FragmentManager; 23 import android.app.FragmentTransaction; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.res.ColorStateList; 27 import android.graphics.Color; 28 import android.os.Bundle; 29 import androidx.annotation.NonNull; 30 import androidx.appcompat.app.AlertDialog; 31 import androidx.appcompat.widget.AppCompatEditText; 32 import android.text.Editable; 33 import android.text.InputType; 34 import android.text.TextUtils; 35 import android.text.TextWatcher; 36 import android.view.KeyEvent; 37 import android.view.Window; 38 import android.view.inputmethod.EditorInfo; 39 import android.widget.TextView; 40 41 import com.android.deskclock.data.DataModel; 42 import com.android.deskclock.data.Timer; 43 import com.android.deskclock.provider.Alarm; 44 45 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE; 46 47 /** 48 * DialogFragment to edit label. 49 */ 50 public 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 newInstance(Alarm alarm, String label, String tag)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 newInstance(Timer timer)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 */ show(FragmentManager manager, LabelDialogFragment fragment)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 onSaveInstanceState(@onNull Bundle outState)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 onCreateDialog(Bundle savedInstanceState)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 onDestroyView()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 */ setLabel()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 { onDialogLabelSet(Alarm alarm, String label, String tag)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 onTextChanged(CharSequence s, int start, int before, int count)205 public void onTextChanged(CharSequence s, int start, int before, int count) { 206 mLabelBox.setActivated(!TextUtils.isEmpty(s)); 207 } 208 209 @Override beforeTextChanged(CharSequence s, int start, int count, int after)210 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 211 } 212 213 @Override afterTextChanged(Editable editable)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 onEditorAction(TextView v, int actionId, KeyEvent event)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 onClick(DialogInterface dialog, int which)238 public void onClick(DialogInterface dialog, int which) { 239 setLabel(); 240 dismiss(); 241 } 242 } 243 } 244