/* * Copyright 2017 Thomas Andres * * This file is part of Kandroid. * * Kandroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Kandroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Foobar. If not, see <http://www.gnu.org/licenses/>. */ package in.andres.kandroid.ui; import android.Manifest; import android.app.Activity; import android.app.DownloadManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.res.ResourcesCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.NestedScrollView; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.method.LinkMovementMethod; import android.util.Base64; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.OvershootInterpolator; import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; import org.commonmark.parser.Parser; import org.commonmark.renderer.NodeRenderer; import org.commonmark.renderer.html.HtmlNodeRendererContext; import org.commonmark.renderer.html.HtmlNodeRendererFactory; import org.commonmark.renderer.html.HtmlRenderer; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Locale; import in.andres.kandroid.BuildConfig; import in.andres.kandroid.CompactHtmlRenderer; import in.andres.kandroid.Constants; import in.andres.kandroid.DownloadIntentService; import in.andres.kandroid.R; import in.andres.kandroid.Utils; import in.andres.kandroid.kanboard.KanboardAPI; import in.andres.kandroid.kanboard.KanboardCategory; import in.andres.kandroid.kanboard.KanboardColumn; import in.andres.kandroid.kanboard.KanboardComment; import in.andres.kandroid.kanboard.KanboardRequest; import in.andres.kandroid.kanboard.KanboardSubtask; import in.andres.kandroid.kanboard.KanboardSwimlane; import in.andres.kandroid.kanboard.KanboardTask; import in.andres.kandroid.kanboard.KanboardTaskFile; import in.andres.kandroid.kanboard.KanboardUserInfo; import in.andres.kandroid.kanboard.events.OnCloseTaskListener; import in.andres.kandroid.kanboard.events.OnCreateCommentListener; import in.andres.kandroid.kanboard.events.OnCreateSubtaskListener; import in.andres.kandroid.kanboard.events.OnDownloadTaskFileListener; import in.andres.kandroid.kanboard.events.OnGetActiveSwimlanesListener; import in.andres.kandroid.kanboard.events.OnGetAllCommentsListener; import in.andres.kandroid.kanboard.events.OnGetAllSubtasksListener; import in.andres.kandroid.kanboard.events.OnGetAllTaskFilesListener; import in.andres.kandroid.kanboard.events.OnGetCategoryListener; import in.andres.kandroid.kanboard.events.OnGetColumnsListener; import in.andres.kandroid.kanboard.events.OnGetProjectUsersListener; import in.andres.kandroid.kanboard.events.OnGetSwimlaneListener; import in.andres.kandroid.kanboard.events.OnGetTaskListener; import in.andres.kandroid.kanboard.events.OnMoveTaskPositionListener; import in.andres.kandroid.kanboard.events.OnOpenTaskListener; import in.andres.kandroid.kanboard.events.OnRemoveCommentListener; import in.andres.kandroid.kanboard.events.OnRemoveSubtaskListener; import in.andres.kandroid.kanboard.events.OnRemoveTaskFileListener; import in.andres.kandroid.kanboard.events.OnRemoveTaskListener; import in.andres.kandroid.kanboard.events.OnSubtaskTimetrackingListener; import in.andres.kandroid.kanboard.events.OnUpdateCommentListener; import in.andres.kandroid.kanboard.events.OnUpdateSubtaskListener; public class TaskDetailActivity extends AppCompatActivity { private KanboardTask task; private KanboardCategory category; private KanboardSwimlane swimlane; private KanboardColumn column; private KanboardUserInfo me; private List<KanboardComment> comments = new ArrayList<>(); private List<KanboardSubtask> subtasks = new ArrayList<>(); private List<KanboardTaskFile> files = new ArrayList<>(); private List<KanboardColumn> projectColumns; private List<KanboardSwimlane> projectSwimlanes; private Dictionary<Integer, String> users; private Hashtable<Integer, Double> hasTimer = new Hashtable<>(); private int downloadFileId = -1; private String downloadFileName = null; // private HashSet<Integer> hasTimer = new HashSet<>(); private MenuItem refreshAction; private MenuItem opencloseAction; private int activeRequests = 0; private boolean progressVisible = false; private Context self; private Parser mParser = Parser.builder().build(); private HtmlRenderer mRenderer = HtmlRenderer.builder().nodeRendererFactory(new HtmlNodeRendererFactory() { @Override public NodeRenderer create(HtmlNodeRendererContext context) { return new CompactHtmlRenderer(context); } }).build(); private KanboardAPI kanboardAPI; //region Event Listeners private OnGetAllCommentsListener commentsListener = new OnGetAllCommentsListener() { @Override public void onGetAllComments(boolean success, List<KanboardComment> result) { hideProgress(); hideCommentProgress(); if (success && result.size() >= 0) { comments = result; commentListview.setAdapter(new CommentAdapter (getBaseContext(), comments, true)); // findViewById(R.id.card_comments).setVisibility(View.VISIBLE); } else { // findViewById(R.id.card_comments).setVisibility(View.GONE); } } }; private OnGetTaskListener taskListener = new OnGetTaskListener() { @Override public void onGetTask(boolean success, KanboardTask result) { if (success) { task = result; if (task.getCategoryId() > 0) { showProgress(); kanboardAPI.getCategory(task.getCategoryId()); } if (task.getSwimlaneId() == 0) { showProgress(); kanboardAPI.getDefaultSwimlane(task.getProjectId()); } else { showProgress(); kanboardAPI.getSwimlane(task.getSwimlaneId()); } hideProgress(); Log.d("Hide Progress", "OnGetTaskListener"); setTaskDetails(); } } }; private OnRemoveTaskListener removeTaskListener = new OnRemoveTaskListener() { @Override public void onRemoveTask(boolean success) { if (success) { setResult(Constants.ResultChanged); finish(); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_task), Snackbar.LENGTH_LONG).show(); } }; private OnGetProjectUsersListener usersListener = new OnGetProjectUsersListener() { @Override public void onGetProjectUsers(boolean success, Dictionary<Integer, String> result) { hideProgress(); if (success) { users = result; setTaskDetails(); // textOwner.setText(Utils.fromHtml(getString(R.string.taskview_owner, result.get(task.getOwnerId())))); // textCreator.setText(Utils.fromHtml(getString(R.string.taskview_creator, result.get(task.getCreatorId())))); //Send change notification to update usernames in comments if (commentListview.getAdapter() != null) ((CommentAdapter) commentListview.getAdapter()).notifyDataSetChanged(); if (filesListview.getAdapter() != null) ((TaskFilesAdapter) filesListview.getAdapter()).notifyDataSetChanged(); } } }; private OnGetCategoryListener categoryListener = new OnGetCategoryListener() { @Override public void onGetCategory(boolean success, KanboardCategory result) { hideProgress(); if (success) { category = result; setCategoryDetails(); } } }; private OnGetSwimlaneListener swimlaneListener = new OnGetSwimlaneListener() { @Override public void onGetSwimlane(boolean success, KanboardSwimlane result) { hideProgress(); if (success) { swimlane = result; setSwimlaneDetails(swimlane.getName()); } } }; private OnGetAllSubtasksListener allSubtasksListener = new OnGetAllSubtasksListener() { @Override public void onGetAllSubtasks(boolean success, List<KanboardSubtask> result) { hideProgress(); hideSubtaskProgress(); if (success && result.size() > 0) { subtasks = result; subtaskListview.setAdapter(new SubtaskAdapter(getBaseContext(), subtasks, true)); // findViewById(R.id.card_subtasks).setVisibility(View.VISIBLE); for (final KanboardSubtask sub: subtasks) { kanboardAPI.hasSubtaskTimer(sub.getId(), me.getId(), new OnSubtaskTimetrackingListener() { @Override public void onSubtaskTimetracking(boolean result, double time) { if (result && !hasTimer.containsKey(sub.getId())) { hasTimer.put(sub.getId(), 0d); ((SubtaskAdapter) subtaskListview.getAdapter()).notifyDataSetChanged(); kanboardAPI.getSubtaskTimeSpent(sub.getId(), me.getId(), new OnSubtaskTimetrackingListener() { @Override public void onSubtaskTimetracking(boolean result, double time) { hasTimer.put(sub.getId(), time); ((SubtaskAdapter) subtaskListview.getAdapter()).notifyDataSetChanged(); } }); } else if (!result && hasTimer.containsKey(sub.getId())){ hasTimer.remove(sub.getId()); ((SubtaskAdapter) subtaskListview.getAdapter()).notifyDataSetChanged(); } } }); } } else { // findViewById(R.id.card_subtasks).setVisibility(View.GONE); } } }; private OnCreateCommentListener createCommentListener = new OnCreateCommentListener() { @Override public void onCreateComment(boolean success, Integer commentid) { if (success) { showProgress(); kanboardAPI.getAllComments(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_create_comment), Snackbar.LENGTH_LONG).show(); } }; private OnUpdateCommentListener updateCommentListener = new OnUpdateCommentListener() { @Override public void onUpdateComment(boolean success) { if (success) { showProgress(); kanboardAPI.getAllComments(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_update_comment), Snackbar.LENGTH_LONG).show(); } }; private OnRemoveCommentListener removeCommentListener = new OnRemoveCommentListener() { @Override public void onRemoveComment(boolean success) { if (success) { showProgress(); kanboardAPI.getAllComments(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_comment), Snackbar.LENGTH_LONG).show(); } }; private OnCreateSubtaskListener createSubtaskListener = new OnCreateSubtaskListener() { @Override public void onCreateSubtask(boolean success, Integer result) { if (success) { showProgress(); kanboardAPI.getAllSubtasks(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_create_subtask), Snackbar.LENGTH_LONG).show(); } }; private OnUpdateSubtaskListener updateSubtaskListener = new OnUpdateSubtaskListener() { @Override public void onUpdateSubtask(boolean success) { if (success) { showProgress(); kanboardAPI.getAllSubtasks(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_update_subtask), Snackbar.LENGTH_LONG).show(); } }; private OnRemoveSubtaskListener removeSubtaskListener = new OnRemoveSubtaskListener() { @Override public void onRemoveSubtask(boolean success) { if (success) { showProgress(); kanboardAPI.getAllSubtasks(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_subtask), Snackbar.LENGTH_LONG).show(); } }; private OnOpenTaskListener openTaskListener = new OnOpenTaskListener() { @Override public void onOpenTask(boolean success) { if (success) { textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_open)))); fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_close, null)); fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_close_task)); if (opencloseAction != null) { opencloseAction.setTitle(R.string.taskview_fab_close_task); } showProgress(); kanboardAPI.getTask(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_open_task), Snackbar.LENGTH_LONG).show(); } }; private OnCloseTaskListener closeTaskListener = new OnCloseTaskListener() { @Override public void onCloseTask(boolean success) { if (success) { textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_closed)))); fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_open, null)); fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_open_task)); if (opencloseAction != null) { opencloseAction.setTitle(R.string.taskview_fab_open_task); } showProgress(); kanboardAPI.getTask(task.getId()); } else Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_close_task), Snackbar.LENGTH_LONG).show(); } }; private OnGetAllTaskFilesListener getAllTaskFilesListener = new OnGetAllTaskFilesListener() { @Override public void onGetAllTaskFiles(boolean success, List<KanboardTaskFile> result) { hideProgress(); hideFilesProgress(); if (success && result.size() > 0) { // findViewById(R.id.card_files).setVisibility(View.VISIBLE); files = result; filesListview.setAdapter(new TaskFilesAdapter(getBaseContext(), files)); } else { // findViewById(R.id.card_files).setVisibility(View.GONE); } } }; private OnRemoveTaskFileListener removeTaskFileListener = new OnRemoveTaskFileListener() { @Override public void onRemoveTaskFile(boolean success) { if (success) { showProgress(); kanboardAPI.getAllTaskFiles(task.getId()); } else { Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_msg_remove_subtask), Snackbar.LENGTH_LONG).show(); } } }; private OnDownloadTaskFileListener downloadTaskFileListener = new OnDownloadTaskFileListener() { @Override public void onDownloadTaskFile(boolean success, int id, String data) { if (success) { byte[] inData = Base64.decode(data, Base64.DEFAULT); for (KanboardTaskFile f: files) { if (f.getId() == id) { try { File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), f.getName()); FileOutputStream outData = new FileOutputStream(file); outData.write(inData); outData.close(); String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString())); if (mime == null) { mime = "application/octet-stream"; } if (BuildConfig.DEBUG) { Log.d(Constants.TAG, Uri.fromFile(file).toString()); Log.d(Constants.TAG, mime); } DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); dm.addCompletedDownload(file.getName(), "Kandroid download", false, mime, file.getPath(), file.length(), true); // Snackbar.make(findViewById(R.id.root_layout), String.format(Locale.getDefault(), "Saved file to: %s", file.getPath()), Snackbar.LENGTH_LONG).show(); } catch (IOException e) { Log.w(Constants.TAG, "IOError writing file"); e.printStackTrace(); } break; } } } else { Snackbar.make(findViewById(R.id.root_layout), "Unable to download file", Snackbar.LENGTH_LONG).show(); } } }; private OnGetColumnsListener getColumnsListener = new OnGetColumnsListener() { @Override public void onGetColumns(boolean success, List<KanboardColumn> result) { hideProgress(); if (success) { projectColumns = result; } else { Snackbar.make(findViewById(R.id.root_layout), "Error while receiving project columns", Snackbar.LENGTH_LONG).show(); } } }; private OnMoveTaskPositionListener moveTaskPositionListener = new OnMoveTaskPositionListener() { @Override public void onMoveTaskPosition(boolean success) { if (success) refresh(); else Snackbar.make(findViewById(R.id.root_layout), "Error while moving task", Snackbar.LENGTH_LONG).show(); } }; private OnGetActiveSwimlanesListener getActiveSwimlanesListener = new OnGetActiveSwimlanesListener() { @Override public void onGetActiveSwimlanes(boolean success, List<KanboardSwimlane> result) { hideProgress(); if (success) { projectSwimlanes = result; } } }; //endregion private TextView textCategory; private TextView textStatus; private TextView textPosition; private TextView textPriority; private TextView textSwimlane; private TextView textOwner; private TextView textCreator; private TextView textDateCreated; private TextView textDateModified; private TextView textDateMoved; private TextView textHoursEstimated; private TextView textHoursUsed; private TextView textDateStart; private TextView textDateDue; private TextView textDescription; private ListView commentListview; private ListView subtaskListview; private ListView filesListview; private FloatingActionButton fabMenu; private FloatingActionButton fabMenuButtonRemoveTask; private FloatingActionButton fabMenuButtonOpenCloseTask; private FloatingActionButton fabMenuButtonNewComment; private FloatingActionButton fabMenuButtonNewSubtask; private FloatingActionButton fabMenuButtonEditTask; private TextView fabMenuLabelOpenCloseTask; private Animation fabCloseAnimation; private Animation fabOpenAnimation; private boolean isFABMenuOpen = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_task_detail); self = this; findViewById(R.id.card_description).setVisibility(View.GONE); // findViewById(R.id.card_subtasks).setVisibility(View.GONE); // findViewById(R.id.card_comments).setVisibility(View.GONE); // findViewById(R.id.card_files).setVisibility(View.GONE); textCategory = (TextView) findViewById(R.id.text_category); textStatus = (TextView) findViewById(R.id.text_status); textPosition = (TextView) findViewById(R.id.text_position); textPriority = (TextView) findViewById(R.id.text_priority); textSwimlane = (TextView) findViewById(R.id.text_swimlane); textOwner = (TextView) findViewById(R.id.text_owner); textCreator = (TextView) findViewById(R.id.text_creator); textDateCreated = (TextView) findViewById(R.id.text_DateCreated); textDateModified = (TextView) findViewById(R.id.text_DateModified); textDateMoved = (TextView) findViewById(R.id.text_DateMoved); textHoursEstimated = (TextView) findViewById(R.id.text_HoursEstimated); textHoursUsed = (TextView) findViewById(R.id.text_HoursUsed); textDateStart = (TextView) findViewById(R.id.text_DateStart); textDateDue = (TextView) findViewById(R.id.text_DateDue); textDescription = (TextView) findViewById(R.id.text_Description); textDescription.setMovementMethod(LinkMovementMethod.getInstance()); commentListview = (ListView) findViewById(R.id.comment_listview); commentListview.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (position >= comments.size() && !progressVisible) { showCommentDialog(null); } } }); registerForContextMenu(commentListview); subtaskListview = (ListView) findViewById(R.id.subtask_listview); subtaskListview.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (position >= subtasks.size() && !progressVisible) { showSubtaskDialog(null); } } }); registerForContextMenu(subtaskListview); filesListview = (ListView) findViewById(R.id.files_listview); registerForContextMenu(filesListview); showCommentProgress(); showSubtaskProgress(); showFilesProgress(); fabMenu = (FloatingActionButton) findViewById(R.id.fab); fabMenuButtonRemoveTask = (FloatingActionButton) findViewById(R.id.fab_menu_button_remove_task); fabMenuButtonOpenCloseTask = (FloatingActionButton) findViewById(R.id.fab_menu_button_open_close_task); fabMenuButtonNewComment = (FloatingActionButton) findViewById(R.id.fab_menu_button_new_comment); fabMenuButtonNewSubtask = (FloatingActionButton) findViewById(R.id.fab_menu_button_new_subtask); fabMenuButtonEditTask = (FloatingActionButton) findViewById(R.id.fab_menu_button_edit_task_task); fabMenuLabelOpenCloseTask = (TextView) findViewById(R.id.fab_menu_label_open_close_task); fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close); fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open); fabMenu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (findViewById(R.id.fab).getVisibility() == View.VISIBLE) { if (isFABMenuOpen) collapseFABMenu(); else expandFABMenu(); } } }); ((NestedScrollView) findViewById(R.id.activity_task_detail)).setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { @Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (oldScrollY - scrollY > 0) ((FloatingActionButton) findViewById(R.id.fab)).show(); else if (oldScrollY - scrollY < 0) { if (isFABMenuOpen) collapseFABMenu(); ((FloatingActionButton) findViewById(R.id.fab)).hide(); ViewCompat.setRotation(fabMenu, 0.0F); } } }); fabMenuButtonNewComment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { collapseFABMenu(); showCommentDialog(null); } }); fabMenuButtonNewSubtask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { collapseFABMenu(); showSubtaskDialog(null); } }); fabMenuButtonOpenCloseTask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { collapseFABMenu(); setResult(Constants.ResultChanged); if (task.getIsActive()) { kanboardAPI.closeTask(task.getId()); } else { kanboardAPI.openTask(task.getId()); } } }); fabMenuButtonEditTask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { collapseFABMenu(); Intent intent = new Intent(getBaseContext(), TaskEditActivity.class); intent.putExtra("task", task); intent.putExtra("projectusers", (Hashtable<Integer, String>)users); startActivityForResult(intent, Constants.RequestEditTask); } }); fabMenuButtonRemoveTask.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { collapseFABMenu(); showDeleteTaskDialog(task); } }); commentListview.setAdapter(new CommentAdapter (getBaseContext(), comments, true)); subtaskListview.setAdapter(new SubtaskAdapter(getBaseContext(), subtasks, true)); filesListview.setAdapter((new TaskFilesAdapter(getBaseContext(), files))); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this.getBaseContext()); try { kanboardAPI = new KanboardAPI(preferences.getString("serverurl", ""), preferences.getString("username", ""), preferences.getString("password", "")); kanboardAPI.addOnGetAllCommentsListener(commentsListener); kanboardAPI.addOnGetTaskListener(taskListener); kanboardAPI.addOnRemoveTaskListener(removeTaskListener); kanboardAPI.addOnGetProjectUsersListener(usersListener); kanboardAPI.addOnGetCategoryListener(categoryListener); kanboardAPI.addOnGetSwimlaneListener(swimlaneListener); kanboardAPI.addOnGetDefaultSwimlaneListener(swimlaneListener); kanboardAPI.addOnGetAllSubtasksListener(allSubtasksListener); kanboardAPI.addOnCreateCommentListener(createCommentListener); kanboardAPI.addOnUpdateCommentListener(updateCommentListener); kanboardAPI.addOnRemoveCommentListener(removeCommentListener); kanboardAPI.addOnCreateSubtaskListener(createSubtaskListener); kanboardAPI.addOnUpdateSubtaskListener(updateSubtaskListener); kanboardAPI.addOnRemoveSubtaskListener(removeSubtaskListener); kanboardAPI.addOnOpenTaskListener(openTaskListener); kanboardAPI.addOnCloseTaskListener(closeTaskListener); kanboardAPI.addOnGetAllTaskFilesListListeners(getAllTaskFilesListener); kanboardAPI.addOnRemoveTaskFileListeners(removeTaskFileListener); kanboardAPI.addOnDownloadTaskFileListeners(downloadTaskFileListener); kanboardAPI.addOnGetColumnsListener(getColumnsListener); kanboardAPI.addOnMoveTaskPositionListener(moveTaskPositionListener); kanboardAPI.addOnGetActiveSwimlanesListener(getActiveSwimlanesListener); } catch (IOException e) { e.printStackTrace(); } if (savedInstanceState != null) { restoreSavedState(savedInstanceState); } else { me = (KanboardUserInfo) getIntent().getSerializableExtra("me"); task = (KanboardTask) getIntent().getSerializableExtra("task"); if (getIntent().hasExtra("column")) { column = (KanboardColumn) getIntent().getSerializableExtra("column"); } if (getIntent().hasExtra("swimlane")) { swimlane = (KanboardSwimlane) getIntent().getSerializableExtra("swimlane"); setSwimlaneDetails(swimlane.getName()); } else { textSwimlane.setVisibility(View.INVISIBLE); } if (getIntent().hasExtra("category")) { category = (KanboardCategory) getIntent().getSerializableExtra("category"); setCategoryDetails(); } else { textCategory.setText(Utils.fromHtml(getString(R.string.taskview_category, getString(R.string.task_not_assigned)))); } setTaskDetails(); refresh(); } setupActionBar(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == Constants.RequestEditTask && resultCode == Constants.ResultChanged) { setResult(Constants.ResultChanged); refresh(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("task", task); outState.putSerializable("category", category); outState.putSerializable("swimlane", swimlane); outState.putSerializable("me", me); outState.putSerializable("users", (Hashtable<Integer, String>) users); outState.putSerializable("comments", (ArrayList<KanboardComment>) comments); outState.putSerializable("subtasks", (ArrayList<KanboardSubtask>) subtasks); outState.putSerializable("files", (ArrayList<KanboardTaskFile>) files); outState.putSerializable("projectColumns", (ArrayList<KanboardColumn>) projectColumns); outState.putSerializable("projectSwimlanes", (ArrayList<KanboardSwimlane>) projectSwimlanes); if (BuildConfig.DEBUG) Log.d(Constants.TAG, "TaskDetailActivity: saved savedInstanceState"); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); restoreSavedState(savedInstanceState); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); if (v.getId() == R.id.comment_listview) { if (((AdapterView.AdapterContextMenuInfo)menuInfo).position < comments.size()) { menu.setHeaderTitle(getString(R.string.menu_caption_comment, ((KanboardComment) commentListview.getAdapter().getItem(((AdapterView.AdapterContextMenuInfo) menuInfo).position)).getId() )); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_taskdetail_context_comment, menu); } } if (v.getId() == R.id.subtask_listview) { menu.setHeaderTitle(getString(R.string.menu_caption_subtask, ((KanboardSubtask)subtaskListview.getAdapter().getItem(((AdapterView.AdapterContextMenuInfo)menuInfo).position)).getId() )); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_taskdetail_context_subtask, menu); } if (v.getId() == R.id.files_listview) { menu.setHeaderTitle(getString(R.string.menu_caption_file, ((KanboardTaskFile)filesListview.getAdapter().getItem(((AdapterView.AdapterContextMenuInfo)menuInfo).position)).getId() )); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_taskdetail_context_files, menu); } } @Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); switch (item.getItemId()) { case R.id.action_edit_comment: showCommentDialog((KanboardComment)commentListview.getAdapter().getItem(info.position)); return true; case R.id.action_delete_comment: showDeleteCommentDialog((KanboardComment)commentListview.getAdapter().getItem(info.position)); return true; case R.id.action_edit_subtask: showSubtaskDialog((KanboardSubtask)subtaskListview.getAdapter().getItem(info.position)); return super.onContextItemSelected(item); case R.id.action_delete_subtask: showDeleteSubtaskDialog((KanboardSubtask)subtaskListview.getAdapter().getItem(info.position)); return true; case R.id.action_download_file: String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { downloadFileId = ((KanboardTaskFile) filesListview.getAdapter().getItem(info.position)).getId(); downloadFileName = ((KanboardTaskFile) filesListview.getAdapter().getItem(info.position)).getName(); if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { Intent downloadIntent = new Intent(this, DownloadIntentService.class); downloadIntent.putExtra("request", KanboardRequest.downloadTaskFile(downloadFileId).JSON[0]); downloadIntent.putExtra("filename", downloadFileName); startService(downloadIntent); } else { ActivityCompat.requestPermissions((Activity) self, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Constants.RequestStoragePermission); } } else { Snackbar.make(findViewById(R.id.root_layout), getString(R.string.error_no_sdcard), Snackbar.LENGTH_LONG).show(); } return true; case R.id.action_delete_file: showDeleteTaskFileDialog((KanboardTaskFile) filesListview.getAdapter().getItem(info.position)); return true; default: return super.onContextItemSelected(item); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case Constants.RequestStoragePermission: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (downloadFileId != -1) { Intent downloadIntent = new Intent(this, DownloadIntentService.class); downloadIntent.putExtra("request", KanboardRequest.downloadTaskFile(downloadFileId).JSON[0]); downloadIntent.putExtra("filename", downloadFileName); startService(downloadIntent); downloadFileId = -1; downloadFileName = null; } else { Snackbar.make(findViewById(R.id.root_layout), getString(R.string.permission_storage_granted), Snackbar.LENGTH_LONG).show(); } } else { Snackbar.make(findViewById(R.id.root_layout), getString(R.string.permission_storage_denied), Snackbar.LENGTH_LONG).show(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_taskdetail_main, menu); refreshAction = menu.findItem(R.id.action_refresh); opencloseAction = menu.findItem(R.id.action_close_task); if (task.getIsActive()) { opencloseAction.setTitle(R.string.taskview_fab_close_task); } else { opencloseAction.setTitle(R.string.taskview_fab_open_task); } if (activeRequests > 0 && refreshAction != null) { ProgressBar prog = new ProgressBar(self); prog.getIndeterminateDrawable().setColorFilter(Color.WHITE, android.graphics.PorterDuff.Mode.MULTIPLY); refreshAction.setActionView(prog); refreshAction.expandActionView(); progressVisible = true; } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case android.R.id.home: onBackPressed(); return true; case R.id.action_refresh: refresh(); return true; case R.id.action_new_comment: if (progressVisible) Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show(); else showCommentDialog(null); return true; case R.id.action_new_subtask: if (progressVisible) Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show(); else showSubtaskDialog(null); return true; case R.id.action_edit_task: Intent intent = new Intent(getBaseContext(), TaskEditActivity.class); intent.putExtra("task", task); intent.putExtra("projectusers", (Hashtable<Integer, String>)users); startActivityForResult(intent, Constants.RequestEditTask); return true; case R.id.action_change_column: if (progressVisible) Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show(); else showChangeColumnDialog(); return true; case R.id.action_change_swimlane: if (progressVisible) Toast.makeText(this, getString(R.string.toast_wait_loading), Toast.LENGTH_SHORT).show(); else showChangeSwimlaneDialog(); return true; case R.id.action_close_task: setResult(Constants.ResultChanged); if (task.getIsActive()) { kanboardAPI.closeTask(task.getId()); } else { kanboardAPI.openTask(task.getId()); } return true; case R.id.action_delete_task: showDeleteTaskDialog(task); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onBackPressed() { if (isFABMenuOpen) collapseFABMenu(); else super.onBackPressed(); } private void restoreSavedState(Bundle savedInstanceState) { if (BuildConfig.DEBUG) Log.v(Constants.TAG, "TaskDetailActivity: restore savedInstanceState."); task = (KanboardTask) savedInstanceState.getSerializable("task"); category = (KanboardCategory) savedInstanceState.getSerializable("category"); swimlane = (KanboardSwimlane) savedInstanceState.getSerializable("swimlane"); me = (KanboardUserInfo) savedInstanceState.getSerializable("me"); Object ou = savedInstanceState.getSerializable("users"); if (ou instanceof HashMap) users = new Hashtable<>((HashMap<Integer, String>) ou); else users = (Hashtable<Integer, String>) ou; comments = (ArrayList<KanboardComment>) savedInstanceState.getSerializable("comments"); subtasks = (ArrayList<KanboardSubtask>) savedInstanceState.getSerializable("subtasks"); files = (ArrayList<KanboardTaskFile>) savedInstanceState.getSerializable("files"); projectColumns = (ArrayList<KanboardColumn>) savedInstanceState.getSerializable("projectColumns"); projectSwimlanes = (ArrayList<KanboardSwimlane>) savedInstanceState.getSerializable("projectSwimlanes"); setTaskDetails(); setCategoryDetails(); setSwimlaneDetails(swimlane.getName()); if (comments != null) { commentListview.setAdapter(new CommentAdapter (getBaseContext(), comments, true)); hideCommentProgress(); // findViewById(R.id.card_comments).setVisibility(View.VISIBLE); } else { // findViewById(R.id.card_comments).setVisibility(View.GONE); } if (subtasks != null) { subtaskListview.setAdapter(new SubtaskAdapter(getBaseContext(), subtasks, true)); hideSubtaskProgress(); // findViewById(R.id.card_subtasks).setVisibility(View.VISIBLE); } else { // findViewById(R.id.card_subtasks).setVisibility(View.GONE); } if (files != null) { filesListview.setAdapter(new TaskFilesAdapter(getBaseContext(), files)); hideFilesProgress(); // findViewById(R.id.card_files).setVisibility(View.VISIBLE); Log.d(Constants.TAG, "restore files"); } else { // findViewById(R.id.card_files).setVisibility(View.GONE); Log.d(Constants.TAG, "no files"); } } private void expandFABMenu() { Log.i(Constants.TAG, "Expand FAB."); ViewCompat.animate(fabMenu).rotation(90.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(5.0F)).start(); findViewById(R.id.fab_menu_item0).startAnimation(fabOpenAnimation); findViewById(R.id.fab_menu_item1).startAnimation(fabOpenAnimation); findViewById(R.id.fab_menu_item2).startAnimation(fabOpenAnimation); findViewById(R.id.fab_menu_item3).startAnimation(fabOpenAnimation); findViewById(R.id.fab_menu_item4).startAnimation(fabOpenAnimation); fabMenuButtonRemoveTask.setClickable(true); fabMenuButtonOpenCloseTask.setClickable(true); fabMenuButtonNewComment.setClickable(true); fabMenuButtonNewSubtask.setClickable(true); fabMenuButtonEditTask.setClickable(true); isFABMenuOpen = true; } private void collapseFABMenu() { Log.i(Constants.TAG, "Collapse FAB."); ViewCompat.animate(fabMenu).rotation(0.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(5.0F)).start(); findViewById(R.id.fab_menu_item0).startAnimation(fabCloseAnimation); findViewById(R.id.fab_menu_item1).startAnimation(fabCloseAnimation); findViewById(R.id.fab_menu_item2).startAnimation(fabCloseAnimation); findViewById(R.id.fab_menu_item3).startAnimation(fabCloseAnimation); findViewById(R.id.fab_menu_item4).startAnimation(fabCloseAnimation); fabMenuButtonRemoveTask.setClickable(false); fabMenuButtonOpenCloseTask.setClickable(false); fabMenuButtonNewComment.setClickable(false); fabMenuButtonNewSubtask.setClickable(false); fabMenuButtonEditTask.setClickable(false); isFABMenuOpen = false; } private void showProgress() { activeRequests++; if (activeRequests > 0 && refreshAction != null && !progressVisible) { ProgressBar prog = new ProgressBar(self); prog.getIndeterminateDrawable().setColorFilter(Color.WHITE, android.graphics.PorterDuff.Mode.MULTIPLY); refreshAction.setActionView(prog); refreshAction.expandActionView(); progressVisible = true; } } private void hideProgress() { activeRequests = activeRequests > 0 ? --activeRequests : 0; if (activeRequests == 0 && refreshAction != null && progressVisible) { refreshAction.collapseActionView(); refreshAction.setActionView(null); progressVisible = false; } } private void showCommentProgress() { commentListview.setVisibility(View.GONE); findViewById(R.id.commentProgress).setVisibility(View.VISIBLE); } private void hideCommentProgress() { commentListview.setVisibility(View.VISIBLE); findViewById(R.id.commentProgress).setVisibility(View.GONE); } private void showSubtaskProgress() { subtaskListview.setVisibility(View.GONE); findViewById(R.id.subtaskProgress).setVisibility(View.VISIBLE); } private void hideSubtaskProgress() { subtaskListview.setVisibility(View.VISIBLE); findViewById(R.id.subtaskProgress).setVisibility(View.GONE); } private void showFilesProgress() { filesListview.setVisibility(View.GONE); findViewById(R.id.filesProgress).setVisibility(View.VISIBLE); } private void hideFilesProgress() { filesListview.setVisibility(View.VISIBLE); findViewById(R.id.filesProgress).setVisibility(View.GONE); } private void refresh() { Log.i(Constants.TAG, "Loading task data."); showProgress(); kanboardAPI.getTask(task.getId()); showProgress(); kanboardAPI.getProjectUsers(task.getProjectId()); showProgress(); kanboardAPI.getAllComments(task.getId()); showCommentProgress(); showProgress(); kanboardAPI.getAllSubtasks(task.getId()); showSubtaskProgress(); showProgress(); kanboardAPI.getAllTaskFiles(task.getId()); showFilesProgress(); showProgress(); kanboardAPI.getColumns(task.getProjectId()); showProgress(); kanboardAPI.getActiveSwimlanes(task.getProjectId()); } private void showCommentDialog(@Nullable final KanboardComment comment) { AlertDialog.Builder builder = new AlertDialog.Builder(self); builder.setTitle(getString(comment == null ? R.string.taskview_fab_new_comment : R.string.taskview_dlg_update_comment)); final EditText input = new EditText(this); input.setText(comment == null ? "" : comment.getContent()); input.setSingleLine(false); input.setMinLines(5); input.setMaxLines(10); input.setVerticalScrollBarEnabled(true); builder.setView(input); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (!input.getText().toString().contentEquals("")) { if (comment == null) { Log.i(Constants.TAG, "Creating new comment."); kanboardAPI.createComment(task.getId(), me.getId(), input.getText().toString()); } else { Log.i(Constants.TAG, "Updating comment."); kanboardAPI.updateComment(comment.getId(), input.getText().toString()); } dialog.dismiss(); } } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.show(); } private void showSubtaskDialog(@Nullable final KanboardSubtask subtask) { View dlgView = getLayoutInflater().inflate(R.layout.dialog_new_subtask, null); final EditText editTitle = (EditText) dlgView.findViewById(R.id.subtask_title); editTitle.setText(subtask == null ? "" : subtask.getTitle()); final Spinner userSpinner = (Spinner) dlgView.findViewById(R.id.user_spinner); ArrayList<String> possibleOwners = Collections.list(users.elements()); possibleOwners.add(0, ""); ArrayAdapter<String> adapter = new ArrayAdapter<>(self, android.R.layout.simple_spinner_item, possibleOwners); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); userSpinner.setAdapter(adapter); if (subtask != null) userSpinner.setSelection(possibleOwners.indexOf(users.get(subtask.getUserId()))); AlertDialog.Builder builder = new AlertDialog.Builder(self); builder.setTitle(getString(subtask == null ? R.string.taskview_fab_new_subtask : R.string.taskview_dlg_update_subtask)); builder.setView(dlgView); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Integer userid = null; if (userSpinner.getSelectedItem() != null) { for (Enumeration<Integer> iter = users.keys(); iter.hasMoreElements();) { Integer key = iter.nextElement(); if (users.get(key).contentEquals((String) userSpinner.getSelectedItem())) { userid = key; break; } } } if (!editTitle.getText().toString().equalsIgnoreCase("")) { if (subtask == null) { Log.i(Constants.TAG, "Creating new subtask."); kanboardAPI.createSubtask(task.getId(), editTitle.getText().toString(), userid, null, null, null); } else { Log.i(Constants.TAG, "Updating subtask."); kanboardAPI.updateSubtask(subtask.getId(), subtask.getTaskId(), editTitle.getText().toString(), userid, null, null, null); } dialog.dismiss(); } } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.show(); } private void showChangeColumnDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(self); builder.setTitle(getText(R.string.taskview_fab_change_column)); final Spinner input = new Spinner(this); ArrayAdapter<KanboardColumn> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, projectColumns); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); input.setAdapter(adapter); for (int i = 0; i < projectColumns.size(); i++) { if (projectColumns.get(i).getId() == task.getColumnId()) { input.setSelection(i); break; } } builder.setView(input); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setResult(Constants.ResultChanged); kanboardAPI.moveTaskPosition(task.getProjectId(), task.getId(), ((KanboardColumn)input.getSelectedItem()).getId(), 1, task.getSwimlaneId()); dialog.dismiss(); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.show(); } private void showChangeSwimlaneDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(self); builder.setTitle(getText(R.string.taskview_fab_change_swimlane)); final Spinner input = new Spinner(this); ArrayAdapter<KanboardSwimlane> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, projectSwimlanes); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); input.setAdapter(adapter); for (int i = 0; i < projectSwimlanes.size(); i++) { if (projectSwimlanes.get(i).getId() == task.getSwimlaneId()) { input.setSelection(i); break; } } builder.setView(input); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { setResult(Constants.ResultChanged); kanboardAPI.moveTaskPosition(task.getProjectId(), task.getId(), task.getColumnId(), 1, ((KanboardSwimlane)input.getSelectedItem()).getId()); dialog.dismiss(); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.show(); } private void showDeleteCommentDialog(final KanboardComment comment) { AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this); dlgBuilder.setTitle(getString(R.string.delete_dlg_comment)); dlgBuilder.setMessage(getString(R.string.delete_dlg_message)); dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { kanboardAPI.removeComment(comment.getId()); } }); dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) {} }); dlgBuilder.show(); } private void showDeleteSubtaskDialog(final KanboardSubtask subtask) { AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this); dlgBuilder.setTitle(getString(R.string.delete_dlg_subtask)); dlgBuilder.setMessage(getString(R.string.delete_dlg_message)); dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { kanboardAPI.removeSubtask(subtask.getId()); } }); dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) {} }); dlgBuilder.show(); } private void showDeleteTaskFileDialog(final KanboardTaskFile taskfile) { AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this); dlgBuilder.setTitle(getString(R.string.delete_dlg_file)); dlgBuilder.setMessage(getString(R.string.delete_dlg_message)); dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { kanboardAPI.removeTaskFile(taskfile.getId()); } }); dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) {} }); dlgBuilder.show(); } private void showDeleteTaskDialog(final KanboardTask task) { AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(TaskDetailActivity.this); dlgBuilder.setTitle(getString(R.string.delete_dlg_task)); dlgBuilder.setMessage(getString(R.string.delete_dlg_message)); dlgBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { kanboardAPI.removeTask(task.getId()); } }); dlgBuilder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); dlgBuilder.show(); } private void setupActionBar() { ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { // Show the Up button in the action bar. actionBar.setDisplayHomeAsUpEnabled(true); } } private void setTaskDetails() { if (getSupportActionBar() != null) getSupportActionBar().setTitle(task.getTitle()); if (task.getIsActive()) textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_open)))); else textStatus.setText(Utils.fromHtml(getString(R.string.taskview_status, getString(R.string.taskview_status_closed)))); textPosition.setText(Utils.fromHtml(getString(R.string.taskview_position, task.getPosition()))); textPriority.setText(Utils.fromHtml(getString(R.string.taskview_priority, task.getPriority()))); if (task.getOwnerId() > 0) textOwner.setText(Utils.fromHtml(getString(R.string.taskview_owner, users == null ? Integer.toString(task.getOwnerId()) : users.get(task.getOwnerId())))); else textOwner.setText(Utils.fromHtml(getString(R.string.taskview_owner, getString(R.string.task_not_assigned)))); textCreator.setText(Utils.fromHtml(getString(R.string.taskview_creator, users == null ? Integer.toString(task.getCreatorId()) : users.get(task.getCreatorId())))); textDateCreated.setText(Utils.fromHtml(getString(R.string.taskview_date_created, task.getDateCreation()))); textDateModified.setText(Utils.fromHtml(getString(R.string.taskview_date_modified, task.getDateModification()))); textDateMoved.setText(Utils.fromHtml(getString(R.string.taskview_date_moved, task.getDateMoved()))); textHoursEstimated.setText(Utils.fromHtml(getString(R.string.taskview_hours_estimated, task.getTimeEstimated()))); textHoursUsed.setText(Utils.fromHtml(getString(R.string.taskview_hours_spent, task.getTimeSpent()))); if (task.getDateStarted() != null) { textDateStart.setText(Utils.fromHtml(getString(R.string.taskview_date_start, task.getDateStarted()))); textDateStart.setVisibility(View.VISIBLE); } else { textDateStart.setVisibility(View.INVISIBLE); } if (task.getDateDue() != null) { textDateDue.setText(Utils.fromHtml(getString(R.string.taskview_date_due, task.getDateDue()))); textDateDue.setVisibility(View.VISIBLE); } else { textDateDue.setVisibility(View.INVISIBLE); } if (task.getDateStarted() == null && task.getDateDue() == null) { textDateStart.setVisibility(View.GONE); textDateDue.setVisibility(View.GONE); } if (task.getDescription() != null && !task.getDescription().contentEquals("")) { textDescription.setText(Utils.fromHtml(mRenderer.render(mParser.parse(task.getDescription())))); findViewById(R.id.card_description).setVisibility(View.VISIBLE); } if (task.getIsActive()) { fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_close, null)); fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_close_task)); } else { fabMenuButtonOpenCloseTask.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.task_open, null)); fabMenuLabelOpenCloseTask.setText(getString(R.string.taskview_fab_open_task)); } if (opencloseAction != null) { if (task.getIsActive()) { opencloseAction.setTitle(getString(R.string.taskview_fab_close_task)); } else { opencloseAction.setTitle(getString(R.string.taskview_fab_open_task)); } } } private void setSwimlaneDetails(String swimlanename) { textSwimlane.setText(Utils.fromHtml(getString(R.string.taskview_swimlane, swimlanename))); textSwimlane.setVisibility(View.VISIBLE); } private void setCategoryDetails() { if (category != null) { textCategory.setText(Utils.fromHtml(getString(R.string.taskview_category, category.getName()))); } else { textCategory.setText(Utils.fromHtml(getString(R.string.taskview_category, getString(R.string.task_not_assigned)))); } } //region internal classes private class SubtaskAdapter extends ArrayAdapter<KanboardSubtask> { private Context mContext; private LayoutInflater mInflater; private List<KanboardSubtask> mObjects; private boolean mShowAdd = false; public SubtaskAdapter(Context context, List<KanboardSubtask> objects, boolean showAdd) { super(context, R.layout.listitem_subtask, objects); mContext = context; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mObjects = objects; mShowAdd = showAdd; } @Override public int getCount() { return mObjects.size() + (mShowAdd ? 1 : 0); } @NonNull @Override public View getView(int position, View convertView, @NonNull ViewGroup parent) { if (position < getCount() - (mShowAdd ? 1 : 0)) { convertView = mInflater.inflate(R.layout.listitem_subtask, parent, false); convertView.setLongClickable(true); TextView text = (TextView) convertView.findViewById(android.R.id.text1); CheckBox check = (CheckBox) convertView.findViewById(android.R.id.checkbox); Switch toggle = (Switch) convertView.findViewById(R.id.buttonToggle); // Use tag to save list position toggle.setTag(position); check.setTag(position); final OnSubtaskTimetrackingListener startTimer = new OnSubtaskTimetrackingListener() { @Override public void onSubtaskTimetracking(boolean result, double time) { } }; final OnSubtaskTimetrackingListener stopTimer = new OnSubtaskTimetrackingListener() { @Override public void onSubtaskTimetracking(boolean result, double time) { } }; View.OnClickListener toggleClick = new View.OnClickListener() { @Override public void onClick(View v) { if (((Switch) v).isChecked()) { kanboardAPI.setSubtaskStartTime(mObjects.get((int) v.getTag()).getId(), me.getId(), startTimer); } else { kanboardAPI.setSubtaskEndTime(mObjects.get((int) v.getTag()).getId(), me.getId(), stopTimer); } } }; toggle.setOnClickListener(toggleClick); check.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (((CheckBox) v).isChecked()) kanboardAPI.updateSubtask(mObjects.get((int) v.getTag()).getId(), mObjects.get((int) v.getTag()).getTaskId(), null, null, null, null, 2); else kanboardAPI.updateSubtask(mObjects.get((int) v.getTag()).getId(), mObjects.get((int) v.getTag()).getTaskId(), null, null, null, null, 0); } }); if (mObjects.get(position).getUserId() == me.getId()) toggle.setEnabled(true); else toggle.setEnabled(false); double timer = 0; if (hasTimer.containsKey(mObjects.get(position).getId())) { toggle.setChecked(true); timer = hasTimer.get(mObjects.get(position).getId()); } else toggle.setChecked(false); if (mObjects.get(position).getStatus() == 2) { text.setText(Utils.fromHtml(String.format(Locale.getDefault(), "<del>%s</del>", mObjects.get(position).getTitle()))); check.setChecked(true); } else if (mObjects.get(position).getStatus() == 1) { text.setText(Utils.fromHtml(String.format(Locale.getDefault(), "<b>%s</b>", mObjects.get(position).getTitle()))); check.setChecked(false); } else { text.setText(mObjects.get(position).getTitle()); check.setChecked(false); } text.setSelected(true); ((TextView) convertView.findViewById(android.R.id.text2)).setText(String.format(Locale.getDefault(), "%.2fh", mObjects.get(position).getTimeSpent() + timer)); } else { convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); ((TextView) convertView.findViewById(android.R.id.text1)).setText(getString(R.string.taskview_fab_new_subtask)); ((TextView) convertView.findViewById(android.R.id.text1)).setTextAlignment(View.TEXT_ALIGNMENT_CENTER); } return convertView; } } private class CommentAdapter extends ArrayAdapter<KanboardComment> { private Context mContext; private LayoutInflater mInflater; private List<KanboardComment> mObjects; private boolean mShowAdd = false; public CommentAdapter(Context context, List<KanboardComment> objects, boolean showAdd) { super(context, R.layout.listitem_comment, objects); mContext = context; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mObjects = objects; mShowAdd = showAdd; } @Override public int getCount() { return mObjects.size() + (mShowAdd ? 1 : 0); } @NonNull @Override public View getView(int position, View convertView, @NonNull ViewGroup parent) { if (position < getCount() - (mShowAdd ? 1 : 0)) { convertView = mInflater.inflate(R.layout.listitem_comment, parent, false); convertView.setLongClickable(true); ((TextView) convertView.findViewById(R.id.username)).setText(Utils.fromHtml(String.format("<small>%s</small>", users == null ? mObjects.get(position).getUsername() : users.get(mObjects.get(position).getUserId())))); ((TextView) convertView.findViewById(R.id.date)).setText(Utils.fromHtml(String.format("<small>%tF</small>", mObjects.get(position).getDateModification()))); ((TextView) convertView.findViewById(R.id.comment)).setText(Utils.fromHtml(mRenderer.render(mParser.parse(mObjects.get(position).getContent())))); } else { convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); ((TextView) convertView.findViewById(android.R.id.text1)).setText(getString(R.string.taskview_fab_new_comment)); ((TextView) convertView.findViewById(android.R.id.text1)).setTextAlignment(View.TEXT_ALIGNMENT_CENTER); } return convertView; } } private class TaskFilesAdapter extends ArrayAdapter<KanboardTaskFile> { private Context mContext; private LayoutInflater mInflater; List<KanboardTaskFile> mObjects; public TaskFilesAdapter(Context context, List<KanboardTaskFile> objects) { super(context, R.layout.listitem_comment, objects); mContext = context; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mObjects = objects; } @NonNull @Override public View getView(int position, View convertView, @NonNull ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.listitem_taskfiles, parent, false); convertView.setLongClickable(true); } ((TextView) convertView.findViewById(R.id.username)).setText(Utils.fromHtml(String.format("<small>%s</small>", users == null ? mObjects.get(position).getUsername() : users.get(mObjects.get(position).getUserId())))); ((TextView) convertView.findViewById(R.id.date)).setText(Utils.fromHtml(String.format("<small>%tF</small>", mObjects.get(position).getFileDate()))); ((TextView) convertView.findViewById(R.id.filename)).setText(String.format("%s", mObjects.get(position).getName())); double size = mObjects.get(position).getSize(); int rounds = 0; while (size > 1024 && rounds < 4) { size /= 1024; rounds++; } ((TextView) convertView.findViewById(R.id.filesize)).setText(String.format(Locale.getDefault(), "%.2f %s", size, mContext.getResources().getStringArray(R.array.file_sizes)[rounds])); return convertView; } } //endregion }