commit 0b5e585cb8c00a1dd32b0f9da5a2c056eba34550
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Aug 11 18:09:46 2022 +0200

    Sort the objects in the object popup by difficulty level.

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java b/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java
index 441bd65a..7a18fe8d 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java
@@ -194,8 +194,15 @@ public class RubikDialogUpdateView implements RubikNetwork.Downloadee
         try
           {
           JsonReader reader = JsonReader.getInstance();
-          mInfo.mNumScrambles = reader.readNumScrambles(act,objectName);
-          if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "Read from JSON numScrambles="+mInfo.mNumScrambles);
+          reader.readNumScramblesAndComplexity(act,objectName);
+          mInfo.mNumScrambles = reader.getNumScrambles();
+          mInfo.mDifficulty   = reader.getComplexity();
+
+          if( SHOW_DOWNLOADED_DEBUG )
+            {
+            android.util.Log.e("D", "Read from JSON numScrambles="+mInfo.mNumScrambles);
+            android.util.Log.e("D", "Read from JSON difficulty="+mInfo.mDifficulty);
+            }
 
           if( mInfo.mExtrasStream!=null )
             {
@@ -211,8 +218,8 @@ public class RubikDialogUpdateView implements RubikNetwork.Downloadee
 
             if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "1");
 
-            boolean success = RubikObjectList.addDownloadedObject(act, mInfo.mObjectShortName,mInfo.mNumScrambles, mInfo.mObjectMinorVersion,
-                                                                  mInfo.mExtrasMinorVersion, mIconSaved, oSuccess, eSuccess);
+            boolean success = RubikObjectList.addDownloadedObject(act, mInfo.mObjectShortName,mInfo.mNumScrambles, mInfo.mDifficulty,
+                                                                  mInfo.mObjectMinorVersion, mInfo.mExtrasMinorVersion, mIconSaved, oSuccess, eSuccess);
             if( success )
               {
               if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "2");
diff --git a/src/main/java/org/distorted/external/RubikUpdates.java b/src/main/java/org/distorted/external/RubikUpdates.java
index 7ede9f02..ba6d0d1d 100644
--- a/src/main/java/org/distorted/external/RubikUpdates.java
+++ b/src/main/java/org/distorted/external/RubikUpdates.java
@@ -36,6 +36,7 @@ public class RubikUpdates
     public final boolean mUpdateExtras;
 
     public int mNumScrambles;
+    public int mDifficulty;
     public Bitmap mIcon;
     public InputStream mObjectStream;
     public InputStream mExtrasStream;
@@ -55,6 +56,7 @@ public class RubikUpdates
 
       mIcon = null;
       mNumScrambles = 0;
+      mDifficulty   = 0;
       }
     }
 
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index e0c2a2f7..94eb913d 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -40,6 +40,7 @@ public class RubikObject
   private final String mLowerName, mUpperName;
   private final int mIconID;
   private final String[][] mPatterns;
+  private final int mDifficulty;
 
   private int mJsonID, mMeshID, mExtrasID;
   private int mObjectMinor, mExtrasMinor;
@@ -57,6 +58,7 @@ public class RubikObject
     mUpperName   = type.name();
     mLowerName   = type.name().toLowerCase(Locale.ENGLISH);
     mNumScramble = type.getNumScramble();
+    mDifficulty  = type.getDifficulty();
 
     mIconID      = type.getIconID();
     mJsonID      = ObjectJson.getObjectJsonID(ordinal);
@@ -85,6 +87,7 @@ public class RubikObject
     mLowerName     = object.shortName;
     mUpperName     = object.shortName.toUpperCase(Locale.ENGLISH);
     mNumScramble   = object.numScrambles;
+    mDifficulty    = object.difficulty;
     mObjectMinor   = object.objectMinor;
     mExtrasMinor   = object.extrasMinor;
 
@@ -204,6 +207,13 @@ public class RubikObject
     return mUpperName;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getDifficulty()
+    {
+    return mDifficulty;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public int getNumScramble()
diff --git a/src/main/java/org/distorted/objects/RubikObjectList.java b/src/main/java/org/distorted/objects/RubikObjectList.java
index 4c4ff9ab..e5c16920 100644
--- a/src/main/java/org/distorted/objects/RubikObjectList.java
+++ b/src/main/java/org/distorted/objects/RubikObjectList.java
@@ -17,6 +17,7 @@ import android.content.SharedPreferences;
 
 import org.distorted.external.RubikFiles;
 import org.distorted.main.RubikActivity;
+import org.distorted.objectlib.json.JsonReader;
 import org.distorted.objectlib.main.ObjectSignatures;
 import org.distorted.objectlib.main.ObjectType;
 
@@ -28,6 +29,7 @@ import static org.distorted.main.RubikActivity.SHOW_DOWNLOADED_DEBUG;
 
 public class RubikObjectList
 {
+  public static final int NUM_DIFFICULTIES = 5;
   public static final int DEF_OBJECT= ObjectSignatures.CUBE_3;
   private static RubikObjectList mThis;
   private static int mNumObjects;
@@ -39,13 +41,14 @@ public class RubikObjectList
     {
     String shortName;
     boolean icon,object,extras;
-    int numScrambles, objectMinor, extrasMinor;
+    int numScrambles, difficulty, objectMinor, extrasMinor;
 
-    DownloadedObject(String sName, int scrambles, int oMinor, int eMinor, boolean i, boolean o, boolean e)
+    DownloadedObject(String sName, int scrambles, int diff, int oMinor, int eMinor, boolean i, boolean o, boolean e)
       {
       shortName = sName;
 
       numScrambles= scrambles;
+      difficulty  = diff;
       objectMinor = oMinor;
       extrasMinor = eMinor;
 
@@ -56,6 +59,7 @@ public class RubikObjectList
     }
 
   private static ArrayList<DownloadedObject> mDownloadedObjects;
+  private static int[][] mDifficultyOrdinals;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -130,10 +134,57 @@ public class RubikObjectList
     return true;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static void createDifficultyList(int difficulty)
+    {
+    int num=0;
+
+    for(int i=0; i<mNumObjects; i++)
+      {
+      RubikObject object = mObjects.get(i);
+      if( object.getDifficulty()==difficulty ) num++;
+      }
+
+    if( num>0 )
+      {
+      mDifficultyOrdinals[difficulty] = new int[num];
+      num=0;
+
+      for(int i=0; i<mNumObjects; i++)
+        {
+        RubikObject object = mObjects.get(i);
+        if( object.getDifficulty()==difficulty )
+          {
+          mDifficultyOrdinals[difficulty][num] = i;
+          num++;
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static int figureOutDifficulty(Context context, String objectName)
+    {
+    JsonReader reader = JsonReader.getInstance();
+
+    try
+      {
+      reader.readNumScramblesAndComplexity(context,objectName);
+      return reader.getComplexity();
+      }
+    catch(Exception ex)
+      {
+      android.util.Log.e("D", "exception "+ex.getMessage()+" trying to read complexity of a puzzle "+objectName);
+      return 0;
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 
-  public static boolean addDownloadedObject(Context context, String shortName, int numScrambles, int objectMinor,
+  public static boolean addDownloadedObject(Context context, String shortName, int numScrambles, int difficulty, int objectMinor,
                                          int extrasMinor, boolean icon, boolean object, boolean extras)
     {
     if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "New downloaded object "+shortName+" icon="+icon+" object="+object+" extras="+extras);
@@ -171,7 +222,7 @@ public class RubikObjectList
     if( !object ) objectMinor=-1;
     if( !extras ) extrasMinor=-1;
 
-    DownloadedObject obj = new DownloadedObject(shortName,numScrambles,objectMinor,extrasMinor,icon,object,extras);
+    DownloadedObject obj = new DownloadedObject(shortName,numScrambles,difficulty,objectMinor,extrasMinor,icon,object,extras);
     if ( internalAddDownloadedObject(obj) )
       {
       if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "Adding new downloaded object "+shortName+" icon="+obj.icon+" object="+obj.object+" extras="+obj.extras);
@@ -234,6 +285,8 @@ public class RubikObjectList
         downloadedObjects.append(object.object ? "1":"0");
         downloadedObjects.append(' ');
         downloadedObjects.append(object.extras ? "1":"0");
+        downloadedObjects.append(' ');
+        downloadedObjects.append(object.difficulty);
         }
 
       String objects = downloadedObjects.toString();
@@ -280,7 +333,7 @@ public class RubikObjectList
         {
         String[] parts = dObj.split(" ");
 
-        if( parts.length==7 )
+        if( parts.length==7 || parts.length==8 )
           {
           String name = parts[0];
           String scra = parts[1];
@@ -290,6 +343,7 @@ public class RubikObjectList
           String obje = parts[5];
           String extr = parts[6];
 
+          int difficulty= parts.length==7 ? figureOutDifficulty(context,name) : Integer.parseInt(parts[7]);
           int scrambles = Integer.parseInt(scra);
           int oMinor    = Integer.parseInt(objM);
           int eMinor    = Integer.parseInt(extM);
@@ -298,7 +352,7 @@ public class RubikObjectList
           boolean bObje = obje.equals("1");
           boolean bExtr = extr.equals("1");
 
-          addDownloadedObject(context,name,scrambles,oMinor,eMinor,bIcon,bObje,bExtr);
+          addDownloadedObject(context,name,scrambles,difficulty,oMinor,eMinor,bIcon,bObje,bExtr);
           }
         }
       }
@@ -465,4 +519,22 @@ public class RubikObjectList
     RubikObject object = getObject(objectOrdinal);
     return object!=null ? object.getExtrasMinor() : -1;
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int[] produceListOfOrdinals(int difficulty)
+    {
+    if( mDifficultyOrdinals==null )
+      {
+      mDifficultyOrdinals = new int[NUM_DIFFICULTIES][];
+      }
+
+    if( difficulty>=0 && difficulty<NUM_DIFFICULTIES )
+      {
+      if( mDifficultyOrdinals[difficulty]==null ) createDifficultyList(difficulty);
+      }
+
+    return mDifficultyOrdinals[difficulty];
+    }
+
 }
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index c32ed31e..5afee625 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -25,6 +25,7 @@ import android.view.View;
 import android.widget.Button;
 import android.widget.GridLayout;
 import android.widget.ImageButton;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.widget.RelativeLayout;
@@ -52,7 +53,7 @@ import static android.view.View.inflate;
 
 public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Updatee
   {
-  public static final int NUM_COLUMNS  = 5;
+  public static final int NUM_COLUMNS  = 6;
   public static final int LEVELS_SHOWN = 8;
   private static final int NUM_BUTTONS = 6;
   private static final int[] mLocation = new int[2];
@@ -65,9 +66,10 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
   private int mObjectSize, mMenuLayoutWidth, mMenuLayoutHeight, mMenuButtonHeight, mMenuTextSize;
   private int mLevelValue;
   private int mColCount, mRowCount, mMaxRowCount;
-  private int mUpperBarHeight;
+  private int mBarHeight;
   private boolean mShouldReactToEndOfScrambling;
-  private float mScreenWidth;
+  private float mScreenWidth, mScreenHeight;
+  private int mMargin;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -80,12 +82,14 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
   void enterScreen(final RubikActivity act)
     {
-    mWeakAct = new WeakReference<>(act);
+    mWeakAct     = new WeakReference<>(act);
     mScreenWidth = act.getScreenWidthInPixels();
-    mUpperBarHeight = act.getHeightUpperBar();
+    mScreenHeight= act.getScreenHeightInPixels();
+    mBarHeight   = act.getHeightUpperBar();
 
     mMenuButtonHeight = (int)(mScreenWidth*RubikActivity.MENU_BUTTON_HEIGHT);
     mMenuTextSize     = (int)(mScreenWidth*RubikActivity.MENU_MAIN_TEXT_SIZE);
+    mMargin           = (int)(mScreenWidth*RubikActivity.LARGE_MARGIN);
 
     mObjectPopup = null;
 
@@ -101,13 +105,13 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     layoutTop.addView(mScrambleButton);
 
     // BOTTOM /////////////////////////
-    setupObjectButton(act,mScreenWidth);
+    setupObjectButton(act,mScreenWidth,mScreenHeight);
     createBottomPane(act,mObjButton,null);
     }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void setupObjectButton(final RubikActivity act, final float width)
+  private void setupObjectButton(final RubikActivity act, final float width, final float height)
     {
     final int margin  = (int)(width*RubikActivity.SMALL_MARGIN);
     final int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_menu,R.drawable.ui_medium_cube_menu, R.drawable.ui_big_cube_menu, R.drawable.ui_huge_cube_menu);
@@ -128,10 +132,12 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
         if( act.getControl().isUINotBlocked())
           {
-          int rowCount = Math.min(mMaxRowCount,mRowCount);
+          //int rowCount = Math.min(mMaxRowCount,mRowCount);
           View popupView = mObjectPopup.getContentView();
           popupView.setSystemUiVisibility(RubikActivity.FLAGS);
-          displayPopup(act,view,mObjectPopup,mObjectSize*mColCount,mObjectSize*rowCount+5*margin,margin,margin);
+          int popupWidth = (int)width;
+          int popupHeight= (int)(height-2*mBarHeight);
+          displayPopup(act,view,mObjectPopup,popupWidth,popupHeight,margin,margin);
           }
         }
       });
@@ -171,23 +177,28 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
   private void setupObjectWindow(final RubikActivity act, final float width, final float height)
     {
+    LinearLayout view = (LinearLayout)inflate( act, R.layout.popup_object, null);
+    LinearLayout layout = view.findViewById(R.id.objectLayout);
+
     int numObjects = RubikObjectList.getNumObjects();
     mRowCount = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
     mColCount = NUM_COLUMNS;
 
     int cubeSize = (int)(width/9);
-    int margin   = (int)(width*RubikActivity.LARGE_MARGIN);
-    mObjectSize  = (int)(cubeSize + 2*margin + 0.5f);
-    mMaxRowCount = (int)((height-1.8f*mUpperBarHeight)/mObjectSize);
+    mObjectSize  = (int)(cubeSize + 2*mMargin + 0.5f);
+    mMaxRowCount = (int)((height-1.8f*mBarHeight)/mObjectSize);
 
-    LinearLayout view = (LinearLayout)inflate( act, R.layout.popup_object, null);
-    GridLayout objectGrid = view.findViewById(R.id.objectGrid);
+    GridLayout section0 = produceSection(act, R.drawable.difficulty1, 0, cubeSize);
+    GridLayout section1 = produceSection(act, R.drawable.difficulty2, 1, cubeSize);
+    GridLayout section2 = produceSection(act, R.drawable.difficulty3, 2, cubeSize);
+    GridLayout section3 = produceSection(act, R.drawable.difficulty4, 3, cubeSize);
+    GridLayout section4 = produceSection(act, R.drawable.difficulty5, 4, cubeSize);
 
-    GridLayout.Spec[] rowSpecs = new GridLayout.Spec[mRowCount];
-    GridLayout.Spec[] colSpecs = new GridLayout.Spec[mColCount];
-
-    objectGrid.setColumnCount(mColCount);
-    objectGrid.setRowCount(mRowCount);
+    layout.addView(section0);
+    layout.addView(section1);
+    layout.addView(section2);
+    layout.addView(section3);
+    layout.addView(section4);
 
     RelativeLayout bottomLayout = view.findViewById(R.id.bottomLayout);
     setupBottomLayout(act,bottomLayout);
@@ -195,24 +206,61 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     mObjectPopup = new PopupWindow(act);
     mObjectPopup.setFocusable(true);
     mObjectPopup.setContentView(view);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private GridLayout produceSection(final RubikActivity act, int iconID, int difficulty, int cubeSize)
+    {
+    GridLayout objectGrid = (GridLayout)inflate( act, R.layout.popup_object_section, null);
 
-    int[] nextInRow = new int[mRowCount];
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+    params.setMargins(0, (difficulty==0? 0:mMargin) ,0,0);
+    objectGrid.setLayoutParams(params);
 
-    for(int row=0; row<mRowCount; row++)
+    int[] ordinals = RubikObjectList.produceListOfOrdinals(difficulty);
+    int numObjects = ordinals.length;
+    int colCount = NUM_COLUMNS-1;
+    int rowCount = (numObjects + colCount-1) / colCount;
+
+    GridLayout.Spec[] rowSpecs = new GridLayout.Spec[rowCount];
+    GridLayout.Spec[] colSpecs = new GridLayout.Spec[colCount+1];
+
+    objectGrid.setColumnCount(colCount+1);
+    objectGrid.setRowCount(rowCount);
+
+    int[] nextInRow = new int[rowCount];
+
+    for(int row=0; row<rowCount; row++)
       {
       rowSpecs[row] = GridLayout.spec(row);
-      nextInRow[row]= 0;
+      nextInRow[row]= 1;
       }
-    for(int col=0; col<mColCount; col++)
+    for(int col=0; col<colCount+1; col++)
       {
       colSpecs[col] = GridLayout.spec(col);
       }
 
+    GridLayout.Spec rs = rowSpecs[0];
+    GridLayout.Spec cs = colSpecs[0];
+    GridLayout.LayoutParams ps = new GridLayout.LayoutParams(rs,cs);
+    ps.bottomMargin = mMargin;
+    ps.topMargin    = mMargin;
+    ps.leftMargin   = mMargin;
+    ps.rightMargin  = mMargin;
+    ps.width        = cubeSize;
+    ps.height       = cubeSize;
+
+    ImageView image = new ImageView(act);
+    image.setImageResource(iconID);
+    objectGrid.addView(image,ps);
+
     for(int object=0; object<numObjects; object++)
       {
-      final int ordinal = object;
-      final RubikObject rObject = RubikObjectList.getObject(object);
-      int row = object/NUM_COLUMNS;
+      final int ordinal = ordinals[object];
+      final RubikObject rObject = RubikObjectList.getObject(ordinal);
+      int row = object/colCount;
       ImageButton button = new ImageButton(act);
       if( rObject!=null ) rObject.setIconTo(act,button);
 
@@ -233,19 +281,22 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
           }
         });
 
-      GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowSpecs[row],colSpecs[nextInRow[row]]);
-      params.bottomMargin = margin;
-      params.topMargin    = margin;
-      params.leftMargin   = margin;
-      params.rightMargin  = margin;
-
-      params.width = cubeSize;
-      params.height= cubeSize;
+      GridLayout.Spec r = rowSpecs[row];
+      GridLayout.Spec c = colSpecs[nextInRow[row]];
+      GridLayout.LayoutParams p = new GridLayout.LayoutParams(r,c);
+      p.bottomMargin = mMargin;
+      p.topMargin    = mMargin;
+      p.leftMargin   = mMargin;
+      p.rightMargin  = mMargin;
+      p.width        = cubeSize;
+      p.height       = cubeSize;
 
       nextInRow[row]++;
 
-      objectGrid.addView(button, params);
+      objectGrid.addView(button,p);
       }
+
+    return objectGrid;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/res/layout/popup_object.xml b/src/main/res/layout/popup_object.xml
index 82b618b7..9e046136 100644
--- a/src/main/res/layout/popup_object.xml
+++ b/src/main/res/layout/popup_object.xml
@@ -10,12 +10,11 @@
        android:layout_height="0dp"
        android:layout_weight="1">
 
-       <GridLayout
-           android:id="@+id/objectGrid"
+       <LinearLayout
+           android:id="@+id/objectLayout"
            android:layout_width="match_parent"
-           android:layout_height="wrap_content">
-       </GridLayout>
-
+           android:layout_height="wrap_content"
+           android:orientation="vertical"/>
    </ScrollView>
 
    <RelativeLayout
diff --git a/src/main/res/layout/popup_object_section.xml b/src/main/res/layout/popup_object_section.xml
new file mode 100644
index 00000000..378e735c
--- /dev/null
+++ b/src/main/res/layout/popup_object_section.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/objectSectionGrid"
+    android:background="@color/transparent_grey"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent">
+</GridLayout>
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index e58b481d..bf65ecda 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -9,6 +9,7 @@
     <color name="grey">#ff333333</color>
     <color name="light_grey">#ff555555</color>
     <color name="medium_grey">#ff444444</color>
+    <color name="transparent_grey">#77333333</color>
     <color name="black">#ff010101</color>
     <color name="white">#ffffffff</color>
 </resources>
