1 /* 2 * Copyright (C) 2020 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.content.Context 21 import android.content.DialogInterface 22 import android.content.res.ColorStateList 23 import android.os.Bundle 24 import android.text.Editable 25 import android.text.InputType 26 import android.text.TextUtils 27 import android.text.TextWatcher 28 import android.view.KeyEvent 29 import android.view.Window 30 import android.view.WindowManager 31 import android.view.inputmethod.EditorInfo 32 import android.widget.TextView 33 import android.widget.TextView.OnEditorActionListener 34 import androidx.appcompat.app.AlertDialog 35 import androidx.appcompat.widget.AppCompatEditText 36 import androidx.fragment.app.DialogFragment 37 import androidx.fragment.app.FragmentManager 38 39 import com.android.deskclock.data.DataModel 40 import com.android.deskclock.data.Timer 41 import com.android.deskclock.provider.Alarm 42 43 /** 44 * DialogFragment to edit label. 45 */ 46 class LabelDialogFragment : DialogFragment() { 47 private var mLabelBox: AppCompatEditText? = null 48 private var mAlarm: Alarm? = null 49 private var mTimerId = 0 50 private var mTag: String? = null 51 onSaveInstanceStatenull52 override fun onSaveInstanceState(outState: Bundle) { 53 super.onSaveInstanceState(outState) 54 // As long as the label box exists, save its state. 55 mLabelBox?. let { 56 outState.putString(ARG_LABEL, it.getText().toString()) 57 } 58 } 59 onCreateDialognull60 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 61 val args = arguments ?: Bundle.EMPTY 62 mAlarm = args.getParcelable(ARG_ALARM) 63 mTimerId = args.getInt(ARG_TIMER_ID, -1) 64 mTag = args.getString(ARG_TAG) 65 66 var label = args.getString(ARG_LABEL) 67 savedInstanceState?.let { 68 label = it.getString(ARG_LABEL, label) 69 } 70 71 val dialog: AlertDialog = AlertDialog.Builder(requireActivity()) 72 .setPositiveButton(android.R.string.ok, OkListener()) 73 .setNegativeButton(android.R.string.cancel, null) 74 .setMessage(R.string.label) 75 .create() 76 val context: Context = dialog.context 77 78 val colorControlActivated = ThemeUtils.resolveColor(context, R.attr.colorControlActivated) 79 val colorControlNormal = ThemeUtils.resolveColor(context, R.attr.colorControlNormal) 80 81 mLabelBox = AppCompatEditText(context) 82 mLabelBox?.setSupportBackgroundTintList(ColorStateList( 83 arrayOf(intArrayOf(android.R.attr.state_activated), intArrayOf()), 84 intArrayOf(colorControlActivated, colorControlNormal))) 85 mLabelBox?.setOnEditorActionListener(ImeDoneListener()) 86 mLabelBox?.addTextChangedListener(TextChangeListener()) 87 mLabelBox?.setSingleLine() 88 mLabelBox?.setInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) 89 mLabelBox?.setText(label) 90 mLabelBox?.selectAll() 91 92 // The line at the bottom of EditText is part of its background therefore the padding 93 // must be added to its container. 94 val padding = context.resources 95 .getDimensionPixelSize(R.dimen.label_edittext_padding) 96 dialog.setView(mLabelBox, padding, 0, padding, 0) 97 98 val alertDialogWindow: Window? = dialog.window 99 alertDialogWindow?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) 100 return dialog 101 } 102 onDestroyViewnull103 override fun onDestroyView() { 104 super.onDestroyView() 105 106 // Stop callbacks from the IME since there is no view to process them. 107 mLabelBox?.setOnEditorActionListener(null) 108 } 109 110 /** 111 * Sets the new label into the timer or alarm. 112 */ setLabelnull113 private fun setLabel() { 114 var label: String = mLabelBox!!.getText().toString() 115 if (label.trim { it <= ' ' }.isEmpty()) { 116 // Don't allow user to input label with only whitespace. 117 label = "" 118 } 119 120 if (mAlarm != null) { 121 (activity as AlarmLabelDialogHandler).onDialogLabelSet(mAlarm!!, label, mTag!!) 122 } else if (mTimerId >= 0) { 123 val timer: Timer? = DataModel.dataModel.getTimer(mTimerId) 124 if (timer != null) { 125 DataModel.dataModel.setTimerLabel(timer, label) 126 } 127 } 128 } 129 130 interface AlarmLabelDialogHandler { onDialogLabelSetnull131 fun onDialogLabelSet(alarm: Alarm, label: String, tag: String) 132 } 133 134 /** 135 * Alters the UI to indicate when input is valid or invalid. 136 */ 137 private inner class TextChangeListener : TextWatcher { 138 override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { 139 mLabelBox?.setActivated(!TextUtils.isEmpty(s)) 140 } 141 142 override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { 143 } 144 145 override fun afterTextChanged(editable: Editable) { 146 } 147 } 148 149 /** 150 * Handles completing the label edit from the IME keyboard. 151 */ 152 private inner class ImeDoneListener : OnEditorActionListener { onEditorActionnull153 override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent): Boolean { 154 if (actionId == EditorInfo.IME_ACTION_DONE) { 155 setLabel() 156 dismissAllowingStateLoss() 157 return true 158 } 159 return false 160 } 161 } 162 163 /** 164 * Handles completing the label edit from the Ok button of the dialog. 165 */ 166 private inner class OkListener : DialogInterface.OnClickListener { onClicknull167 override fun onClick(dialog: DialogInterface, which: Int) { 168 setLabel() 169 dismiss() 170 } 171 } 172 173 companion object { 174 /** 175 * The tag that identifies instances of LabelDialogFragment in the fragment manager. 176 */ 177 private const val TAG = "label_dialog" 178 179 private const val ARG_LABEL = "arg_label" 180 private const val ARG_ALARM = "arg_alarm" 181 private const val ARG_TIMER_ID = "arg_timer_id" 182 private const val ARG_TAG = "arg_tag" 183 newInstancenull184 fun newInstance(alarm: Alarm, label: String?, tag: String?): LabelDialogFragment { 185 val args = Bundle() 186 args.putString(ARG_LABEL, label) 187 args.putParcelable(ARG_ALARM, alarm) 188 args.putString(ARG_TAG, tag) 189 190 val frag = LabelDialogFragment() 191 frag.arguments = args 192 return frag 193 } 194 195 @JvmStatic newInstancenull196 fun newInstance(timer: Timer): LabelDialogFragment { 197 val args = Bundle() 198 args.putString(ARG_LABEL, timer.label) 199 args.putInt(ARG_TIMER_ID, timer.id) 200 201 val frag = LabelDialogFragment() 202 frag.arguments = args 203 return frag 204 } 205 206 /** 207 * Replaces any existing LabelDialogFragment with the given `fragment`. 208 */ 209 @JvmStatic shownull210 fun show(manager: FragmentManager?, fragment: LabelDialogFragment) { 211 if (manager == null || manager.isDestroyed) { 212 return 213 } 214 215 // Finish any outstanding fragment work. 216 manager.executePendingTransactions() 217 218 val tx = manager.beginTransaction() 219 220 // Remove existing instance of LabelDialogFragment if necessary. 221 val existing = manager.findFragmentByTag(TAG) 222 existing?.let { 223 tx.remove(it) 224 } 225 tx.addToBackStack(null) 226 227 fragment.show(tx, TAG) 228 } 229 } 230 }