commit b42c8399c189580d59537afe4e75ad49eca83f38
Author: leszek <leszek@koltunski.pl>
Date:   Fri Nov 3 14:50:01 2023 +0100

    Major progress with version 2.0.0.

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 359f9f35..9eca4d61 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -20,7 +20,10 @@
             android:value="${crashlyticsCollectionEnabled}"
         />
 
-        <activity android:name="org.distorted.main.MainActivity" android:exported="true" android:screenOrientation="portrait">
+        <activity android:name="org.distorted.main.MainActivity"
+                  android:exported="true"
+                  android:screenOrientation="portrait"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index 8e461c42..7816b33e 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -13,6 +13,7 @@ import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
 import android.util.DisplayMetrics;
@@ -25,6 +26,7 @@ import android.widget.LinearLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.preference.PreferenceManager;
 
@@ -57,15 +59,12 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
                                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 
-    private static final float RATIO_INSET= 0.09f;
-
     private boolean mJustStarted;
     private FirebaseAnalytics mFirebaseAnalytics;
-    private static int mScreenWidth, mScreenHeight;
     private int mCurrentApiVersion;
-    private int mHeightUpperBar;
     private String mOldVersion, mCurrVersion;
     private int mSolverIndex;
+    private int mScreenWidth;
     private TextView mBubbleUpdates;
     private int mNumUpdates;
     private MainScrollGrid mGrid;
@@ -83,11 +82,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       mJustStarted = true;
       mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
 
-      DisplayMetrics displaymetrics = new DisplayMetrics();
-      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
-      mScreenWidth =displaymetrics.widthPixels;
-      mScreenHeight=displaymetrics.heightPixels;
-
       cutoutHack();
       computeHeights();
 
@@ -111,24 +105,17 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
 
     private void computeHeights()
       {
-      int barHeight    = (int)(mScreenHeight*RATIO_BAR);
-      int scrollHeight = (int)(mScreenHeight*(1-2*RATIO_BAR));
-      mHeightUpperBar  = barHeight;
-
+      LinearLayout.LayoutParams pU = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, RATIO_BAR);
       LinearLayout layoutTop = findViewById(R.id.upperBar);
-      ViewGroup.LayoutParams paramsTop = layoutTop.getLayoutParams();
-      paramsTop.height = barHeight;
-      layoutTop.setLayoutParams(paramsTop);
-
-      LinearLayout layoutBot = findViewById(R.id.lowerBar);
-      ViewGroup.LayoutParams paramsBot = layoutBot.getLayoutParams();
-      paramsBot.height = barHeight;
-      layoutBot.setLayoutParams(paramsBot);
+      layoutTop.setLayoutParams(pU);
 
+      LinearLayout.LayoutParams pS = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1-2*RATIO_BAR);
       ScrollView scroll = findViewById(R.id.objectScroll);
-      ViewGroup.LayoutParams paramsScroll = scroll.getLayoutParams();
-      paramsScroll.height = scrollHeight;
-      scroll.setLayoutParams(paramsScroll);
+      scroll.setLayoutParams(pS);
+
+      LinearLayout.LayoutParams pL = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, RATIO_BAR);
+      LinearLayout layoutBot = findViewById(R.id.lowerBar);
+      layoutBot.setLayoutParams(pL);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -164,16 +151,25 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       {
       super.onAttachedToWindow();
 
+      getWindowWidth(getResources().getConfiguration());
+      mGrid = new MainScrollGrid();
+      mGrid.createGrid(this,mScreenWidth);
+
       if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
         {
         DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
         int insetHeight = cutout!=null ? cutout.getSafeInsetTop() : 0;
 
-        LinearLayout layoutHid = findViewById(R.id.hiddenBar);
-        ViewGroup.LayoutParams paramsHid = layoutHid.getLayoutParams();
-        paramsHid.height = (int)(insetHeight*RATIO_INSET);
-        layoutHid.setLayoutParams(paramsHid);
-        mHeightUpperBar += paramsHid.height;
+        if( insetHeight>0 )
+          {
+          LinearLayout.LayoutParams pH = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, RATIO_BAR);
+          LinearLayout layoutHid = findViewById(R.id.hiddenBar);
+          layoutHid.setLayoutParams(pH);
+
+          LinearLayout.LayoutParams pS = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1-3*RATIO_BAR);
+          ScrollView scroll = findViewById(R.id.objectScroll);
+          scroll.setLayoutParams(pS);
+          }
         }
       }
 
@@ -188,6 +184,16 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
         }
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void getWindowWidth(Configuration conf)
+      {
+      int dpi = getResources().getDisplayMetrics().densityDpi;
+      float conv = ((float) dpi/DisplayMetrics.DENSITY_DEFAULT);
+
+      mScreenWidth = (int) (conf.screenWidthDp*conv + 0.5f);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
@@ -201,6 +207,17 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
         }
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration conf)
+      {
+      super.onConfigurationChanged(conf);
+
+      getWindowWidth(conf);
+      mGrid.updateGrid(this,mScreenWidth);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
     
     @Override
@@ -229,9 +246,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       network.signUpForUpdates(this);
       network.downloadUpdates(this);
 
-      mGrid = new MainScrollGrid();
-      mGrid.createGrid(this,mScreenWidth,mScreenHeight);
-
       if( mJustStarted )
         {
         mJustStarted = false;
@@ -312,34 +326,40 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
         }
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public FirebaseAnalytics getAnalytics()
+    private void updateBubble(int num)
       {
-      return mFirebaseAnalytics;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+      runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          mNumUpdates = num;
 
-    public int getHeightUpperBar()
-      {
-      return mHeightUpperBar;
+          if( num>0 )
+            {
+            String shownNum = String.valueOf(num);
+            mBubbleUpdates.setText(shownNum);
+            mBubbleUpdates.setVisibility(View.VISIBLE);
+            int height = (int)(0.05f*mScreenWidth);
+            mBubbleUpdates.setTextSize(TypedValue.COMPLEX_UNIT_PX,height);
+            }
+          else
+            {
+            mBubbleUpdates.setVisibility(View.INVISIBLE);
+            }
+          }
+        });
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenWidthInPixels()
-      {
-      return mScreenWidth;
-      }
-
+// PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public int getScreenHeightInPixels()
+    public FirebaseAnalytics getAnalytics()
       {
-      return mScreenHeight;
+      return mFirebaseAnalytics;
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -436,33 +456,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       diag.show(getSupportFragmentManager(), RubikDialogCreators.getDialogTag() );
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void updateBubble(int num)
-      {
-      runOnUiThread(new Runnable()
-        {
-        @Override
-        public void run()
-          {
-          mNumUpdates = num;
-
-          if( num>0 )
-            {
-            String shownNum = String.valueOf(num);
-            mBubbleUpdates.setText(shownNum);
-            mBubbleUpdates.setVisibility(View.VISIBLE);
-            int height = (int)(0.05f*mScreenWidth);
-            mBubbleUpdates.setTextSize(TypedValue.COMPLEX_UNIT_PX,height);
-            }
-          else
-            {
-            mBubbleUpdates.setVisibility(View.INVISIBLE);
-            }
-          }
-        });
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void receiveUpdate(RubikUpdates updates)
@@ -477,7 +470,7 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       {
       mNumUpdates--;
       updateBubble(mNumUpdates);
-      mGrid.updateGrid(this,mScreenWidth,mScreenHeight);
+      mGrid.updateGrid(this,mScreenWidth);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/main/MainObjectPopup.java b/src/main/java/org/distorted/main/MainObjectPopup.java
new file mode 100644
index 00000000..5272e3af
--- /dev/null
+++ b/src/main/java/org/distorted/main/MainObjectPopup.java
@@ -0,0 +1,197 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.main;
+
+import static android.view.View.GONE;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class MainObjectPopup
+  {
+  private static final float MENU_TEXT_SIZE= 0.024f;
+  private static final float MENU_MARGIN   = 0.008f;
+  private static final float BUTTON_HEIGHT = 0.150f;
+  private static final float MENU_WIDTH    = 0.550f;
+
+  public static final int LEVELS_SHOWN = 8;
+
+  private final PopupWindow mPopup;
+  private final int mMenuTextSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MainObjectPopup(Activity act, int ordinal, int width, int height)
+    {
+    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    final View layout = layoutInflater.inflate(R.layout.object_popup, null);
+    mPopup = new PopupWindow(act);
+    mPopup.setContentView(layout);
+    mPopup.setFocusable(true);
+
+    mMenuTextSize = (int)(height*MENU_TEXT_SIZE);
+    int padding   = (int)(height*MENU_MARGIN);
+    int marginH   = padding/2;
+    int marginV   =-padding/4;
+    layout.setPadding(padding,0,padding,0);
+    int levelHeight = (int)(width*BUTTON_HEIGHT);
+
+    RubikObject object = RubikObjectList.getObject(ordinal);
+
+    Button b1 = layout.findViewById(R.id.objectSolver);
+
+    if( object!=null && object.hasSolver() )
+      {
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
+      params.setMargins(marginH,marginV,marginH,marginV);
+      b1.setLayoutParams(params);
+      b1.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+      b1.setOnClickListener(new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+
+          }
+        });
+      }
+    else b1.setVisibility(GONE);
+
+    Button b2 = layout.findViewById(R.id.objectPattern);
+
+    if( object!=null && object.hasPatterns() )
+      {
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
+      params.setMargins(marginH,marginV,marginH,marginV);
+      b2.setLayoutParams(params);
+      b2.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+      b2.setOnClickListener(new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+
+          }
+        });
+      }
+    else b2.setVisibility(GONE);
+
+    Button b3 = layout.findViewById(R.id.objectTutorial);
+
+    if( object!=null && object.hasExtras() )
+      {
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
+      params.setMargins(marginH,marginV,marginH,marginV);
+      b3.setLayoutParams(params);
+      b3.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+      b3.setOnClickListener(new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+
+          }
+        });
+      }
+    else b3.setVisibility(GONE);
+
+    Button b4 = layout.findViewById(R.id.objectInfo);
+
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
+    params.setMargins(marginH,marginV,marginH,marginV);
+    b4.setLayoutParams(params);
+    b4.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+    b4.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+
+        }
+      });
+
+    TextView levels = layout.findViewById(R.id.objectLevels);
+    levels.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+
+    setupLevelButtons(act,object,layout,width,padding,marginH);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupLevelButtons(Activity act, RubikObject object, View layout, int width,int padding, int margin)
+    {
+    int layoutWidth = (int)(width*MENU_WIDTH);
+    int levelHeight = (int)(width*BUTTON_HEIGHT);
+    int levelWidth  = (layoutWidth-4*padding)/3;
+
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(levelWidth,levelHeight);
+    params.setMargins(margin,-margin,margin,-margin);
+
+    Button[] level = new Button[LEVELS_SHOWN+1];
+
+    level[0] = layout.findViewById(R.id.level0);
+    level[1] = layout.findViewById(R.id.level1);
+    level[2] = layout.findViewById(R.id.level2);
+    level[3] = layout.findViewById(R.id.level3);
+    level[4] = layout.findViewById(R.id.level4);
+    level[5] = layout.findViewById(R.id.level5);
+    level[6] = layout.findViewById(R.id.level6);
+    level[7] = layout.findViewById(R.id.level7);
+    level[8] = layout.findViewById(R.id.levelM);
+
+    int numScramble = object==null ? 1 : object.getNumScramble();
+    int min = Math.min(numScramble,LEVELS_SHOWN);
+
+    if( numScramble>=1 && numScramble<=7 )
+      {
+      level[numScramble].setText(R.string.levelM);
+      for(int i=numScramble+1; i<=8; i++) level[i].setVisibility(GONE);
+      }
+
+    for(int i=0; i<=min; i++)
+      {
+      final int ii = i;
+      level[i].setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+      level[i].setLayoutParams(params);
+      level[i].setPadding(0,0,0,0);
+      level[i].setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void show(View v)
+    {
+    View popupView = mPopup.getContentView();
+    popupView.setSystemUiVisibility(MainActivity.FLAGS);
+    mPopup.showAtLocation(v, Gravity.CENTER, 0, 0);
+    }
+  }
+
diff --git a/src/main/java/org/distorted/main/MainScrollGrid.java b/src/main/java/org/distorted/main/MainScrollGrid.java
index b9f526c6..f56efe9d 100644
--- a/src/main/java/org/distorted/main/MainScrollGrid.java
+++ b/src/main/java/org/distorted/main/MainScrollGrid.java
@@ -9,12 +9,13 @@
 
 package org.distorted.main;
 
+import android.app.Activity;
+import android.util.DisplayMetrics;
 import android.view.View;
 import android.widget.GridLayout;
 import android.widget.ImageButton;
 
 import org.distorted.helpers.PopupCreator;
-import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 
@@ -22,23 +23,30 @@ import org.distorted.objects.RubikObjectList;
 
 public class MainScrollGrid
   {
-  public static final int NUM_COLUMNS  = 5;
+  public static final int   NUM_COLUMNS  = 5;
+  public static final float POPUP_PADDING= 0.035f;
+  public static final float POPUP_MARGIN = 0.024f;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void createGrid(final MainActivity act, int scrW, int scrH)
+  void createGrid(final Activity act, int windowWidth)
     {
     int numObjects = RubikObjectList.getNumObjects();
     int rowCount   = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
     int colCount   = NUM_COLUMNS;
-    int objectSize = scrW / NUM_COLUMNS;
-    int margin     = (int)(scrH*RubikActivity.POPUP_MARGIN);
-    int padding    = (int)(scrH*RubikActivity.POPUP_PADDING);
+    int objectSize = windowWidth / NUM_COLUMNS;
+    int margin     = (int)(windowWidth*POPUP_MARGIN);
+    int padding    = (int)(windowWidth*POPUP_PADDING);
     int cubeSize   = objectSize - 2*margin;
 
     GridLayout objectGrid = act.findViewById(R.id.objectGrid);
+    objectGrid.removeAllViews();
+
     PopupCreator.createObjectGrid(objectGrid,act,rowCount,colCount,numObjects,margin,cubeSize,padding);
 
+    DisplayMetrics displaymetrics = new DisplayMetrics();
+    act.getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+
     for(int child=0; child<numObjects; child++)
       {
       final RubikObject obj = RubikObjectList.getObject(child);
@@ -51,7 +59,10 @@ public class MainScrollGrid
         @Override
         public void onClick(View v)
           {
-          android.util.Log.e("D", "object "+ordinal+" clicked!");
+          int w = displaymetrics.widthPixels;
+          int h = displaymetrics.heightPixels;
+          MainObjectPopup popup = new MainObjectPopup(act,ordinal,w,h);
+          popup.show(v);
           }
         });
       }
@@ -59,14 +70,14 @@ public class MainScrollGrid
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void updateGrid(final MainActivity act, int scrW, int scrH)
+  void updateGrid(final Activity act, int scrW)
     {
     act.runOnUiThread(new Runnable()
       {
       @Override
       public void run()
         {
-        createGrid(act,scrW,scrH);
+        createGrid(act,scrW);
         }
       });
     }
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index 8a808830..f34968f2 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -29,6 +29,7 @@ import org.distorted.main.R;
 import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.patterns.RubikPatternList;
+import org.distorted.solvers.ImplementedSolversList;
 
 import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
 import static org.distorted.main_old.RubikActivity.SHOW_DOWNLOADED_DEBUG;
@@ -50,6 +51,7 @@ public class RubikObject
   private int mNumScramble;
   private int mMeshState;
   private int mExtrasOrdinal;
+  private int mSolverOrdinal;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -70,6 +72,8 @@ public class RubikObject
     int patternOrdinal  = RubikPatternList.getOrdinal(ordinal);
     mPatterns = RubikPatternList.getPatterns(patternOrdinal);
 
+    mSolverOrdinal = ImplementedSolversList.getSolverOrdinal(ordinal);
+
     mMeshState = MESH_NICE;
     mExtrasOrdinal = -1;
 
@@ -94,6 +98,7 @@ public class RubikObject
     mPatterns      = null;
     mMeshState     = MESH_NICE;
     mExtrasOrdinal = -1;
+    mSolverOrdinal = -1;
 
     mMeshID        =  0;
     mJsonID        = -1;
@@ -304,6 +309,27 @@ public class RubikObject
     return mExtrasID!=0;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean hasSolver()
+    {
+    return mSolverOrdinal>=0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getSolverOrdinal()
+    {
+    return mSolverOrdinal;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean hasPatterns()
+    {
+    return mPatterns!=null;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public String[][] getPatterns()
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index f96da6c7..53ee542a 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -596,7 +596,7 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // work around lame bugs in Android's version <= 10 pop-up and split-screen modes
 
-  private void displayPopup(RubikActivity act, View view, PopupWindow window, int w, int h, int xoff, int yoff)
+  private void displayPopup(Activity act, View view, PopupWindow window, int w, int h, int xoff, int yoff)
     {
     View topLayout = act.findViewById(R.id.relativeLayout);
     boolean isFullScreen;
diff --git a/src/main/java/org/distorted/solvers/ImplementedSolversList.java b/src/main/java/org/distorted/solvers/ImplementedSolversList.java
index f89117fa..4e2816ac 100644
--- a/src/main/java/org/distorted/solvers/ImplementedSolversList.java
+++ b/src/main/java/org/distorted/solvers/ImplementedSolversList.java
@@ -53,9 +53,19 @@ public enum ImplementedSolversList
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public static ImplementedSolversList getSolver(int ordinal)
+  public static ImplementedSolversList getSolver(int solverOrdinal)
     {
-    return objects[ordinal];
+    return objects[solverOrdinal];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getSolverOrdinal(int objectOrdinal)
+    {
+    for(int o=0; o<NUM_OBJECTS; o++)
+      if( objects[o].mObject==objectOrdinal ) return o;
+
+    return -1;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/res/layout/new_main.xml b/src/main/res/layout/new_main.xml
index 0847d80a..e0e8e65d 100644
--- a/src/main/res/layout/new_main.xml
+++ b/src/main/res/layout/new_main.xml
@@ -3,12 +3,14 @@
     android:id="@+id/relativeLayout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:weightSum="1.0"
     android:orientation="vertical">
 
     <LinearLayout
         android:id="@+id/hiddenBar"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="0.00"
         android:gravity="center"
         android:orientation="horizontal"
         android:background="@color/dark_grey">
@@ -17,7 +19,8 @@
     <LinearLayout
         android:id="@+id/upperBar"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="0.08"
         android:gravity="center"
         android:weightSum="1.0"
         android:orientation="horizontal"
@@ -77,7 +80,8 @@
     <ScrollView
         android:id="@+id/objectScroll"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="0.84"
         android:background="@color/grey">
 
         <GridLayout
@@ -91,7 +95,8 @@
     <LinearLayout
         android:id="@+id/lowerBar"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
+        android:layout_weight="0.08"
         android:orientation="horizontal"
         android:background="@color/dark_grey">
 
diff --git a/src/main/res/layout/object_popup.xml b/src/main/res/layout/object_popup.xml
new file mode 100644
index 00000000..80be915d
--- /dev/null
+++ b/src/main/res/layout/object_popup.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+   android:id="@+id/objectGrid"
+   android:layout_width="wrap_content"
+   android:layout_height="wrap_content"
+   android:gravity="center"
+    android:layout_marginTop="0dp"
+    android:layout_marginBottom="0dp"
+    android:padding="0dp"
+   android:orientation="vertical">
+
+   <Button
+      android:id="@+id/objectSolver"
+      android:text="@string/object_solver"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingRight="10dp"
+      android:paddingLeft="10dp"
+      android:singleLine="true"
+      android:backgroundTint="@color/dark_grey"
+      android:gravity="center"/>
+
+   <Button
+      android:id="@+id/objectPattern"
+      android:text="@string/object_pattern"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingRight="10dp"
+      android:paddingLeft="10dp"
+      android:singleLine="true"
+      android:backgroundTint="@color/dark_grey"
+      android:gravity="center"/>
+
+   <Button
+      android:id="@+id/objectTutorial"
+      android:text="@string/object_tutorial"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingRight="10dp"
+      android:paddingLeft="10dp"
+      android:singleLine="true"
+      android:backgroundTint="@color/dark_grey"
+      android:gravity="center"/>
+
+   <Button
+        android:id="@+id/objectInfo"
+        android:text="@string/object_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingRight="10dp"
+        android:paddingLeft="10dp"
+        android:singleLine="true"
+        android:backgroundTint="@color/dark_grey"
+        android:gravity="center"/>
+
+   <TextView
+       android:id="@+id/objectLevels"
+       android:layout_width="match_parent"
+       android:text="@string/levels"
+       android:layout_height="wrap_content"
+       android:gravity="center"/>
+
+   <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="left"
+        android:orientation="horizontal">
+
+        <Button
+           android:id="@+id/level0"
+           android:text="@string/level0"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+        <Button
+           android:id="@+id/level1"
+           android:text="@string/level1"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+        <Button
+           android:id="@+id/level2"
+           android:text="@string/level2"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+   </LinearLayout>
+
+   <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="left"
+        android:orientation="horizontal">
+
+        <Button
+           android:id="@+id/level3"
+           android:text="@string/level3"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+        <Button
+           android:id="@+id/level4"
+           android:text="@string/level4"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+        <Button
+           android:id="@+id/level5"
+           android:text="@string/level5"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+   </LinearLayout>
+
+   <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="left"
+        android:orientation="horizontal">
+
+        <Button
+           android:id="@+id/level6"
+           android:text="@string/level6"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+        <Button
+           android:id="@+id/level7"
+           android:text="@string/level7"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+        <Button
+           android:id="@+id/levelM"
+           android:text="@string/levelM"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:paddingRight="10dp"
+           android:paddingLeft="10dp"
+           android:backgroundTint="@color/dark_grey"/>
+   </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 677d598e..14ad248c 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -54,12 +54,17 @@
     <string name="email">Melden Sie einen Fehler, schlagen Sie eine Funktion vor:</string>
     <string name="exit_app">App beenden?</string>
 
+    <string name="object_solver">Löser</string>
+    <string name="object_pattern">Muster</string>
+    <string name="object_tutorial">Tutorials</string>
+    <string name="object_info">Info</string>
+
     <string name="stars">Sterne</string>
     <string name="scores">Highscores</string>
     <string name="patterns">Hübsche Muster</string>
     <string name="control">Den Würfel steuern</string>
     <string name="solver">Löser</string>
-    <string name="tutorials">Lernprogrammen</string>
+    <string name="tutorials">Tutorials</string>
     <string name="about">Über die App</string>
     <string name="bandaged">Bandaged</string>
 
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 019b1b70..190eb6cc 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">Reportar un error, sugerir una función:</string>
     <string name="exit_app">¿Salir de la aplicación?</string>
 
+    <string name="object_solver">Solucionador</string>
+    <string name="object_pattern">Patrones</string>
+    <string name="object_tutorial">Tutoriales</string>
+    <string name="object_info">Información</string>
+
     <string name="stars">Estrellas</string>
     <string name="scores">Leaderboard</string>
     <string name="patterns">Patrones</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index ac17021c..92c41e85 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">Signaler un bug, suggérer une fonctionnalité :</string>
     <string name="exit_app">Quitter l\'application ?</string>
 
+    <string name="object_solver">Solveur</string>
+    <string name="object_pattern">Motifs</string>
+    <string name="object_tutorial">Tutoriels</string>
+    <string name="object_info">Info</string>
+
     <string name="stars">Étoiles</string>
     <string name="scores">Meilleurs scores</string>
     <string name="patterns">Jolis motifs</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index f012e170..8a536de3 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">バグを報告し、機能を提案してください:</string>
     <string name="exit_app">アプリを終了しますか?</string>
 
+    <string name="object_solver">ソルバー</string>
+    <string name="object_pattern">パターン</string>
+    <string name="object_tutorial">チュートリアル</string>
+    <string name="object_info">情報</string>
+
     <string name="stars">星</string>
     <string name="scores">ハイスコア</string>
     <string name="patterns">プリティパターン</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index e29954be..acb9dcbc 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">버그 신고, 기능 제안:</string>
     <string name="exit_app">앱을 종료하시겠습니까?</string>
 
+    <string name="object_solver">솔버</string>
+    <string name="object_pattern">패턴</string>
+    <string name="object_tutorial">튜토리얼</string>
+    <string name="object_info">정보</string>
+
     <string name="stars">별</string>
     <string name="scores">고득점</string>
     <string name="patterns">예쁜 패턴</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index ab4c7b50..f218f0a1 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">Zaraportuj błąd, zadaj pytanie:</string>
     <string name="exit_app">Wyjść z apki?</string>
 
+    <string name="object_solver">Rozwiązywacz</string>
+    <string name="object_pattern">Wzory</string>
+    <string name="object_tutorial">Tutoriale</string>
+    <string name="object_info">Info</string>
+
     <string name="stars">Gwiazdki</string>
     <string name="scores">Lista najlepszych</string>
     <string name="patterns">Piękne Wzory</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 962fa508..8625bb50 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">Сообщить об ошибке, задать вопрос:</string>
     <string name="exit_app">Выйти из приложения?</string>
 
+    <string name="object_solver">Решатель</string>
+    <string name="object_pattern">Узоры</string>
+    <string name="object_tutorial">Учебники</string>
+    <string name="object_info">Инфо</string>
+
     <string name="stars">Звезды</string>
     <string name="scores">Высокие баллы</string>
     <string name="patterns">Красивые узоры</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index bbebd536..c4b875da 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">报告错误，提出问题：</string>
     <string name="exit_app">退出应用程序？</string>
 
+    <string name="object_solver">求解器</string>
+    <string name="object_pattern">图案</string>
+    <string name="object_tutorial">教程</string>
+    <string name="object_info">信息</string>
+
     <string name="stars">星星</string>
     <string name="scores">高分</string>
     <string name="patterns">模式</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index fea1a7c5..2873c6c9 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -54,6 +54,11 @@
     <string name="email">报告错误，提出问题：</string>
     <string name="exit_app">退出應用程式？</string>
 
+    <string name="object_solver">求解器</string>
+    <string name="object_pattern">圖案</string>
+    <string name="object_tutorial">教學</string>
+    <string name="object_info">資訊</string>
+
     <string name="stars">星星</string>
     <string name="scores">高分</string>
     <string name="patterns">模式</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 7ebd4c2e..fde21d7f 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -71,6 +71,12 @@
     <string name="buy_string1">You have %1$d stars.</string>
     <string name="buy_string2">Earn stars by solving puzzles. You can also buy stars.</string>
 
+    <string name="object_solver">Solver</string>
+    <string name="object_pattern">Patterns</string>
+    <string name="object_tutorial">Tutorials</string>
+    <string name="object_info">Info</string>
+
+    <string name="level0" translatable="false">0</string>
     <string name="level1" translatable="false">1</string>
     <string name="level2" translatable="false">2</string>
     <string name="level3" translatable="false">3</string>
