Java tutorial
/* * Copyright (C) 2015 Marten Gajda <marten@dmfs.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.granita.tasks; import org.dmfs.android.retentionmagic.SupportDialogFragment; import org.dmfs.android.retentionmagic.annotations.Parameter; import org.dmfs.android.retentionmagic.annotations.Retain; import com.granita.provider.tasks.TaskContract; import com.granita.provider.tasks.TaskContract.TaskLists; import com.granita.provider.tasks.TaskContract.Tasks; import com.granita.tasks.model.ContentSet; import com.granita.tasks.model.TaskFieldAdapters; import com.granita.tasks.utils.TasksListCursorSpinnerAdapter; import android.annotation.TargetApi; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.text.Editable; import android.text.TextWatcher; import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager.LayoutParams; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; /** * A quick add dialog. It allows the user to enter a new task without having to deal with the full blown editor interface. At present it support task with a * title only, but there is an option to fire up the full editor. * * @author Marten Gajda <marten@dmfs.org> */ public class QuickAddDialogFragment extends SupportDialogFragment implements OnEditorActionListener, LoaderManager.LoaderCallbacks<Cursor>, OnItemSelectedListener, OnClickListener, TextWatcher { /** * The minimal duration for the "Task completed" info to be visible */ private final static int COMPLETION_DELAY_BASE = 500; // ms /** * The maximum time to add for the first time the "Task completed" info is shown. */ private final static int COMPLETION_DELAY_MAX = 1500; // ms private final static String ARG_LIST_ID = "list_id"; private final static String ARG_CONTENT = "content"; public static final String LIST_LOADER_URI = "uri"; public static final String LIST_LOADER_FILTER = "filter"; public static final String LIST_LOADER_VISIBLE_LISTS_FILTER = TaskLists.SYNC_ENABLED + "=1"; /** * Projection into the task list. */ private final static String[] TASK_LIST_PROJECTION = new String[] { TaskContract.TaskListColumns._ID, TaskContract.TaskListColumns.LIST_NAME, TaskContract.TaskListSyncColumns.ACCOUNT_TYPE, TaskContract.TaskListSyncColumns.ACCOUNT_NAME, TaskContract.TaskListColumns.LIST_COLOR }; /** * This interface provides a convenient way to get column indices of {@link #TASK_LIST_PROJECTION} without any overhead. */ private interface TASK_LIST_PROJECTION_VALUES { public final static int id = 0; @SuppressWarnings("unused") public final static int list_name = 1; @SuppressWarnings("unused") public final static int account_type = 2; @SuppressWarnings("unused") public final static int account_name = 3; @SuppressWarnings("unused") public final static int list_color = 4; } public interface OnTextInputListener { void onTextInput(String inputText); } @Parameter(key = ARG_LIST_ID) private long mListId = -1; @Parameter(key = ARG_CONTENT) private ContentSet mInitialContent; @Retain(permanent = true, key = "quick_add_list_id", classNS = "") private long mSelectedListId = -1; @Retain private int mLastColor = Color.WHITE; @Retain(permanent = true, key = "quick_add_save_count", classNS = "") private int mSaveCounter = 0; private View mColorBackground; private Spinner mListSpinner; private EditText mEditText; private View mConfirmation; private View mContent; private View mSaveButton; private View mSaveAndNextButton; private TasksListCursorSpinnerAdapter mTaskListAdapter; private String mAuthority; /** * Create a {@link QuickAddDialogFragment} with the given title and initial text value. * * @param titleId * The resource id of the title. * @param initalText * The initial text in the input field. * @return A new {@link QuickAddDialogFragment}. */ public static QuickAddDialogFragment newInstance(long listId) { QuickAddDialogFragment fragment = new QuickAddDialogFragment(); Bundle args = new Bundle(); args.putLong(ARG_LIST_ID, listId); fragment.setArguments(args); return fragment; } /** * Create a {@link QuickAddDialogFragment} with the given title and initial text value. * * @param titleId * The resource id of the title. * @param initalText * The initial text in the input field. * @return A new {@link QuickAddDialogFragment}. */ public static QuickAddDialogFragment newInstance(ContentSet content) { QuickAddDialogFragment fragment = new QuickAddDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_CONTENT, content); args.putLong(ARG_LIST_ID, -1); fragment.setArguments(args); return fragment; } public QuickAddDialogFragment() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = super.onCreateDialog(savedInstanceState); // hide the actual dialog title, we have our own... dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); return dialog; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // create ContextThemeWrapper from the original Activity Context with the custom theme final Context contextThemeWrapperLight = new ContextThemeWrapper(getActivity(), R.style.ThemeOverlay_AppCompat_Light); final Context contextThemeWrapperDark = new ContextThemeWrapper(getActivity(), R.style.Base_Theme_AppCompat); LayoutInflater localInflater = inflater.cloneInContext(contextThemeWrapperLight); View view = localInflater.inflate(R.layout.fragment_quick_add_dialog, container); ViewGroup headerContainer = (ViewGroup) view.findViewById(R.id.header_container); localInflater = inflater.cloneInContext(contextThemeWrapperDark); localInflater.inflate(R.layout.fragment_quick_add_dialog_header, headerContainer); if (savedInstanceState == null) { if (mListId >= 0) { mSelectedListId = mListId; } } mColorBackground = view.findViewById(R.id.color_background); mColorBackground.setBackgroundColor(mLastColor); mListSpinner = (Spinner) view.findViewById(R.id.task_list_spinner); mTaskListAdapter = new TasksListCursorSpinnerAdapter(getActivity(), R.layout.list_spinner_item_selected_quick_add, R.layout.list_spinner_item_dropdown); mListSpinner.setAdapter(mTaskListAdapter); mListSpinner.setOnItemSelectedListener(this); mEditText = (EditText) view.findViewById(android.R.id.input); mEditText.requestFocus(); getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); mEditText.setOnEditorActionListener(this); mEditText.addTextChangedListener(this); mConfirmation = view.findViewById(R.id.created_confirmation); mContent = view.findViewById(R.id.content); mSaveButton = view.findViewById(android.R.id.button1); mSaveButton.setOnClickListener(this); mSaveAndNextButton = view.findViewById(android.R.id.button2); mSaveAndNextButton.setOnClickListener(this); view.findViewById(android.R.id.edit).setOnClickListener(this); mAuthority = getString(R.string.org_dmfs_tasks_authority); afterTextChanged(mEditText.getEditableText()); setListUri(TaskLists.getContentUri(mAuthority), LIST_LOADER_VISIBLE_LISTS_FILTER); return view; } @Override public Loader<Cursor> onCreateLoader(int id, Bundle bundle) { return new CursorLoader(getActivity(), (Uri) bundle.getParcelable(LIST_LOADER_URI), TASK_LIST_PROJECTION, bundle.getString(LIST_LOADER_FILTER), null, null); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { mTaskListAdapter.changeCursor(cursor); if (cursor != null) { if (mSelectedListId == -1) { mSelectedListId = mListId; } // set the list that was used the last time the user created an event // iterate over all lists and select the one that matches the given id cursor.moveToFirst(); while (!cursor.isAfterLast()) { long listId = cursor.getLong(TASK_LIST_PROJECTION_VALUES.id); if (listId == mSelectedListId) { mListSpinner.setSelection(cursor.getPosition()); break; } cursor.moveToNext(); } } } @Override public void onLoaderReset(Loader<Cursor> loader) { mTaskListAdapter.changeCursor(null); } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (EditorInfo.IME_ACTION_DONE == actionId) { notifyUser(true /* close afterwards */); createTask(); return true; } return false; } private void setListUri(Uri uri, String filter) { if (this.isAdded()) { Bundle bundle = new Bundle(); bundle.putParcelable(LIST_LOADER_URI, uri); bundle.putString(LIST_LOADER_FILTER, filter); getLoaderManager().restartLoader(-2, bundle, this); } } @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Cursor c = (Cursor) parent.getItemAtPosition(position); mLastColor = TaskFieldAdapters.LIST_COLOR.get(c); mColorBackground.setBackgroundColor(mLastColor); mSelectedListId = id; } @Override public void onNothingSelected(AdapterView<?> parent) { } /** * Launch the task editor activity. */ private void editTask() { Intent intent = new Intent(Intent.ACTION_INSERT); intent.setData(Tasks.getContentUri(mAuthority)); intent.putExtra(EditTaskActivity.EXTRA_DATA_CONTENT_SET, buildContentSet()); getActivity().startActivity(intent); } /** * Store the task. */ private void createTask() { ContentSet content = buildContentSet(); content.persist(getActivity()); } private ContentSet buildContentSet() { ContentSet task; if (mInitialContent != null) { task = new ContentSet(mInitialContent); } else { task = new ContentSet(Tasks.getContentUri(mAuthority)); } task.put(Tasks.LIST_ID, mListSpinner.getSelectedItemId()); TaskFieldAdapters.TITLE.set(task, mEditText.getText().toString()); return task; } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onClick(View v) { int id = v.getId(); mSaveButton.setEnabled(false); mSaveAndNextButton.setEnabled(false); if (id == android.R.id.button1) { // "save" pressed notifyUser(true /* close afterwards */); createTask(); } else if (id == android.R.id.button2) { // "save and continue" pressed notifyUser(false /* reset view */); createTask(); } else if (id == android.R.id.edit) { // "edit" pressed editTask(); dismiss(); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { // disable buttons if there is no title boolean enabled = s == null || s.length() != 0; mSaveButton.setEnabled(enabled); mSaveAndNextButton.setEnabled(enabled); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void notifyUser(boolean close) { if (VERSION.SDK_INT >= 14) { mContent.animate().alpha(0).setDuration(250).start(); mConfirmation.setAlpha(0); mConfirmation.setVisibility(View.VISIBLE); mConfirmation.animate().alpha(1).setDuration(250).start(); } else { mContent.setVisibility(View.INVISIBLE); mConfirmation.setVisibility(View.VISIBLE); } if (close) { mContent.postDelayed(mDismiss, 1000); } else { // We use a dynamic duration. When you hit "save & continue" for the very first time we use a rather long delay, that gets closer to // COMPLETION_DELAY_BASE with every time you do that. int duration = COMPLETION_DELAY_BASE + COMPLETION_DELAY_MAX / ++mSaveCounter; mContent.postDelayed(mReset, duration); } } /** * A runnable that closes the dialog. */ private final Runnable mDismiss = new Runnable() { @Override public void run() { dismiss(); } }; /** * A {@link Runnable} that resets the editor view. */ private final Runnable mReset = new Runnable() { @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void run() { if (VERSION.SDK_INT >= 14) { mContent.animate().alpha(1).setDuration(250).start(); mConfirmation.animate().alpha(0).setDuration(250).start(); } else { mContent.setVisibility(View.VISIBLE); mConfirmation.setVisibility(View.INVISIBLE); } mSaveButton.setEnabled(true); mSaveAndNextButton.setEnabled(true); // reset view mEditText.selectAll(); // bring the keyboard up again mEditText.requestFocus(); InputMethodManager inputMethodManager = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(mEditText, 0); } }; }