commit 4debbf446371ed5907bc764e13f670e4dbc84565
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sun Jul 5 23:57:44 2020 +0100

    Reinvent the Pattern Dialog (Part 1)

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPattern.java b/src/main/java/org/distorted/dialogs/RubikDialogPattern.java
index 1945b0f6..53343643 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPattern.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPattern.java
@@ -86,9 +86,6 @@ public class RubikDialogPattern extends AppCompatDialogFragment
     final View view = inflater.inflate(R.layout.dialog_tabbed, null);
     builder.setView(view);
 
- //   TabLayout tl = view.findViewById(R.id.sliding_tabs);
-
-
     ViewPager viewPager = view.findViewById(R.id.viewpager);
     TabLayout tabLayout = view.findViewById(R.id.sliding_tabs);
     mPagerAdapter = new RubikDialogPatternPagerAdapter(act, viewPager, this);
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java b/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java
new file mode 100644
index 00000000..6f91ee8d
--- /dev/null
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java
@@ -0,0 +1,154 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.dialogs;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.TextView;
+
+import org.distorted.patterns.RubikPattern;
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class RubikDialogPatternListAdapter extends BaseExpandableListAdapter
+  {
+  private Context mContext;
+  private int mTab;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikDialogPatternListAdapter(Context context, int tab)
+    {
+    mContext = context;
+    mTab     = tab;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public Object getChild(int groupPosition, int childPosition)
+    {
+    RubikPattern pattern = RubikPattern.getInstance();
+    return pattern.getPatternName(mTab,groupPosition,childPosition);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public long getChildId(int groupPosition, int childPosition)
+    {
+    return childPosition;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View view, ViewGroup parent)
+    {
+    String childName = (String) getChild(groupPosition, childPosition);
+
+    if (view == null)
+      {
+      LayoutInflater infalInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+      view = infalInflater.inflate(R.layout.dialog_pattern_child_item, null);
+      }
+
+    TextView sequence = view.findViewById(R.id.sequence);
+    sequence.setText(mContext.getString(R.string.sq_placeholder,childPosition+1));
+    TextView childItem = view.findViewById(R.id.childItem);
+    childItem.setText(childName);
+
+    return view;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public int getChildrenCount(int groupPosition)
+    {
+    RubikPattern pattern = RubikPattern.getInstance();
+    return pattern.getNumPatterns(mTab,groupPosition);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public Object getGroup(int groupPosition)
+    {
+    RubikPattern pattern = RubikPattern.getInstance();
+    return pattern.getCategoryName(mTab,groupPosition);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public int getGroupCount()
+    {
+    RubikPattern pattern = RubikPattern.getInstance();
+    return pattern.getNumCategories(mTab);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public long getGroupId(int groupPosition)
+    {
+    return groupPosition;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public View getGroupView(int groupPosition, boolean isLastChild, View view, ViewGroup parent)
+    {
+    String groupName = (String) getGroup(groupPosition);
+
+    if (view == null)
+      {
+      LayoutInflater inf = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+      view = inf.inflate(R.layout.dialog_pattern_group_item, null);
+      }
+
+    TextView heading = view.findViewById(R.id.heading);
+    heading.setText(groupName);
+
+    return view;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public boolean hasStableIds()
+    {
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public boolean isChildSelectable(int groupPosition, int childPosition)
+    {
+    return true;
+    }
+  }
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java b/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
index 447ad08a..04be3e9a 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
@@ -22,15 +22,9 @@ package org.distorted.dialogs;
 import android.content.Context;
 import androidx.fragment.app.FragmentActivity;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
 import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
+import android.widget.ExpandableListView;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.Spinner;
 
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
@@ -42,12 +36,13 @@ import org.distorted.states.RubikStatePattern;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikDialogPatternView extends FrameLayout implements AdapterView.OnItemSelectedListener
+public class RubikDialogPatternView extends FrameLayout
   {
-  private LinearLayout mLayout;
-  private ScrollView mScroll;
+  private ExpandableListView mListView;
+  private RubikDialogPatternListAdapter mListAdapter;
   private RubikDialogPattern mDialog;
   private int mTab, mPos;
+  private int mExpandedGroup;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -69,107 +64,57 @@ public class RubikDialogPatternView extends FrameLayout implements AdapterView.O
     {
     super(act);
 
-    mDialog = dialog;
-    mTab = position;
-    View tab = inflate( act, R.layout.dialog_pattern_tab, null);
-    mLayout = tab.findViewById(R.id.tabLayout);
-    mScroll = tab.findViewById(R.id.tabScrollView);
-
-    String[] categories = createCategories();
-
-    Spinner spinner = tab.findViewById(R.id.pattern_category_spinner);
-    spinner.setOnItemSelectedListener(this);
-
-    ArrayAdapter<String> adapter = new ArrayAdapter<>(act, android.R.layout.simple_spinner_item, categories);
-    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-    spinner.setAdapter(adapter);
-
-    RubikPattern pattern = RubikPattern.getInstance();
-    spinner.setSelection(pattern.recallCategory(mTab));
+    final RubikActivity ract = (RubikActivity)getContext();
 
-    addView(tab);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+    mTab = position;
+    mDialog = dialog;
+    mExpandedGroup = -1;
 
-  private String[] createCategories()
-    {
-    RubikPattern pattern = RubikPattern.getInstance();
-    int numCat = pattern.getNumCategories(mTab);
+    View tab = inflate( act, R.layout.dialog_pattern_tab, null);
 
-    String[] ret = new String[numCat];
+    mListView = tab.findViewById(R.id.patternListView);
+    mListAdapter = new RubikDialogPatternListAdapter(act,mTab);
+    mListView.setAdapter(mListAdapter);
 
-    for(int i=0; i<numCat; i++)
+    mListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener()
       {
-      ret[i] = pattern.getCategoryName(mTab,i);
-      }
+      @Override
+      public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id)
+        {
+        RubikPattern pattern = RubikPattern.getInstance();
+        int[][] moves = pattern.reInitialize(mTab, groupPosition, childPosition);
 
-    return ret;
-    }
+        RubikObjectList list = RubikPatternList.getObject(mTab);
+        int size             = RubikPatternList.getSize(mTab);
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+        ract.setupObject(list, size, moves);
 
-  private String[] createPatterns(int category)
-    {
-    RubikPattern pattern = RubikPattern.getInstance();
-    int numPat = pattern.getNumPatterns(mTab, category);
+        RubikStatePattern state = (RubikStatePattern) RubikState.PATT.getStateClass();
 
-    String[] ret = new String[numPat];
+        state.setPattern(ract, mTab, groupPosition, childPosition);
 
-    for(int i=0; i<numPat; i++)
-      {
-      ret[i] = pattern.getPatternName(mTab, category, i);
-      }
-
-    return ret;
-    }
+        mDialog.rememberState();
+        mDialog.dismiss();
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fillPatterns(final int category)
-    {
-    final RubikActivity act = (RubikActivity)getContext();
+        return false;
+        }
+      });
 
-    DisplayMetrics metrics = act.getResources().getDisplayMetrics();
-    final float scale = metrics.density;
-    int margin = (int)(3*scale + 0.5f);
-    LinearLayout.LayoutParams bParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
-    bParams.setMargins(margin, margin, margin, margin);
-
-    final String[] patterns = createPatterns(category);
-    int len = patterns.length;
-    final RubikPattern pattern = RubikPattern.getInstance();
-
-    mLayout.removeAllViews();
-
-    for(int i=0; i<len; i++)
+    mListView.setOnGroupExpandListener(new ExpandableListView.OnGroupExpandListener()
       {
-      final int ii = i;
-      Button button = new Button(act);
-      button.setLayoutParams(bParams);
-      button.setText(patterns[i]);
-
-      button.setOnClickListener( new View.OnClickListener()
+      @Override
+      public void onGroupExpand(int groupPosition)
         {
-        @Override
-        public void onClick(View view)
+        if(mExpandedGroup!=-1 && groupPosition!=mExpandedGroup)
           {
-          int[][] moves = pattern.reInitialize(mTab, category, ii);
-
-          RubikObjectList list = RubikPatternList.getObject(mTab);
-          int size             = RubikPatternList.getSize(mTab);
-
-          act.setupObject( list, size, moves);
-
-          RubikStatePattern state = (RubikStatePattern) RubikState.PATT.getStateClass();
-          state.setPattern(act, mTab, category, ii);
-          mDialog.rememberState();
-          mDialog.dismiss();
+          mListView.collapseGroup(mExpandedGroup);
           }
-        });
 
-      mLayout.addView(button);
-      }
+        mExpandedGroup = groupPosition;
+        }
+      });
+
+    addView(tab);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -183,7 +128,7 @@ public class RubikDialogPatternView extends FrameLayout implements AdapterView.O
 
   int getCurrentScrollPos()
     {
-    return mScroll.getScrollY();
+    return 0;//mScroll.getScrollY();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -192,31 +137,12 @@ public class RubikDialogPatternView extends FrameLayout implements AdapterView.O
   protected void onLayout(boolean changed, int left, int top, int right, int bottom)
     {
     super.onLayout(changed,left,top,right,bottom);
-
+/*
     if( !changed )
       {
       final RubikPattern pattern = RubikPattern.getInstance();
       mScroll.setScrollY( pattern.recallScrollPos(mTab) );
       }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
-    {
-    if( parent.getId() == R.id.pattern_category_spinner )
-      {
-      mPos = pos;
-      fillPatterns(pos);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void onNothingSelected(AdapterView<?> parent)
-    {
-
+ */
     }
   }
diff --git a/src/main/java/org/distorted/states/RubikStatePlay.java b/src/main/java/org/distorted/states/RubikStatePlay.java
index 05e50f64..e0247e9f 100644
--- a/src/main/java/org/distorted/states/RubikStatePlay.java
+++ b/src/main/java/org/distorted/states/RubikStatePlay.java
@@ -24,7 +24,6 @@ import android.content.SharedPreferences;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Build;
 import android.os.Bundle;
-import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.LayoutInflater;
diff --git a/src/main/res/layout/dialog_pattern_child_item.xml b/src/main/res/layout/dialog_pattern_child_item.xml
new file mode 100644
index 00000000..a7c70bf9
--- /dev/null
+++ b/src/main/res/layout/dialog_pattern_child_item.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/sequence"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:paddingLeft="35sp"
+        />
+
+    <TextView
+        android:id="@+id/childItem"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/sequence"
+        />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/src/main/res/layout/dialog_pattern_group_item.xml b/src/main/res/layout/dialog_pattern_group_item.xml
new file mode 100644
index 00000000..56dd3397
--- /dev/null
+++ b/src/main/res/layout/dialog_pattern_group_item.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/heading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="35sp"
+        android:textStyle="bold" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/layout/dialog_pattern_tab.xml b/src/main/res/layout/dialog_pattern_tab.xml
index 0c7dd1bc..fed5d465 100644
--- a/src/main/res/layout/dialog_pattern_tab.xml
+++ b/src/main/res/layout/dialog_pattern_tab.xml
@@ -1,33 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@color/grey"
-        android:orientation="vertical" >
-
-    <Spinner
-        android:id="@+id/pattern_category_spinner"
-        android:background="@drawable/ui_small_spinner"
-        android:layout_marginLeft="20dp"
-        android:layout_marginRight="20dp"
-        android:layout_marginTop="10dp"
-        android:layout_marginBottom="10dp"
-        android:layout_width="match_parent"
-        android:layout_height="50dp"/>
-
-    <ScrollView
-        android:id="@+id/tabScrollView"
-        android:paddingBottom="10dp"
-        android:background="@color/grey"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-        <LinearLayout
-            android:id="@+id/tabLayout"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical">
-        </LinearLayout>
-    </ScrollView>
-
-</LinearLayout>
+<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/patternListView"
+    android:background="@color/grey"
+    android:paddingLeft="10dp"
+    android:paddingRight="10dp"
+    android:paddingTop="10dp"
+    android:paddingBottom="10dp"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    />
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index f31e65b3..713299cb 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -1,7 +1,7 @@
 <resources>
     <string name="app_name">Magic Cube</string>
-    <string name="distorted">DISTORTED</string>
-    <string name="opengl_error">Graphics Error</string>
+    <string name="distorted" translatable="false">DISTORTED</string>
+    <string name="opengl_error" translatable="false">Graphics Error</string>
     <string name="scramble">Scramble</string>
     <string name="solve">Solve</string>
     <string name="exit">Exit</string>
@@ -58,4 +58,5 @@
     <string name="ap_placeholder">%1$s %2$s</string>
     <string name="ti_placeholder">%1$.1f seconds</string>
     <string name="mo_placeholder">%1$d/%2$d</string>
+    <string name="sq_placeholder" translatable="false">%1$2d.</string>
 </resources>
