5454import java .time .ZoneId ;
5555import java .util .ArrayList ;
5656import java .util .Locale ;
57+ import java .util .Objects ;
5758import java .util .stream .Collectors ;
5859import java .util .stream .Stream ;
5960
@@ -154,7 +155,7 @@ public JSONObject call() {
154155 });
155156 else if (getIntent ().hasExtra ("id" )) {
156157 int subtaskId = getIntent ().getIntExtra ("id" , 0 );
157- new SubtaskRetrieval (subtaskId ).runAsync (getSharedPreferences ("user identification" , Context .MODE_PRIVATE ).getString ("apiKey" , "" ), new ApiResponseExecutor () {
158+ new TaskRetrieval (subtaskId ).runAsync (getSharedPreferences ("user identification" , Context .MODE_PRIVATE ).getString ("apiKey" , "" ), new ApiResponseExecutor () {
158159 @ Override
159160 public void onComplete (JSONObject result ) {
160161 super .onComplete (result );
@@ -258,31 +259,8 @@ private void setSubtask(TextView view, boolean isCompleted) {
258259 */
259260 private void loadSubtasks () {
260261 new TaskRunner ().executeAsync (
261- new ApiRequestExecutor () {
262- @ Override
263- protected void initialize () {
264- super .initialize ();
265- setRequestMethod ("GET" );
266- addRequestHeader ("Authorization" , "Basic " + getUserApiKey ());
267- }
268-
269- @ Override
270- public JSONObject call () {
271- super .call ();
272- return this .connectToApi (encodeQueryString ("subtasks" , "id=" + getJottingData ().getId ()));
273- }
274- }, data -> {
275- try {
276- if (data != null && data .has ("data" ) && data .getInt ("statusCode" ) == 200 ) {
277- ArrayList <Task > subtaskData = JSONToModel .convertJSONToTasks (data .getJSONArray ("data" ));
278- setSubtasksTableContent (subtaskData );
279- getTaskData ().setSubtasks (subtaskData );
280- }
281- } catch (JSONException e ) {
282- e .printStackTrace ();
283- Toast .makeText (this , "The subtasks could not load" , Toast .LENGTH_SHORT ).show ();
284- }
285- }
262+ requestSubtasks (getTaskData ().getId ()),
263+ handleGetSubtasksResponse ()
286264 );
287265
288266 }
@@ -481,8 +459,12 @@ private void setSubtasksTableContent(ArrayList<Task> subtasks) {
481459
482460 subtaskList .setColumnShrinkable (2 , true );
483461
484- for (Task subtask : subtasks ) {
485- subtaskList .addView (addSubtaskToTable (subtask ));
462+ for (int i = 0 ; i < subtasks .size (); i ++) {
463+ Task subtask = subtasks .get (i );
464+ subtaskList .addView (createSubtaskRow (subtask ), i * 2 );
465+ subtaskList .addView (new TableRow (getApplicationContext ()), i * 2 + 1 );
466+
467+ new TaskRunner ().executeAsync (requestSubtasks (subtask .getId ()), handleGetSecondLevelSubtasksResponse (i * 2 + 1 ));
486468 }
487469
488470 TableRow addSubtaskLayout = new TableRow (this );
@@ -513,12 +495,13 @@ private void setDueDate(Date dueDate) {
513495 }
514496 }
515497
516- private LinearLayout addSubtaskToTable (Task subtask ) {
498+ private LinearLayout createSubtaskRow (Task subtask ) {
517499 LinearLayout horizLayout = (LinearLayout ) getLayoutInflater ().inflate (R .layout .subtask , null );
518500 horizLayout .setTag (subtask .getId ());
519501
520502 TextView title = horizLayout .findViewById (R .id .subtask_title );
521503 title .setText (SlashNormalizer .unescapeUserSlashes (subtask .getName ()));
504+
522505 title .setOnFocusChangeListener (this ::onSubtaskBoxFocus );
523506 // Display strikethrough if the subtask is marked as complete
524507 if (subtask .isCompleted ())
@@ -530,6 +513,30 @@ private LinearLayout addSubtaskToTable(Task subtask) {
530513 return horizLayout ;
531514 }
532515
516+ /**
517+ * @param secondLevelSubtasks The list of subtasks of a subtask
518+ * @return The table of subtasks for a subtask
519+ */
520+ private LinearLayout createSecondLevelSubtaskTable (ArrayList <Task > secondLevelSubtasks ) {
521+ LinearLayout secondLevelSubtaskList = new LinearLayout (getApplicationContext ());
522+ secondLevelSubtaskList .setOrientation (LinearLayout .VERTICAL );
523+
524+ for (Task subtask : secondLevelSubtasks ) {
525+ LinearLayout subtaskRow = createSubtaskRow (subtask );
526+ LinearLayout .LayoutParams subtaskRowParams = new LinearLayout .LayoutParams (ViewGroup .LayoutParams .MATCH_PARENT , ViewGroup .LayoutParams .WRAP_CONTENT );
527+ subtaskRowParams .bottomMargin = 10 ;
528+ subtaskRowParams .topMargin = 10 ;
529+ subtaskRowParams .leftMargin = 50 ;
530+
531+ subtaskRow .setLayoutParams (subtaskRowParams );
532+
533+ secondLevelSubtaskList .addView (subtaskRow );
534+ }
535+
536+ return secondLevelSubtaskList ;
537+ }
538+
539+
533540 /* Event Handlers */
534541
535542 public void onEnterSubtaskClick (View v ) {
@@ -602,6 +609,116 @@ public void onComplete(JSONObject data) {
602609 );
603610 }
604611
612+ private ApiRequestExecutor createSubtask (int parentId , String subtaskName ) {
613+ return new ApiRequestExecutor (String .valueOf (parentId ), subtaskName ) {
614+ @ Override
615+ protected void initialize () {
616+ super .initialize ();
617+ setRequestMethod ("POST" );
618+ addRequestHeader ("Authorization" , "Basic " + getUserApiKey ());
619+ setQuery (encodePostQuery ("id=%s&name=%s" ));
620+ }
621+
622+ @ Override
623+ public JSONObject call () {
624+ super .call ();
625+ return this .connectToApi (this .encodeQueryString ("subtasks" ));
626+ }
627+ };
628+ }
629+
630+ private ApiRequestExecutor requestSubtasks (int id ) {
631+ return new ApiRequestExecutor () {
632+ @ Override
633+ protected void initialize () {
634+ super .initialize ();
635+ setRequestMethod ("GET" );
636+ addRequestHeader ("Authorization" , "Basic " + getUserApiKey ());
637+ }
638+
639+ @ Override
640+ public JSONObject call () {
641+ super .call ();
642+ return this .connectToApi (encodeQueryString ("subtasks" , "id=" + id ));
643+ }
644+ };
645+ }
646+
647+ private ApiResponseExecutor handleCreateSubtaskResponse () {
648+ return new ApiResponseExecutor () {
649+ @ Override
650+ public void onComplete (JSONObject result ) {
651+ super .onComplete (result );
652+ try {
653+ if (ranOk ()) {
654+ Task subtask = new Task (Objects .requireNonNull (newSubtaskField .getText ()).toString ());
655+ subtask .setId (result .getJSONObject ("data" ).getInt ("id" ));
656+
657+ int indexAfterLastIncompleteTask = searchForLastIncompleteTask (subtask );
658+ subtaskList .addView (createSubtaskRow (subtask ), indexAfterLastIncompleteTask );
659+
660+ ((EditText ) subtaskList .findViewById (R .id .newSubtaskFieldId )).setText ("" );
661+ }
662+ } catch (JSONException e ) {
663+ e .printStackTrace ();
664+ }
665+ }
666+ };
667+ }
668+
669+ private ApiResponseExecutor handleGetSubtasksResponse () {
670+ return new ApiResponseExecutor () {
671+ @ Override
672+ public void onComplete (JSONObject data ) {
673+ super .onComplete (data );
674+ try {
675+ if (data != null && data .has ("data" ) && data .getInt ("statusCode" ) == 200 ) {
676+ ArrayList <Task > subtaskData = JSONToModel .convertJSONToTasks (data .getJSONArray ("data" ));
677+ setSubtasksTableContent (subtaskData );
678+ getTaskData ().setSubtasks (subtaskData );
679+ }
680+ } catch (JSONException e ) {
681+ e .printStackTrace ();
682+ Toast .makeText (getApplicationContext (), "The subtasks could not load" , Toast .LENGTH_SHORT ).show ();
683+ }
684+ }
685+ };
686+ }
687+
688+ private ApiResponseExecutor handleGetSecondLevelSubtasksResponse (int loc ) {
689+ return new ApiResponseExecutor () {
690+ @ Override
691+ public void onComplete (JSONObject data ) {
692+ super .onComplete (data );
693+ try {
694+ if (ranOk ()) {
695+ ArrayList <Task > subtaskData = JSONToModel .convertJSONToTasks (data .getJSONArray ("data" ));
696+ TableRow subtaskListRow = new TableRow (getApplicationContext ());
697+ subtaskListRow .addView (createSecondLevelSubtaskTable (subtaskData ));
698+ subtaskList .removeViewAt (loc );
699+ subtaskList .addView (subtaskListRow , loc );
700+
701+ if (subtaskData .isEmpty ()) return ;
702+
703+ // Set subtask data in task for non-UI reference
704+ ArrayList <Task > topLevelSubtasks = getTaskData ().getSubtasks ();
705+ int numTopLevelSubtasks = topLevelSubtasks .size ();
706+ for (int i = 0 ; i < numTopLevelSubtasks ; i ++) {
707+ Task subtask = topLevelSubtasks .get (i );
708+ if (subtask .getId () == subtaskData .get (0 ).getParentId ()) {
709+ subtask .setSubtasks (subtaskData );
710+ getTaskData ().setSubtask (i , subtask );
711+ }
712+ }
713+ }
714+ } catch (JSONException e ) {
715+ e .printStackTrace ();
716+ }
717+ }
718+ };
719+ }
720+
721+
605722 public void onAddSubtaskClick (View view ) {
606723 // Exit early if the field unexpectedly doesn't exist
607724 if (newSubtaskField == null || newSubtaskField .getText () == null ) return ;
@@ -613,41 +730,11 @@ public void onAddSubtaskClick(View view) {
613730 }
614731
615732 new TaskRunner ().executeAsync (
616- new ApiRequestExecutor (String .valueOf (getTaskData ().getId ()), newSubtaskField .getText ().toString ()) {
617- @ Override
618- protected void initialize () {
619- super .initialize ();
620- setRequestMethod ("POST" );
621- addRequestHeader ("Authorization" , "Basic " + getUserApiKey ());
622- setQuery (encodePostQuery ("id=%s&name=%s" ));
623- }
624-
625- @ Override
626- public JSONObject call () {
627- super .call ();
628- return this .connectToApi (this .encodeQueryString ("subtasks" ));
629- }
630- }, new ApiResponseExecutor () {
631- @ Override
632- public void onComplete (JSONObject result ) {
633- super .onComplete (result );
634- try {
635- if (ranOk ()) {
636- Task subtask = new Task (newSubtaskField .getText ().toString ());
637- subtask .setId (result .getJSONObject ("data" ).getInt ("id" ));
638-
639- int indexAfterLastIncompleteTask = searchForLastIncompleteTask (subtask );
640- subtaskList .addView (addSubtaskToTable (subtask ), indexAfterLastIncompleteTask );
641-
642- ((EditText ) subtaskList .findViewById (R .id .newSubtaskFieldId )).setText ("" );
643- }
644- } catch (JSONException e ) {
645- e .printStackTrace ();
646- }
647- }
648- }
733+ createSubtask (getTaskData ().getId (), newSubtaskField .getText ().toString ()),
734+ handleCreateSubtaskResponse ()
649735 );
650736 }
737+
651738 /**
652739 * Add right after last incomplete task using binary search for the last incomplete subtask
653740 * @implNote The algorithm is as follows:
@@ -692,7 +779,6 @@ private int searchForLastIncompleteTask(Task subtask) {
692779 return currSubtasks .size ();
693780 }
694781
695-
696782 public void onCompleteSubtaskClick (View view ) {
697783 TableRow subtaskRow = (TableRow ) view .getParent ();
698784 int completed = ((CheckBox ) view ).isChecked () ? 1 : 0 ;
0 commit comments