commit fcd5b99047d475e645777a54d53fd53ef99325c7
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sun May 2 13:56:20 2021 +0200

    Rename 'state' to 'screen'

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java b/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java
index cb015c2f..0382595f 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java
@@ -39,8 +39,8 @@ import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
 import org.distorted.objects.ObjectList;
 import org.distorted.network.RubikScores;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStatePlay;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenPlay;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -75,11 +75,11 @@ public class RubikDialogNewRecord extends AppCompatDialogFragment
         RubikScores scores = RubikScores.getInstance();
         String name = scores.getName();
         Bundle bundle = new Bundle();
-        StateList.switchState(act, StateList.PLAY);
+        ScreenList.switchState(act, ScreenList.PLAY);
 
         if( name.length()>0 )
           {
-          RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
+          RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
           int object = play.getObject();
           int size   = play.getSize();
           int sizeIndex = ObjectList.getSizeIndex(object,size);
@@ -108,7 +108,7 @@ public class RubikDialogNewRecord extends AppCompatDialogFragment
       public void onClick(DialogInterface dialog, int which)
         {
         RubikActivity act = (RubikActivity)getActivity();
-        StateList.switchState(act, StateList.PLAY);
+        ScreenList.switchState(act, ScreenList.PLAY);
         }
       });
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java b/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
index eb933e9b..b93adeff 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
@@ -31,8 +31,8 @@ import org.distorted.main.RubikActivity;
 import org.distorted.objects.ObjectList;
 import org.distorted.patterns.RubikPattern;
 import org.distorted.patterns.RubikPatternList;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStatePattern;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenPattern;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -98,8 +98,8 @@ public class RubikDialogPatternView extends FrameLayout
 
         ract.setupObject(list, size, moves);
 
-        StateList.switchState(ract,StateList.PATT);
-        RubikStatePattern state = (RubikStatePattern) StateList.PATT.getStateClass();
+        ScreenList.switchState(ract, ScreenList.PATT);
+        RubikScreenPattern state = (RubikScreenPattern) ScreenList.PATT.getStateClass();
         state.setPattern(ract, mTab, groupPosition, childPosition);
 
         mDialog.rememberState();
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java b/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java
index 2fb77cb8..75c7a610 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java
@@ -34,7 +34,7 @@ import org.distorted.main.R;
 import org.distorted.network.RubikScores;
 import org.distorted.network.RubikNetwork;
 import org.distorted.objects.ObjectList;
-import org.distorted.states.RubikStatePlay;
+import org.distorted.screens.RubikScreenPlay;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -108,7 +108,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikNetwork
     {
     prepareView();
 
-    int MAX = RubikStatePlay.LEVELS_SHOWN;
+    int MAX = RubikScreenPlay.LEVELS_SHOWN;
     int toDo=0;
     int[] toDoTab = new int[mNumTabs];
     int[] maxTab  = new int[mNumTabs];
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSetName.java b/src/main/java/org/distorted/dialogs/RubikDialogSetName.java
index 8dc254f3..91d3b181 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSetName.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSetName.java
@@ -42,8 +42,8 @@ import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
 import org.distorted.objects.ObjectList;
 import org.distorted.network.RubikScores;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStatePlay;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenPlay;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -144,9 +144,9 @@ public class RubikDialogSetName extends AppCompatDialogFragment
           name = name.replace(' ', '_');
 
           RubikActivity act = (RubikActivity)getActivity();
-          StateList.switchState(act, StateList.PLAY);
+          ScreenList.switchState(act, ScreenList.PLAY);
           RubikScores.getInstance().setName(name);
-          RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
+          RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
 
           int object = play.getObject();
           int size   = play.getSize();
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSolved.java b/src/main/java/org/distorted/dialogs/RubikDialogSolved.java
index d1d1f80f..abccb696 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSolved.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSolved.java
@@ -32,13 +32,12 @@ import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.Window;
-import android.view.WindowManager;
 import android.widget.Button;
 import android.widget.TextView;
 
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
-import org.distorted.states.StateList;
+import org.distorted.screens.ScreenList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -70,7 +69,7 @@ public class RubikDialogSolved extends AppCompatDialogFragment
       public void onClick(DialogInterface dialog, int which)
         {
         RubikActivity act = (RubikActivity)getActivity();
-        StateList.switchState(act, StateList.PLAY);
+        ScreenList.switchState(act, ScreenList.PLAY);
         }
       });
 
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 7998e000..ca9aba3b 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -45,8 +45,8 @@ import org.distorted.objects.TwistyObject;
 import org.distorted.network.RubikScores;
 import org.distorted.network.RubikNetwork;
 import org.distorted.objects.ObjectList;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStatePlay;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenPlay;
 import org.distorted.tutorials.TutorialActivity;
 
 import java.util.Locale;
@@ -242,7 +242,7 @@ public class RubikActivity extends AppCompatActivity
       view.onResume();
       view.initialize();
       restorePreferences();
-      StateList.setState(this);
+      ScreenList.setState(this);
 
       if( mJustStarted )
         {
@@ -253,7 +253,7 @@ public class RubikActivity extends AppCompatActivity
         }
 
       boolean success = false;
-      RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
+      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
       int object = play.getObject();
       int size   = play.getSize();
 
@@ -272,8 +272,8 @@ public class RubikActivity extends AppCompatActivity
 
       if( !success )
         {
-        ObjectList obj = ObjectList.getObject(RubikStatePlay.DEF_OBJECT);
-        int s = RubikStatePlay.DEF_SIZE;
+        ObjectList obj = ObjectList.getObject(RubikScreenPlay.DEF_OBJECT);
+        int s = RubikScreenPlay.DEF_SIZE;
 
         play.setObjectAndSize(this,obj,s);
         view.getPreRender().changeObject(obj,s);
@@ -305,12 +305,12 @@ public class RubikActivity extends AppCompatActivity
         BaseEffect.Type.getType(i).savePreferences(editor);
         }
 
-      for (int i = 0; i< StateList.LENGTH; i++)
+      for (int i = 0; i< ScreenList.LENGTH; i++)
         {
-        StateList.getState(i).getStateClass().savePreferences(editor);
+        ScreenList.getState(i).getStateClass().savePreferences(editor);
         }
 
-      StateList.savePreferences(editor);
+      ScreenList.savePreferences(editor);
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       view.getPreRender().savePreferences(editor);
 
@@ -330,12 +330,12 @@ public class RubikActivity extends AppCompatActivity
         BaseEffect.Type.getType(i).restorePreferences(preferences);
         }
 
-      for (int i = 0; i< StateList.LENGTH; i++)
+      for (int i = 0; i< ScreenList.LENGTH; i++)
         {
-        StateList.getState(i).getStateClass().restorePreferences(preferences);
+        ScreenList.getState(i).getStateClass().restorePreferences(preferences);
         }
 
-      StateList.restorePreferences(preferences);
+      ScreenList.restorePreferences(preferences);
 
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       view.getPreRender().restorePreferences(preferences);
@@ -545,9 +545,9 @@ public class RubikActivity extends AppCompatActivity
 
     public boolean isLocked()
       {
-      StateList state = StateList.getCurrentState();
+      ScreenList state = ScreenList.getCurrentState();
 
-      if( state== StateList.PLAY || state== StateList.READ || state== StateList.SOLV )
+      if( state== ScreenList.PLAY || state== ScreenList.READ || state== ScreenList.SOLV )
         {
         return mIsLocked;
         }
diff --git a/src/main/java/org/distorted/main/RubikPreRender.java b/src/main/java/org/distorted/main/RubikPreRender.java
index 75f89125..ec198622 100644
--- a/src/main/java/org/distorted/main/RubikPreRender.java
+++ b/src/main/java/org/distorted/main/RubikPreRender.java
@@ -42,9 +42,9 @@ import org.distorted.effects.scramble.ScrambleEffect;
 import org.distorted.objects.TwistyObject;
 import org.distorted.objects.ObjectList;
 import org.distorted.network.RubikScores;
-import org.distorted.states.RubikStatePlay;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStateSolving;
+import org.distorted.screens.RubikScreenPlay;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenSolving;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -172,9 +172,9 @@ public class RubikPreRender implements EffectController
 
     if( solved && !mIsSolved )
       {
-      if( StateList.getCurrentState()== StateList.SOLV )
+      if( ScreenList.getCurrentState()== ScreenList.SOLV )
         {
-        RubikStateSolving solving = (RubikStateSolving) StateList.SOLV.getStateClass();
+        RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getStateClass();
         mNewRecord = solving.getRecord();
 
         if( mNewRecord< 0 )
@@ -349,7 +349,7 @@ public class RubikPreRender implements EffectController
 
   private void reportRecord()
     {
-    RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
+    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
     RubikScores scores = RubikScores.getInstance();
 
     int object      = play.getObject();
@@ -693,14 +693,14 @@ public class RubikPreRender implements EffectController
               @Override
               public void run()
                 {
-                StateList.switchState( act, StateList.READ);
+                ScreenList.switchState( act, ScreenList.READ);
                 }
               });
             }
 
           if( i==BaseEffect.Type.WIN.ordinal() )
             {
-            if( StateList.getCurrentState()== StateList.SOLV )
+            if( ScreenList.getCurrentState()== ScreenList.SOLV )
               {
               final RubikActivity act = (RubikActivity)mView.getContext();
               Bundle bundle = new Bundle();
@@ -727,7 +727,7 @@ public class RubikPreRender implements EffectController
                 @Override
                 public void run()
                   {
-                  StateList.switchState( act, StateList.DONE);
+                  ScreenList.switchState( act, ScreenList.DONE);
                   }
                 });
               }
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index b9b5e58c..60197f92 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -35,10 +35,10 @@ import org.distorted.library.type.Static4D;
 import org.distorted.objects.TwistyObject;
 import org.distorted.objects.Movement;
 import org.distorted.solvers.SolverMain;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStatePlay;
-import org.distorted.states.RubikStateSolver;
-import org.distorted.states.RubikStateSolving;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenPlay;
+import org.distorted.screens.RubikScreenSolver;
+import org.distorted.screens.RubikScreenSolving;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -295,7 +295,7 @@ public class RubikSurfaceView extends GLSurfaceView
 
     private void setUpDragOrRotate(boolean down, float x, float y)
       {
-      int mode = StateList.getMode();
+      int mode = ScreenList.getMode();
 
       if( mode==MODE_DRAG )
         {
@@ -327,7 +327,7 @@ public class RubikSurfaceView extends GLSurfaceView
 
             if( down )
               {
-              RubikStateSolver solver = (RubikStateSolver) StateList.SVER.getStateClass();
+              RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getStateClass();
               mLastCubitFace = mMovement.getTouchedFace();
               float[] point = mMovement.getTouchedPoint3D();
               int color = solver.getCurrentColor();
@@ -416,14 +416,14 @@ public class RubikSurfaceView extends GLSurfaceView
 
       if( angle!=0 )
         {
-        if( StateList.getCurrentState()== StateList.SOLV )
+        if( ScreenList.getCurrentState()== ScreenList.SOLV )
           {
-          RubikStateSolving solving = (RubikStateSolving) StateList.SOLV.getStateClass();
+          RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getStateClass();
           solving.addMove(mCurrentAxis, mCurrentRow, angle);
           }
-        if( StateList.getCurrentState()== StateList.PLAY )
+        if( ScreenList.getCurrentState()== ScreenList.PLAY )
           {
-          RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
+          RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
           play.addMove(mCurrentAxis, mCurrentRow, angle);
           }
         }
@@ -477,9 +477,9 @@ public class RubikSurfaceView extends GLSurfaceView
 
       object.beginNewRotation( mCurrentAxis, mCurrentRow );
 
-      if( StateList.getCurrentState()== StateList.READ )
+      if( ScreenList.getCurrentState()== ScreenList.READ )
         {
-        RubikStateSolving solving = (RubikStateSolving) StateList.SOLV.getStateClass();
+        RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getStateClass();
         solving.resetElapsed();
 
         final RubikActivity act = (RubikActivity)getContext();
@@ -489,7 +489,7 @@ public class RubikSurfaceView extends GLSurfaceView
           @Override
           public void run()
             {
-            StateList.switchState( act, StateList.SOLV);
+            ScreenList.switchState( act, ScreenList.SOLV);
             }
           });
         }
diff --git a/src/main/java/org/distorted/screens/RubikScreenAbstract.java b/src/main/java/org/distorted/screens/RubikScreenAbstract.java
new file mode 100644
index 00000000..73b4f480
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenAbstract.java
@@ -0,0 +1,72 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+
+import org.distorted.main.RubikActivity;
+import org.distorted.objects.ObjectList;
+import org.distorted.patterns.RubikPatternList;
+import org.distorted.tutorials.TutorialList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class RubikScreenAbstract
+  {
+  int getPatternOrdinal()
+    {
+    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
+    int obj  = play.getObject();
+    int size = play.getSize();
+    int ret = RubikPatternList.getOrdinal(obj,size);
+
+    if( ret<0 )
+      {
+      ret = ObjectList.getSizeIndex(RubikScreenPlay.DEF_OBJECT, RubikScreenPlay.DEF_SIZE);
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int getTutorialOrdinal()
+    {
+    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
+    int obj  = play.getObject();
+    int size = play.getSize();
+
+    int ret = TutorialList.getOrdinal(obj,size);
+
+    if( ret<0 )
+      {
+      ret = ObjectList.getSizeIndex(RubikScreenPlay.DEF_OBJECT, RubikScreenPlay.DEF_SIZE);
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void enterState(RubikActivity act);
+  abstract void leaveState(RubikActivity act);
+  public abstract void savePreferences(SharedPreferences.Editor editor);
+  public abstract void restorePreferences(SharedPreferences preferences);
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenBase.java b/src/main/java/org/distorted/screens/RubikScreenBase.java
new file mode 100644
index 00000000..c69056ab
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenBase.java
@@ -0,0 +1,190 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.main.RubikPreRender;
+import org.distorted.objects.TwistyObject;
+
+import java.util.ArrayList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+abstract class RubikScreenBase extends RubikScreenAbstract implements RubikPreRender.ActionFinishedListener
+  {
+  private static final int DURATION_MILLIS = 750;
+
+  private ImageButton mPrevButton, mLockButton;
+
+  private boolean mCanPrevMove;
+
+  private static class Move
+    {
+    private final int mAxis, mRow, mAngle;
+
+    Move(int axis, int row, int angle)
+      {
+      mAxis = axis;
+      mRow  = row;
+      mAngle= angle;
+      }
+    }
+
+  ArrayList<Move> mMoves;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void backMove(RubikPreRender pre)
+    {
+    if( mCanPrevMove )
+      {
+      int numMoves = mMoves.size();
+
+      if( numMoves>0 )
+        {
+        RubikScreenBase.Move move = mMoves.remove(numMoves-1);
+        TwistyObject object = pre.getObject();
+
+        int axis  = move.mAxis;
+        int row   = (1<<move.mRow);
+        int angle = move.mAngle;
+        int numRot= Math.abs(angle*object.getBasicAngle()/360);
+
+        if( angle!=0 )
+          {
+          mCanPrevMove = false;
+          pre.addRotation(this, axis, row, -angle, numRot*DURATION_MILLIS);
+          }
+        else
+          {
+          android.util.Log.e("solution", "error: trying to back move of angle 0");
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void toggleLock(RubikActivity act)
+    {
+    act.toggleLock();
+    mLockButton.setImageResource(getLockIcon(act));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getLockIcon(RubikActivity act)
+    {
+    if( act.retLocked() )
+      {
+      return RubikActivity.getDrawable(R.drawable.ui_small_locked,R.drawable.ui_medium_locked, R.drawable.ui_big_locked, R.drawable.ui_huge_locked);
+      }
+    else
+      {
+      return RubikActivity.getDrawable(R.drawable.ui_small_unlocked,R.drawable.ui_medium_unlocked, R.drawable.ui_big_unlocked, R.drawable.ui_huge_unlocked);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createBottomPane(final RubikActivity act, float width, ImageButton button)
+    {
+    mCanPrevMove = true;
+
+    if( mMoves==null ) mMoves = new ArrayList<>();
+    else               mMoves.clear();
+
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(params);
+    LinearLayout layoutMid = new LinearLayout(act);
+    layoutMid.setLayoutParams(params);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(params);
+
+    setupPrevButton(act,width);
+    layoutLeft.addView(mPrevButton);
+    setupLockButton(act,width);
+    layoutMid.addView(mLockButton);
+    layoutRight.addView(button);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupLockButton(final RubikActivity act, final float width)
+    {
+    final int icon = getLockIcon(act);
+    mLockButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mLockButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        toggleLock(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupPrevButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_back,R.drawable.ui_medium_cube_back, R.drawable.ui_big_cube_back, R.drawable.ui_huge_cube_back);
+    mPrevButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikPreRender pre = act.getPreRender();
+        backMove(pre);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void addMove(int axis, int row, int angle)
+    {
+    mMoves.add(new Move(axis,row,angle));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onActionFinished(final long effectID)
+    {
+    mCanPrevMove = true;
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenDone.java b/src/main/java/org/distorted/screens/RubikScreenDone.java
new file mode 100644
index 00000000..df515a3c
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenDone.java
@@ -0,0 +1,122 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.fragment.app.FragmentManager;
+
+import org.distorted.dialogs.RubikDialogNewRecord;
+import org.distorted.dialogs.RubikDialogSolved;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenDone extends RubikScreenAbstract
+  {
+  private ImageButton mBackButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float titleSize = width*RubikActivity.TITLE_TEXT_SIZE;
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+    TextView label = (TextView)inflater.inflate(R.layout.upper_text, null);
+    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    label.setText(R.string.solved);
+    layoutTop.addView(label);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,2);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupBackButton(act,width);
+
+    layoutRight.addView(mBackButton);
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+
+        FragmentManager mana = act.getSupportFragmentManager();
+        RubikDialogNewRecord diag1 = (RubikDialogNewRecord) mana.findFragmentByTag(RubikDialogNewRecord.getDialogTag());
+        RubikDialogSolved    diag2 = (RubikDialogSolved   ) mana.findFragmentByTag(RubikDialogSolved.getDialogTag());
+
+        if( diag1 !=null ) diag1.dismiss();
+        if( diag2 !=null ) diag2.dismiss();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenPattern.java b/src/main/java/org/distorted/screens/RubikScreenPattern.java
new file mode 100644
index 00000000..a13d2d6f
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenPattern.java
@@ -0,0 +1,267 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import androidx.fragment.app.FragmentManager;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.dialogs.RubikDialogPattern;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.main.RubikPreRender;
+import org.distorted.objects.ObjectList;
+import org.distorted.patterns.RubikPattern;
+import org.distorted.patterns.RubikPatternList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenPattern extends RubikScreenAbstract
+  {
+  private TextView mText;
+  private ImageButton mPrevButton, mNextButton, mBackButton;
+  private TextView mMovesText;
+  private int mNumMoves;
+  private int mPatternOrdinal, mCategory, mPattern;
+  private float mButtonSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikScreenPattern()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
+
+    ObjectList object = RubikPatternList.getObject(mPatternOrdinal);
+    int size = RubikPatternList.getSize(mPatternOrdinal);
+
+    if( !play.setObjectAndSize(act,object,size) )
+      {
+      int objectPlay= play.getObject();
+      int sizePlay  = play.getSize();
+
+      act.changeObject(ObjectList.getObject(objectPlay),sizePlay, false);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
+    float titleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
+    mPatternOrdinal = getPatternOrdinal();
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+    mText = (TextView)inflater.inflate(R.layout.upper_pattern_text, null);
+    mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    mText.setText(R.string.patterns);
+    layoutTop.addView(mText);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    setupPrevButton(act,width);
+    setupNextButton(act,width);
+    setupTextView(act,width);
+
+    setTrioState(false);
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams((int)(width/2),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams((int)(width/6),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams((int)(width/3),LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    layoutLeft.addView(mPrevButton);
+    layoutLeft.addView(mMovesText);
+    layoutLeft.addView(mNextButton);
+
+    setupBackButton(act,width);
+
+    layoutRight.addView(mBackButton);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void showDialog(FragmentManager manager)
+    {
+    RubikDialogPattern diag = new RubikDialogPattern();
+    Bundle bundle = new Bundle();
+    bundle.putInt("tab", mPatternOrdinal );
+    diag.setArguments(bundle);
+    diag.show( manager, RubikDialogPattern.getDialogTag() );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setTrioState(boolean enable)
+    {
+    int state = enable ? View.VISIBLE : View.INVISIBLE;
+
+    if( mPrevButton!=null ) mPrevButton.setVisibility(state);
+    if( mNextButton!=null ) mNextButton.setVisibility(state);
+    if( mMovesText !=null ) mMovesText.setVisibility(state);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act, final float width)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        FragmentManager mana = act.getSupportFragmentManager();
+        ScreenList.goBack(act);
+        showDialog(mana);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPrevButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_left,R.drawable.ui_medium_left, R.drawable.ui_big_left, R.drawable.ui_huge_left);
+    mPrevButton = new TransparentImageButton(act,icon,width,0);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikPattern pattern = RubikPattern.getInstance();
+        RubikPreRender pre = act.getPreRender();
+        pattern.backMove( pre, mPatternOrdinal, mCategory, mPattern);
+        int currMove = pattern.getCurMove(mPatternOrdinal, mCategory, mPattern);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupNextButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_right,R.drawable.ui_medium_right, R.drawable.ui_big_right, R.drawable.ui_huge_right);
+    mNextButton = new TransparentImageButton(act,icon,width,0);
+
+    mNextButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikPattern pattern = RubikPattern.getInstance();
+        RubikPreRender pre = act.getPreRender();
+        pattern.makeMove( pre, mPatternOrdinal, mCategory, mPattern);
+        int currMove = pattern.getCurMove(mPatternOrdinal, mCategory, mPattern);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupTextView(final RubikActivity act, final float width)
+    {
+    int padding = (int)(width*RubikActivity.PADDING);
+    int margin  = (int)(width*RubikActivity.MARGIN);
+
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
+    params.topMargin    = margin;
+    params.bottomMargin = margin;
+    params.leftMargin   = margin;
+    params.rightMargin  = margin;
+
+    mMovesText = new TextView(act);
+    mMovesText.setTextSize(20);
+    mMovesText.setLayoutParams(params);
+    mMovesText.setPadding(padding,0,padding,0);
+    mMovesText.setGravity(Gravity.CENTER);
+    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
+    mMovesText.setText(act.getString(R.string.mo_placeholder,0,0));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setPattern(final RubikActivity act, int ordinal, int category, int pattern)
+    {
+    mPatternOrdinal = ordinal;
+    mCategory       = category;
+    mPattern        = pattern;
+
+    setTrioState(true);
+
+    RubikPattern patt = RubikPattern.getInstance();
+    String patternName = patt.getPatternName(ordinal,category,pattern);
+    mText.setText(patternName);
+
+    mNumMoves   = patt.getNumMoves(ordinal,category,pattern);
+    int currMove= patt.getCurMove(ordinal,category,pattern);
+
+    mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
new file mode 100644
index 00000000..e1e2e9d3
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -0,0 +1,592 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import org.distorted.dialogs.RubikDialogAbout;
+import org.distorted.dialogs.RubikDialogPattern;
+import org.distorted.dialogs.RubikDialogScores;
+import org.distorted.dialogs.RubikDialogTutorial;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.main.RubikPreRender;
+import org.distorted.objects.ObjectList;
+import org.distorted.network.RubikScores;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenPlay extends RubikScreenBase
+  {
+  public static final int LEVELS_SHOWN = 10;
+  public static final int DEF_OBJECT= ObjectList.CUBE.ordinal();
+  public static final int DEF_SIZE  =  3;
+
+  private static final int[] BUTTON_LABELS = { R.string.scores,
+                                               R.string.patterns,
+                                               R.string.control,
+                                               R.string.solver,
+                                               R.string.tutorials,
+                                               R.string.about };
+
+  private static final int NUM_BUTTONS = BUTTON_LABELS.length;
+  private static final float LAST_BUTTON = 1.5f;
+
+  private ImageButton mObjButton, mMenuButton, mSolveButton;
+  private Button mPlayButton;
+  private PopupWindow mObjectPopup, mMenuPopup, mPlayPopup;
+  private int mObject = DEF_OBJECT;
+  private int mSize   = DEF_SIZE;
+  private int mObjectSize, mMenuLayoutWidth, mMenuLayoutHeight, mPlayLayoutWidth;
+  private int mLevelValue;
+  private float mButtonSize, mMenuItemSize, mMenuTextSize;
+  private int mColCount, mRowCount;
+  private LinearLayout mPlayLayout;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+
+    mMenuTextSize = width*RubikActivity.MENU_MED_TEXT_SIZE;
+    mButtonSize   = width*RubikActivity.BUTTON_TEXT_SIZE;
+    mMenuItemSize = width*RubikActivity.MENU_ITEM_SIZE;
+
+    mRowCount = ObjectList.getRowCount();
+    mColCount = ObjectList.getColumnCount();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    setupObjectWindow(act,width);
+    setupObjectButton(act,width);
+    layoutTop.addView(mObjButton);
+
+    setupMenuWindow(act,width);
+    setupMenuButton(act,width);
+    layoutTop.addView(mMenuButton);
+
+    setupPlayWindow(act,width);
+    setupPlayButton(act,width);
+    layoutTop.addView(mPlayButton);
+
+    setupSolveButton(act,width);
+    createBottomPane(act,width,mSolveButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupObjectButton(final RubikActivity act, final float width)
+    {
+    final int margin  = (int)(width*RubikActivity.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);
+    mObjButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mObjButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View view)
+        {
+        if( act.getPreRender().canPlay() )
+          {
+          if( mObjectPopup==null )
+            {
+            // I completely don't understand it, but Firebase says occasionally mObjectPopup is null here. Recreate.
+            float width = act.getScreenWidthInPixels();
+            setupObjectWindow(act,width);
+            }
+
+          mObjectPopup.setFocusable(false);
+          mObjectPopup.update();
+
+          View popupView = mObjectPopup.getContentView();
+          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
+
+          mObjectPopup.showAsDropDown(view, margin, margin);
+          mObjectPopup.update(view, mObjectSize*mColCount, mObjectSize*mRowCount);
+
+          mObjectPopup.setFocusable(true);
+          mObjectPopup.update();
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPlayButton(final RubikActivity act, final float width)
+    {
+    final int margin  = (int)(width*RubikActivity.MARGIN);
+    mPlayButton = new TransparentButton(act, R.string.play, mButtonSize, width);
+
+    mPlayButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View view)
+        {
+        if( act.getPreRender().canPlay() )
+          {
+          if( mPlayPopup==null )
+            {
+            // I completely don't understand it, but Firebase says occasionally mPlayPopup is null here. Recreate.
+            float width = act.getScreenWidthInPixels();
+            setupPlayWindow(act,width);
+            }
+
+          mPlayPopup.setFocusable(false);
+          mPlayPopup.update();
+
+          View popupView = mPlayPopup.getContentView();
+          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
+
+          final int sizeIndex = ObjectList.getSizeIndex(mObject,mSize);
+          final int maxLevel = ObjectList.getMaxLevel(mObject, sizeIndex);
+          final int levelsShown = Math.min(maxLevel,LEVELS_SHOWN);
+
+          mPlayPopup.showAsDropDown(view, margin, margin);
+          mPlayPopup.update(view, mPlayLayoutWidth, (int)(levelsShown*(mMenuItemSize+margin)+3*margin+mMenuItemSize*(LAST_BUTTON-1.0f)));
+          mPlayPopup.setFocusable(true);
+          mPlayPopup.update();
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupMenuButton(final RubikActivity act, final float width)
+    {
+    final int margin  = (int)(width*RubikActivity.MARGIN);
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_menu,R.drawable.ui_medium_menu, R.drawable.ui_big_menu, R.drawable.ui_huge_menu);
+    mMenuButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mMenuButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View view)
+        {
+        if( act.getPreRender().canPlay() )
+          {
+          if( mMenuPopup==null )
+            {
+            // I completely don't understand it, but Firebase says occasionally mMenuPopup is null here. Recreate.
+            float width = act.getScreenWidthInPixels();
+            setupMenuWindow(act,width);
+            }
+
+          mMenuPopup.setFocusable(false);
+          mMenuPopup.update();
+
+          View popupView = mMenuPopup.getContentView();
+          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
+
+          mMenuPopup.showAsDropDown(view, (int)(-width/12), margin, Gravity.CENTER);
+          mMenuPopup.update(view, mMenuLayoutWidth, mMenuLayoutHeight);
+          mMenuPopup.setFocusable(true);
+          mMenuPopup.update();
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupObjectWindow(final RubikActivity act, final float width)
+    {
+    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    final View layout = layoutInflater.inflate(R.layout.popup_objects, null);
+    GridLayout objectGrid = layout.findViewById(R.id.objectGrid);
+
+    int[] indices = ObjectList.getIndices();
+
+    GridLayout.Spec[] rowSpecs = new GridLayout.Spec[mRowCount];
+    GridLayout.Spec[] colSpecs = new GridLayout.Spec[mColCount];
+
+    objectGrid.setColumnCount(mColCount);
+    objectGrid.setRowCount(mRowCount);
+
+    int[] nextInRow = new int[mRowCount];
+
+    for(int row=0; row<mRowCount; row++)
+      {
+      rowSpecs[row] = GridLayout.spec(row);
+      nextInRow[row]= 0;
+      }
+    for(int col=0; col<mColCount; col++)
+      {
+      colSpecs[col] = GridLayout.spec(col);
+      }
+
+    mObjectPopup = new PopupWindow(act);
+    mObjectPopup.setContentView(layout);
+    mObjectPopup.setFocusable(true);
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube2,R.drawable.ui_medium_cube2, R.drawable.ui_big_cube2, R.drawable.ui_huge_cube2);
+
+    BitmapDrawable bd = (BitmapDrawable) act.getResources().getDrawable(icon);
+    int cubeWidth = bd.getIntrinsicWidth();
+    int margin = (int)(width*RubikActivity.LARGE_MARGIN);
+    mObjectSize = (int)(cubeWidth + 2*margin + 0.5f);
+
+    for(int object=0; object< ObjectList.NUM_OBJECTS; object++)
+      {
+      final ObjectList list = ObjectList.getObject(object);
+      final int[] sizes = list.getSizes();
+      int[] icons = list.getIconIDs();
+      int len = sizes.length;
+      final int obj = object;
+      int row = indices[object];
+
+      for(int i=0; i<len; i++)
+        {
+        final int index = i;
+
+        ImageButton button = new ImageButton(act);
+        button.setBackgroundResource(icons[i]);
+        button.setOnClickListener( new View.OnClickListener()
+          {
+          @Override
+          public void onClick(View v)
+            {
+            if( act.getPreRender().canPlay() && ScreenList.getCurrentState()== ScreenList.PLAY )
+              {
+              mObject = obj;
+              mSize   = sizes[index];
+              act.changeObject(list,sizes[index], true);
+              adjustLevels(act);
+              mMoves.clear();
+              }
+
+            mObjectPopup.dismiss();
+            }
+          });
+
+        GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowSpecs[row],colSpecs[nextInRow[row]]);
+        params.bottomMargin = margin;
+        params.topMargin    = margin;
+        params.leftMargin   = margin;
+        params.rightMargin  = margin;
+
+        nextInRow[row]++;
+
+        objectGrid.addView(button, params);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupMenuWindow(final RubikActivity act, final float width)
+    {
+    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    final View layout = layoutInflater.inflate(R.layout.popup_menu, null);
+    LinearLayout menuLayout = layout.findViewById(R.id.menuGrid);
+
+    mMenuPopup = new PopupWindow(act);
+    mMenuPopup.setContentView(layout);
+    mMenuPopup.setFocusable(true);
+    int margin  = (int)(width*RubikActivity.MARGIN);
+    int padding = (int)(width*RubikActivity.PADDING);
+
+    mMenuLayoutWidth = (int)(width/2);
+    mMenuLayoutHeight= (int)(2*margin + NUM_BUTTONS*(mMenuItemSize+margin));
+
+    LinearLayout.LayoutParams p = new LinearLayout.LayoutParams( mMenuLayoutWidth - 2*padding, (int)mMenuItemSize);
+
+    for(int i=0; i<NUM_BUTTONS; i++)
+      {
+      final int but = i;
+      Button button = new Button(act);
+      button.setLayoutParams(p);
+      button.setText(BUTTON_LABELS[i]);
+      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+
+      button.setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+          mMenuPopup.dismiss();
+          MenuAction(act,but);
+          }
+        });
+
+      menuLayout.addView(button);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPlayWindow(final RubikActivity act, final float width)
+    {
+    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    final View layout = layoutInflater.inflate(R.layout.popup_play, null);
+    mPlayLayout = layout.findViewById(R.id.playGrid);
+
+    mPlayLayoutWidth = (int)(width*0.4f);
+
+    mPlayPopup = new PopupWindow(act);
+    mPlayPopup.setContentView(layout);
+    mPlayPopup.setFocusable(true);
+
+    adjustLevels(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void controlTheCube()
+    {
+    android.util.Log.e("D", "Control");
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void MenuAction(RubikActivity act, int button)
+    {
+    switch(button)
+      {
+      case 0: RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
+              int object = play.getObject();
+              int size   = play.getSize();
+              int sizeIndex = ObjectList.getSizeIndex(object,size);
+              Bundle sBundle = new Bundle();
+              sBundle.putInt("tab", ObjectList.pack(object,sizeIndex) );
+              sBundle.putBoolean("submitting", false);
+              RubikDialogScores scores = new RubikDialogScores();
+              scores.setArguments(sBundle);
+              scores.show(act.getSupportFragmentManager(), null);
+              break;
+      case 1: RubikDialogPattern pDiag = new RubikDialogPattern();
+              Bundle pBundle = new Bundle();
+              int pOrd = getPatternOrdinal();
+              pBundle.putInt("tab", pOrd );
+              pDiag.setArguments(pBundle);
+              pDiag.show( act.getSupportFragmentManager(), RubikDialogPattern.getDialogTag() );
+              break;
+      case 2: controlTheCube();
+              break;
+      case 3: ScreenList.switchState(act, ScreenList.SVER);
+              break;
+      case 4: RubikDialogTutorial tDiag = new RubikDialogTutorial();
+              Bundle tBundle = new Bundle();
+              int tOrd = getTutorialOrdinal();
+              tBundle.putInt("tab", tOrd );
+              tDiag.setArguments(tBundle);
+              tDiag.show( act.getSupportFragmentManager(), RubikDialogTutorial.getDialogTag() );
+              break;
+      case 5: RubikDialogAbout aDiag = new RubikDialogAbout();
+              aDiag.show(act.getSupportFragmentManager(), null);
+              break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupSolveButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_solve,R.drawable.ui_medium_cube_solve, R.drawable.ui_big_cube_solve, R.drawable.ui_huge_cube_solve);
+    mSolveButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.getPreRender().solveObject();
+        mMoves.clear();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putInt("statePlay_object", mObject);
+    editor.putInt("statePlay_size"  , mSize);
+
+    if( mObjectPopup!=null )
+      {
+      mObjectPopup.dismiss();
+      mObjectPopup = null;
+      }
+
+    if( mMenuPopup!=null )
+      {
+      mMenuPopup.dismiss();
+      mMenuPopup = null;
+      }
+
+    if( mPlayPopup!=null )
+      {
+      mPlayPopup.dismiss();
+      mPlayPopup = null;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+    mObject= preferences.getInt("statePlay_object", DEF_OBJECT);
+    mSize  = preferences.getInt("statePlay_size"  , DEF_SIZE  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean setObjectAndSize(RubikActivity act, ObjectList obj, int size)
+    {
+    if( mObject!=obj.ordinal() || mSize != size )
+      {
+      boolean success = false;
+
+      for( int s: obj.getSizes() )
+        if( s==size )
+          {
+          success = true;
+          break;
+          }
+
+      if( success )
+        {
+        mObject = obj.ordinal();
+        mSize   = size;
+
+        if( mPlayLayout!=null ) adjustLevels(act);
+        }
+
+      return success;
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void adjustLevels(final RubikActivity act)
+    {
+    int sizeIndex = ObjectList.getSizeIndex(mObject,mSize);
+    int maxLevel = ObjectList.getMaxLevel(mObject, sizeIndex);
+    int numLevel = Math.min(maxLevel, LEVELS_SHOWN);
+    String[] levels = new String[numLevel];
+
+    for(int i=0; i<numLevel-1; i++)
+      {
+      levels[i] = act.getString(R.string.lv_placeholder,i+1);
+      }
+
+    levels[numLevel-1] = act.getString(R.string.level_full);
+
+    if( mLevelValue>maxLevel || mLevelValue<1 ||
+       (mLevelValue<maxLevel || mLevelValue>LEVELS_SHOWN ) )
+      {
+      mLevelValue=1;
+      }
+
+    float width  = act.getScreenWidthInPixels();
+    int margin   = (int)(width*RubikActivity.MARGIN);
+    int padding  = (int)(width*RubikActivity.PADDING);
+    int butWidth = mPlayLayoutWidth - 2*padding;
+    int butHeight= (int)mMenuItemSize;
+    int lastButH = (int)(mMenuItemSize*LAST_BUTTON) ;
+
+    LinearLayout.LayoutParams pM = new LinearLayout.LayoutParams( butWidth, butHeight );
+    pM.setMargins(margin, 0, margin, margin);
+    LinearLayout.LayoutParams pT = new LinearLayout.LayoutParams( butWidth, butHeight );
+    pT.setMargins(margin, margin, margin, margin);
+    LinearLayout.LayoutParams pB = new LinearLayout.LayoutParams( butWidth, lastButH  );
+    pB.setMargins(margin, margin, margin, 2*margin);
+
+    mPlayLayout.removeAllViews();
+
+    RubikScores scores = RubikScores.getInstance();
+
+    for(int i=0; i<numLevel; i++)
+      {
+      final int scrambles = i<numLevel-1 ? i+1 : maxLevel;
+      Button button = new Button(act);
+      button.setLayoutParams(i==0 ? pT : (i==numLevel-1 ? pB : pM));
+      button.setText(levels[i]);
+      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+
+      int icon = scores.isSolved(mObject, sizeIndex, i) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
+      button.setCompoundDrawablesWithIntrinsicBounds(icon,0,0,0);
+
+      button.setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+          RubikPreRender pre = act.getPreRender();
+
+          if( pre.canPlay() )
+            {
+            mPlayPopup.dismiss();
+            mLevelValue = scrambles;
+            pre.scrambleObject(mLevelValue);
+            }
+          }
+        });
+
+      mPlayLayout.addView(button);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getLevel()
+    {
+    return mLevelValue;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObject()
+    {
+    return mObject;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getSize()
+    {
+    return mSize;
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenReady.java b/src/main/java/org/distorted/screens/RubikScreenReady.java
new file mode 100644
index 00000000..f06780a7
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenReady.java
@@ -0,0 +1,96 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenReady extends RubikScreenBase
+  {
+  private ImageButton mBackButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float titleSize = width*RubikActivity.TITLE_TEXT_SIZE;
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+    TextView label = (TextView)inflater.inflate(R.layout.upper_text, null);
+    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    label.setText(R.string.ready);
+    layoutTop.addView(label);
+
+    setupBackButton(act,width);
+    createBottomPane(act,width,mBackButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolution.java b/src/main/java/org/distorted/screens/RubikScreenSolution.java
new file mode 100644
index 00000000..ee61fe8b
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenSolution.java
@@ -0,0 +1,295 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.main.RubikPreRender;
+import org.distorted.objects.TwistyObject;
+import org.distorted.patterns.RubikPattern;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenSolution extends RubikScreenAbstract implements RubikPreRender.ActionFinishedListener
+  {
+  private static final int DURATION_MILLIS = 750;
+
+  private ImageButton mPrevButton, mNextButton, mBackButton;
+  private TextView mMovesText;
+  private int[][] mMoves;
+  private int mCurrMove, mNumMoves;
+  private boolean mCanRotate;
+  private float mButtonSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+    TwistyObject object = act.getObject();
+    object.solve();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
+    float titleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
+
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    final TextView text = (TextView)inflater.inflate(R.layout.upper_text, null);
+    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    text.setText(R.string.solution);
+    layoutTop.addView(text);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams((int)(width/2),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams((int)(width/6),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams((int)(width/3),LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupPrevButton(act,width);
+    setupNextButton(act,width);
+    setupTextView(act,width);
+
+    layoutLeft.addView(mPrevButton);
+    layoutLeft.addView(mMovesText);
+    layoutLeft.addView(mNextButton);
+
+    setupBackButton(act,width);
+
+    layoutRight.addView(mBackButton);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPrevButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_left,R.drawable.ui_medium_left, R.drawable.ui_big_left, R.drawable.ui_huge_left);
+    mPrevButton = new TransparentImageButton(act,icon,width,0);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikPreRender pre = act.getPreRender();
+        backMove(pre);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupNextButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_right,R.drawable.ui_medium_right, R.drawable.ui_big_right, R.drawable.ui_huge_right);
+    mNextButton = new TransparentImageButton(act,icon,width,0);
+
+    mNextButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikPreRender pre = act.getPreRender();
+        makeMove(pre);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupTextView(final RubikActivity act, final float width)
+    {
+    int padding = (int)(width*RubikActivity.PADDING);
+    int margin  = (int)(width*RubikActivity.MARGIN);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
+    params.topMargin    = margin;
+    params.bottomMargin = margin;
+    params.leftMargin   = margin;
+    params.rightMargin  = margin;
+
+    mMovesText = new TextView(act);
+    mMovesText.setTextSize(20);
+    mMovesText.setLayoutParams(params);
+    mMovesText.setPadding(padding,0,padding,0);
+    mMovesText.setGravity(Gravity.CENTER);
+    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
+    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act, final float width)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void makeMove(RubikPreRender pre)
+    {
+    if( mCanRotate )
+      {
+      mCurrMove++;
+
+      if( mCurrMove>mNumMoves )
+        {
+        mCurrMove= 0;
+        pre.initializeObject(null);
+        }
+      else
+        {
+        int axis     = mMoves[mCurrMove-1][0];
+		    int rowBitmap= mMoves[mCurrMove-1][1];
+		    int bareAngle= mMoves[mCurrMove-1][2];
+        int angle    = bareAngle*(360/pre.getObject().getBasicAngle());
+        int numRot   = Math.abs(bareAngle);
+
+        if( angle!=0 )
+          {
+          mCanRotate = false;
+          pre.addRotation(this, axis, rowBitmap, angle, numRot*DURATION_MILLIS);
+          }
+        else
+          {
+          android.util.Log.e("solution", "error: solution contains angle 0");
+          }
+        }
+      }
+    else
+      {
+      android.util.Log.e("solution", "failed to make move!");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void backMove(RubikPreRender pre)
+    {
+    if( mCanRotate )
+      {
+      mCurrMove--;
+
+      if( mCurrMove<0 )
+        {
+        mCurrMove=mNumMoves;
+        pre.initializeObject(mMoves);
+        }
+      else
+        {
+        int axis     = mMoves[mCurrMove][0];
+		    int rowBitmap= mMoves[mCurrMove][1];
+		    int bareAngle= mMoves[mCurrMove][2];
+        int angle    = bareAngle*(360/pre.getObject().getBasicAngle());
+        int numRot   = Math.abs(bareAngle);
+
+        if( angle!=0 )
+          {
+          mCanRotate = false;
+          pre.addRotation(this, axis, rowBitmap, -angle, numRot*DURATION_MILLIS);
+          }
+        else
+          {
+          android.util.Log.e("solution", "error: solution contains angle 0");
+          }
+        }
+      }
+    else
+      {
+      android.util.Log.e("solution", "failed to back move!");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupMoves(final RubikActivity act, String moves)
+    {
+    mCanRotate= true;
+    mCurrMove = 0;
+    mNumMoves = moves.length()/4;
+    mMoves    = new int[mNumMoves][3];
+
+    RubikPattern.parseMoves(mMoves,mNumMoves,moves);
+
+    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onActionFinished(final long effectID)
+    {
+    mCanRotate = true;
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolver.java b/src/main/java/org/distorted/screens/RubikScreenSolver.java
new file mode 100644
index 00000000..4bc19d5e
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenSolver.java
@@ -0,0 +1,319 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import androidx.core.content.ContextCompat;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import org.distorted.dialogs.RubikDialogSolverError;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.main.RubikPreRender;
+import org.distorted.objects.TwistyObject;
+import org.distorted.objects.ObjectList;
+import org.distorted.solvers.ImplementedSolversList;
+import org.distorted.solvers.SolverMain;
+
+import java.lang.ref.WeakReference;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenSolver extends RubikScreenAbstract
+  {
+  private static Bitmap[] mBitmap;
+  private ImageButton[] mColorButton;
+  private ImageButton mBackButton, mSolveButton;
+  private boolean mSolving;
+  private int mCurrentColor;
+  private int[] mFaceColors;
+  private int mNumFaces;
+  private float mBitmapSize;
+
+  private ObjectList mCurrentObject;
+  private int mCurrentObjectSize;
+
+  private WeakReference<RubikActivity> mWeakAct;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float heigh = act.getScreenHeightInPixels();
+
+    int sizeV = (int)(heigh*RubikActivity.SOLVER_BMP_V_SIZE);
+    int sizeH = (int)(width*RubikActivity.SOLVER_BMP_H_SIZE);
+
+    mBitmapSize = Math.min(sizeV,sizeH);
+
+    mWeakAct = new WeakReference<>(act);
+
+    mSolving = false;
+
+    mCurrentObject     = ImplementedSolversList.getObject(0);
+    mCurrentObjectSize = ImplementedSolversList.getObjectSize(0);
+
+    act.setupObject(mCurrentObject, mCurrentObjectSize, null);
+    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
+    play.setObjectAndSize(act, mCurrentObject, mCurrentObjectSize);
+
+    mFaceColors = ObjectList.retFaceColors(mCurrentObject);
+    mNumFaces   = mFaceColors!=null ? mFaceColors.length : 0;
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsL);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsL);
+
+    if( mNumFaces>0 )
+      {
+      setupBitmaps();
+      setupColorButtons(act,width);
+      markButton(act);
+      }
+
+    for(ImageButton button: mColorButton) layoutTop.addView(button);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    setupSolveButton(act,width);
+    setupBackButton(act,width);
+
+    layoutLeft.addView(mSolveButton);
+    layoutRight.addView(mBackButton);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBitmaps()
+    {
+    final int SIZE = (int)mBitmapSize;
+    final float R = SIZE*0.15f;
+    final float M = SIZE*0.08f;
+
+    mBitmap = new Bitmap[mNumFaces];
+
+    Paint paint = new Paint();
+    paint.setColor(0xff008800);
+    paint.setStyle(Paint.Style.FILL);
+
+    paint.setAntiAlias(true);
+    paint.setTextAlign(Paint.Align.CENTER);
+    paint.setStyle(Paint.Style.FILL);
+
+    for(int i=0; i<mNumFaces; i++)
+      {
+      mBitmap[i] = Bitmap.createBitmap(SIZE, SIZE, Bitmap.Config.ARGB_8888);
+      Canvas canvas = new Canvas(mBitmap[i]);
+
+      paint.setColor(0xff000000);
+      canvas.drawRect(0, 0, SIZE, SIZE, paint);
+
+      paint.setColor(mFaceColors[i]);
+      canvas.drawRoundRect( M, M, SIZE-M, SIZE-M, R, R, paint);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupColorButtons(final RubikActivity act, final float width)
+    {
+    mColorButton = new ImageButton[mNumFaces];
+    int padding = (int)(width*RubikActivity.PADDING);
+    int margin  = (int)(width*RubikActivity.MARGIN);
+
+    for(int i=0; i<mNumFaces; i++)
+      {
+      final int ii = i;
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
+      params.topMargin    = margin;
+      params.bottomMargin = margin;
+      params.leftMargin   = margin;
+      params.rightMargin  = margin;
+
+      mColorButton[i] = new ImageButton(act);
+      mColorButton[i].setLayoutParams(params);
+      mColorButton[i].setPadding(padding,0,padding,0);
+      mColorButton[i].setImageBitmap(mBitmap[i]);
+
+      mColorButton[i].setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View view)
+          {
+          mCurrentColor = ii;
+          markButton(act);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupSolveButton(final RubikActivity act, final float width)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_solve,R.drawable.ui_medium_solve, R.drawable.ui_big_solve, R.drawable.ui_huge_solve);
+    mSolveButton = new TransparentImageButton(act,icon,width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        if( !mSolving )
+          {
+          mSolving = true;
+          TwistyObject object = act.getObject();
+          String objectString = object.retObjectString();
+          SolverMain solver = new SolverMain( act.getResources(), mCurrentObject, mCurrentObjectSize, objectString );
+          solver.start();
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act, final float width)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikPreRender pre = act.getPreRender();
+        pre.resetAllTextureMaps();
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markButton(RubikActivity act)
+    {
+    for(int b=0; b<mNumFaces; b++)
+      {
+      Drawable d = mColorButton[b].getBackground();
+
+      if( b==mCurrentColor )
+        {
+        d.setColorFilter(ContextCompat.getColor(act,R.color.red), PorterDuff.Mode.MULTIPLY);
+        }
+      else
+        {
+        d.clearColorFilter();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putInt("stateSolver_color", mCurrentColor);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+    mCurrentColor = preferences.getInt("stateSolver_color", 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCurrentColor()
+    {
+    return mCurrentColor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setSolved(final String moves)
+    {
+    mSolving = false;
+    final RubikActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchState(act, ScreenList.SOLU);
+          RubikScreenSolution solution = (RubikScreenSolution) ScreenList.SOLU.getStateClass();
+          solution.setupMoves(act, moves);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void displayErrorDialog( String message)
+    {
+    mSolving = false;
+    RubikActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      RubikDialogSolverError dialog = new RubikDialogSolverError();
+      Bundle bundle = new Bundle();
+      bundle.putString("error", message );
+      dialog.setArguments(bundle);
+      dialog.show( act.getSupportFragmentManager(), null);
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolving.java b/src/main/java/org/distorted/screens/RubikScreenSolving.java
new file mode 100644
index 00000000..89b58ba5
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenSolving.java
@@ -0,0 +1,194 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.objects.ObjectList;
+import org.distorted.network.RubikScores;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenSolving extends RubikScreenBase
+  {
+  private TextView mTime;
+  private Timer mTimer;
+  private long mStartTime;
+  private boolean mRunning;
+  private RubikScores mScores;
+  private long mElapsed;
+  private ImageButton mBackButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikScreenSolving()
+    {
+    mScores = RubikScores.getInstance();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveState(RubikActivity act)
+    {
+    stopCounting();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterState(final RubikActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float titleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
+
+    startCounting(act);
+
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+    mTime = (TextView)inflater.inflate(R.layout.upper_text, null);
+    int elapsed = (int)mElapsed/1000;
+    mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    mTime.setText(act.getString(R.string.tm_placeholder,elapsed/60,elapsed%60));
+    layoutTop.addView(mTime);
+
+    setupBackButton(act,width);
+    createBottomPane(act,width,mBackButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act, final float width)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    mElapsed = System.currentTimeMillis()-mStartTime;
+    editor.putLong("stateSolving_elapsed" , mElapsed);
+    mScores.savePreferences(editor);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+    mElapsed = preferences.getLong("stateSolving_elapsed" , 0 );
+    mScores.restorePreferences(preferences);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void startCounting(final RubikActivity act)
+    {
+    if( !mRunning )
+      {
+      mRunning = true;
+      mStartTime = System.currentTimeMillis() - mElapsed;
+      mTimer = new Timer();
+
+      mTimer.scheduleAtFixedRate(new TimerTask()
+        {
+        @Override
+        public void run()
+          {
+          act.runOnUiThread(new Runnable()
+            {
+            @Override
+            public void run()
+              {
+              int elapsed = (int)(System.currentTimeMillis()-mStartTime)/1000;
+              mTime.setText(act.getString(R.string.tm_placeholder,elapsed/60,elapsed%60));
+              }
+            });
+          }
+        }, 0, 1000);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void stopCounting()
+    {
+    if( mTimer!=null )
+      {
+      mTimer.cancel();
+      mTimer = null;
+      }
+
+    mRunning = false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long getRecord()
+    {
+    if( mRunning )
+      {
+      stopCounting();
+
+      mElapsed = System.currentTimeMillis()-mStartTime;
+
+      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
+      int object  = play.getObject();
+      int size    = play.getSize();
+      int level   = play.getLevel();
+      int realSize= ObjectList.getSizeIndex(object,size);
+
+      boolean isNew = mScores.setRecord(object, realSize, level, mElapsed);
+
+      return isNew ? mElapsed : -mElapsed;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void resetElapsed()
+    {
+    mElapsed = 0;
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/ScreenList.java b/src/main/java/org/distorted/screens/ScreenList.java
new file mode 100644
index 00000000..c30db604
--- /dev/null
+++ b/src/main/java/org/distorted/screens/ScreenList.java
@@ -0,0 +1,182 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+
+import org.distorted.main.RubikActivity;
+import static org.distorted.main.RubikSurfaceView.*;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public enum ScreenList
+  {
+  PLAY ( null , MODE_ROTATE , new RubikScreenPlay()     ),
+  SOLV ( PLAY , MODE_ROTATE , new RubikScreenSolving()  ),
+  PATT ( PLAY , MODE_DRAG   , new RubikScreenPattern()  ),
+  SVER ( PLAY , MODE_REPLACE, new RubikScreenSolver()   ),
+  SOLU ( SVER , MODE_DRAG   , new RubikScreenSolution() ),
+  READ ( PLAY , MODE_ROTATE , new RubikScreenReady()    ),
+  DONE ( PLAY , MODE_DRAG   , new RubikScreenDone()     ),
+  ;
+
+  public static final int LENGTH = values().length;
+  private final ScreenList mBack;
+  private int mMode;
+  private final RubikScreenAbstract mClass;
+  private static final ScreenList[] states;
+
+  private static ScreenList mCurrState;
+
+  static
+    {
+    int i = 0;
+    states = new ScreenList[LENGTH];
+
+    for(ScreenList state: ScreenList.values())
+      {
+      states[i] = state;
+      i++;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getState(int ordinal)
+    {
+    return ordinal>=0 && ordinal<LENGTH ?  states[ordinal] : PLAY;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getStateFromName(String name)
+    {
+    for(int i=0; i<LENGTH; i++)
+      {
+      if( name.equals(states[i].name()) )
+        {
+        return states[i];
+        }
+      }
+
+    return PLAY;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getCurrentState()
+    {
+    return mCurrState;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getMode()
+    {
+    return mCurrState.mMode;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putString("curr_state_name", mCurrState.name() );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void restorePreferences(SharedPreferences preferences)
+    {
+    String currStateName = preferences.getString("curr_state_name", ScreenList.PLAY.name() );
+    mCurrState = getStateFromName(currStateName);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void goBack(RubikActivity act)
+    {
+    switchState(act, mCurrState.mBack );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void setState(RubikActivity act)
+    {
+    mCurrState.enterState(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void switchState(RubikActivity act, ScreenList next)
+    {
+    if( next!=null )
+      {
+      FirebaseAnalytics analytics = act.getAnalytics();
+
+      if( analytics!=null )
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, next.toString());
+        analytics.logEvent(FirebaseAnalytics.Event.LEVEL_START, bundle);
+        }
+
+      if( mCurrState!=null ) mCurrState.leaveState(act);
+      next.enterState(act);
+      mCurrState = next;
+      }
+    else
+      {
+      act.finish();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ScreenList(ScreenList back, int mode, RubikScreenAbstract clazz)
+    {
+    mBack = back;
+    mMode = mode;
+    mClass= clazz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikScreenAbstract getStateClass()
+    {
+    return mClass;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void leaveState(RubikActivity act)
+    {
+    mClass.leaveState(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void enterState(RubikActivity act)
+    {
+    mClass.enterState(act);
+    }
+  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/screens/TransparentButton.java b/src/main/java/org/distorted/screens/TransparentButton.java
new file mode 100644
index 00000000..62df6524
--- /dev/null
+++ b/src/main/java/org/distorted/screens/TransparentButton.java
@@ -0,0 +1,56 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.TypedValue;
+import android.widget.LinearLayout;
+
+import org.distorted.main.RubikActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@SuppressLint("ViewConstructor")
+public class TransparentButton extends androidx.appcompat.widget.AppCompatButton
+{
+   public TransparentButton(Context context, int resId, float size, float scrWidth)
+      {
+      super(context);
+
+      final int padding = (int)(scrWidth*RubikActivity.PADDING);
+      final int margin  = (int)(scrWidth*RubikActivity.MARGIN);
+
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
+      params.topMargin    = margin;
+      params.bottomMargin = margin;
+      params.leftMargin   = margin;
+      params.rightMargin  = margin;
+
+      setLayoutParams(params);
+      setPadding(padding,0,padding,0);
+      setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+      setText(resId);
+
+      TypedValue outValue = new TypedValue();
+      context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
+      setBackgroundResource(outValue.resourceId);
+      }
+}
diff --git a/src/main/java/org/distorted/screens/TransparentImageButton.java b/src/main/java/org/distorted/screens/TransparentImageButton.java
new file mode 100644
index 00000000..5a4e4f52
--- /dev/null
+++ b/src/main/java/org/distorted/screens/TransparentImageButton.java
@@ -0,0 +1,56 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.screens;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.TypedValue;
+import android.widget.LinearLayout;
+
+import org.distorted.main.RubikActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@SuppressLint("ViewConstructor")
+public class TransparentImageButton extends androidx.appcompat.widget.AppCompatImageButton
+{
+  public TransparentImageButton(Context context, int icon, float scrWidth, int butWidth)
+      {
+      super(context);
+
+      final int padding = (int)(scrWidth*RubikActivity.PADDING);
+      final int margin  = (int)(scrWidth*RubikActivity.MARGIN);
+
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(butWidth,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+
+      params.topMargin    = margin;
+      params.bottomMargin = margin;
+      params.leftMargin   = margin;
+      params.rightMargin  = margin;
+
+      setLayoutParams(params);
+      setPadding(padding,0,padding,0);
+      setImageResource(icon);
+
+      TypedValue outValue = new TypedValue();
+      context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
+      setBackgroundResource(outValue.resourceId);
+      }
+}
diff --git a/src/main/java/org/distorted/solvers/SolverMain.java b/src/main/java/org/distorted/solvers/SolverMain.java
index 85cd4b0d..af625b61 100644
--- a/src/main/java/org/distorted/solvers/SolverMain.java
+++ b/src/main/java/org/distorted/solvers/SolverMain.java
@@ -23,8 +23,8 @@ import android.content.res.Resources;
 
 import org.distorted.main.R;
 import org.distorted.objects.ObjectList;
-import org.distorted.states.StateList;
-import org.distorted.states.RubikStateSolver;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenSolver;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -68,7 +68,7 @@ public class SolverMain implements Runnable
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void solveCube3(RubikStateSolver solver)
+  private void solveCube3(RubikScreenSolver solver)
     {
     String result;
 
@@ -119,7 +119,7 @@ public class SolverMain implements Runnable
 
   public void run()
     {
-    RubikStateSolver solver = (RubikStateSolver) StateList.SVER.getStateClass();
+    RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getStateClass();
 
     if( mObject == ObjectList.CUBE && mSize == 3)
       {
diff --git a/src/main/java/org/distorted/states/RubikStateAbstract.java b/src/main/java/org/distorted/states/RubikStateAbstract.java
deleted file mode 100644
index c14bcf25..00000000
--- a/src/main/java/org/distorted/states/RubikStateAbstract.java
+++ /dev/null
@@ -1,72 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-
-import org.distorted.main.RubikActivity;
-import org.distorted.objects.ObjectList;
-import org.distorted.patterns.RubikPatternList;
-import org.distorted.tutorials.TutorialList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public abstract class RubikStateAbstract
-  {
-  int getPatternOrdinal()
-    {
-    RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
-    int obj  = play.getObject();
-    int size = play.getSize();
-    int ret = RubikPatternList.getOrdinal(obj,size);
-
-    if( ret<0 )
-      {
-      ret = ObjectList.getSizeIndex(RubikStatePlay.DEF_OBJECT,RubikStatePlay.DEF_SIZE);
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getTutorialOrdinal()
-    {
-    RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
-    int obj  = play.getObject();
-    int size = play.getSize();
-
-    int ret = TutorialList.getOrdinal(obj,size);
-
-    if( ret<0 )
-      {
-      ret = ObjectList.getSizeIndex(RubikStatePlay.DEF_OBJECT,RubikStatePlay.DEF_SIZE);
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  abstract void enterState(RubikActivity act);
-  abstract void leaveState(RubikActivity act);
-  public abstract void savePreferences(SharedPreferences.Editor editor);
-  public abstract void restorePreferences(SharedPreferences preferences);
-  }
diff --git a/src/main/java/org/distorted/states/RubikStateBase.java b/src/main/java/org/distorted/states/RubikStateBase.java
deleted file mode 100644
index 792039a4..00000000
--- a/src/main/java/org/distorted/states/RubikStateBase.java
+++ /dev/null
@@ -1,190 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
-import org.distorted.objects.TwistyObject;
-
-import java.util.ArrayList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-abstract class RubikStateBase extends RubikStateAbstract implements RubikPreRender.ActionFinishedListener
-  {
-  private static final int DURATION_MILLIS = 750;
-
-  private ImageButton mPrevButton, mLockButton;
-
-  private boolean mCanPrevMove;
-
-  private static class Move
-    {
-    private final int mAxis, mRow, mAngle;
-
-    Move(int axis, int row, int angle)
-      {
-      mAxis = axis;
-      mRow  = row;
-      mAngle= angle;
-      }
-    }
-
-  ArrayList<Move> mMoves;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void backMove(RubikPreRender pre)
-    {
-    if( mCanPrevMove )
-      {
-      int numMoves = mMoves.size();
-
-      if( numMoves>0 )
-        {
-        RubikStateBase.Move move = mMoves.remove(numMoves-1);
-        TwistyObject object = pre.getObject();
-
-        int axis  = move.mAxis;
-        int row   = (1<<move.mRow);
-        int angle = move.mAngle;
-        int numRot= Math.abs(angle*object.getBasicAngle()/360);
-
-        if( angle!=0 )
-          {
-          mCanPrevMove = false;
-          pre.addRotation(this, axis, row, -angle, numRot*DURATION_MILLIS);
-          }
-        else
-          {
-          android.util.Log.e("solution", "error: trying to back move of angle 0");
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void toggleLock(RubikActivity act)
-    {
-    act.toggleLock();
-    mLockButton.setImageResource(getLockIcon(act));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getLockIcon(RubikActivity act)
-    {
-    if( act.retLocked() )
-      {
-      return RubikActivity.getDrawable(R.drawable.ui_small_locked,R.drawable.ui_medium_locked, R.drawable.ui_big_locked, R.drawable.ui_huge_locked);
-      }
-    else
-      {
-      return RubikActivity.getDrawable(R.drawable.ui_small_unlocked,R.drawable.ui_medium_unlocked, R.drawable.ui_big_unlocked, R.drawable.ui_huge_unlocked);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void createBottomPane(final RubikActivity act, float width, ImageButton button)
-    {
-    mCanPrevMove = true;
-
-    if( mMoves==null ) mMoves = new ArrayList<>();
-    else               mMoves.clear();
-
-    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
-    layoutBot.removeAllViews();
-
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
-
-    LinearLayout layoutLeft = new LinearLayout(act);
-    layoutLeft.setLayoutParams(params);
-    LinearLayout layoutMid = new LinearLayout(act);
-    layoutMid.setLayoutParams(params);
-    LinearLayout layoutRight = new LinearLayout(act);
-    layoutRight.setLayoutParams(params);
-
-    setupPrevButton(act,width);
-    layoutLeft.addView(mPrevButton);
-    setupLockButton(act,width);
-    layoutMid.addView(mLockButton);
-    layoutRight.addView(button);
-
-    layoutBot.addView(layoutLeft);
-    layoutBot.addView(layoutMid);
-    layoutBot.addView(layoutRight);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setupLockButton(final RubikActivity act, final float width)
-    {
-    final int icon = getLockIcon(act);
-    mLockButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mLockButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        toggleLock(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setupPrevButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_back,R.drawable.ui_medium_cube_back, R.drawable.ui_big_cube_back, R.drawable.ui_huge_cube_back);
-    mPrevButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mPrevButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        RubikPreRender pre = act.getPreRender();
-        backMove(pre);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void addMove(int axis, int row, int angle)
-    {
-    mMoves.add(new Move(axis,row,angle));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onActionFinished(final long effectID)
-    {
-    mCanPrevMove = true;
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStateDone.java b/src/main/java/org/distorted/states/RubikStateDone.java
deleted file mode 100644
index 1f00896d..00000000
--- a/src/main/java/org/distorted/states/RubikStateDone.java
+++ /dev/null
@@ -1,122 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.fragment.app.FragmentManager;
-
-import org.distorted.dialogs.RubikDialogNewRecord;
-import org.distorted.dialogs.RubikDialogSolved;
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStateDone extends RubikStateAbstract
-  {
-  private ImageButton mBackButton;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-    float titleSize = width*RubikActivity.TITLE_TEXT_SIZE;
-    LayoutInflater inflater = act.getLayoutInflater();
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-    TextView label = (TextView)inflater.inflate(R.layout.upper_text, null);
-    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    label.setText(R.string.solved);
-    layoutTop.addView(label);
-
-    // BOT ////////////////////////////
-    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
-    layoutBot.removeAllViews();
-
-    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
-    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,2);
-
-    LinearLayout layoutLeft = new LinearLayout(act);
-    layoutLeft.setLayoutParams(paramsL);
-    LinearLayout layoutRight = new LinearLayout(act);
-    layoutRight.setLayoutParams(paramsR);
-
-    setupBackButton(act,width);
-
-    layoutRight.addView(mBackButton);
-    layoutBot.addView(layoutLeft);
-    layoutBot.addView(layoutRight);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
-    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        StateList.goBack(act);
-
-        FragmentManager mana = act.getSupportFragmentManager();
-        RubikDialogNewRecord diag1 = (RubikDialogNewRecord) mana.findFragmentByTag(RubikDialogNewRecord.getDialogTag());
-        RubikDialogSolved    diag2 = (RubikDialogSolved   ) mana.findFragmentByTag(RubikDialogSolved.getDialogTag());
-
-        if( diag1 !=null ) diag1.dismiss();
-        if( diag2 !=null ) diag2.dismiss();
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStatePattern.java b/src/main/java/org/distorted/states/RubikStatePattern.java
deleted file mode 100644
index a6fce1d5..00000000
--- a/src/main/java/org/distorted/states/RubikStatePattern.java
+++ /dev/null
@@ -1,267 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import androidx.fragment.app.FragmentManager;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.distorted.dialogs.RubikDialogPattern;
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
-import org.distorted.objects.ObjectList;
-import org.distorted.patterns.RubikPattern;
-import org.distorted.patterns.RubikPatternList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStatePattern extends RubikStateAbstract
-  {
-  private TextView mText;
-  private ImageButton mPrevButton, mNextButton, mBackButton;
-  private TextView mMovesText;
-  private int mNumMoves;
-  private int mPatternOrdinal, mCategory, mPattern;
-  private float mButtonSize;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RubikStatePattern()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-    RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
-
-    ObjectList object = RubikPatternList.getObject(mPatternOrdinal);
-    int size = RubikPatternList.getSize(mPatternOrdinal);
-
-    if( !play.setObjectAndSize(act,object,size) )
-      {
-      int objectPlay= play.getObject();
-      int sizePlay  = play.getSize();
-
-      act.changeObject(ObjectList.getObject(objectPlay),sizePlay, false);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-    mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
-    float titleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
-    mPatternOrdinal = getPatternOrdinal();
-    LayoutInflater inflater = act.getLayoutInflater();
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-    mText = (TextView)inflater.inflate(R.layout.upper_pattern_text, null);
-    mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    mText.setText(R.string.patterns);
-    layoutTop.addView(mText);
-
-    // BOT ////////////////////////////
-    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
-    layoutBot.removeAllViews();
-
-    setupPrevButton(act,width);
-    setupNextButton(act,width);
-    setupTextView(act,width);
-
-    setTrioState(false);
-
-    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams((int)(width/2),LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams((int)(width/6),LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams((int)(width/3),LinearLayout.LayoutParams.MATCH_PARENT);
-
-    LinearLayout layoutLeft = new LinearLayout(act);
-    layoutLeft.setLayoutParams(paramsL);
-    LinearLayout layoutMid = new LinearLayout(act);
-    layoutMid.setLayoutParams(paramsM);
-    LinearLayout layoutRight = new LinearLayout(act);
-    layoutRight.setLayoutParams(paramsR);
-
-    layoutLeft.addView(mPrevButton);
-    layoutLeft.addView(mMovesText);
-    layoutLeft.addView(mNextButton);
-
-    setupBackButton(act,width);
-
-    layoutRight.addView(mBackButton);
-
-    layoutBot.addView(layoutLeft);
-    layoutBot.addView(layoutMid);
-    layoutBot.addView(layoutRight);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void showDialog(FragmentManager manager)
-    {
-    RubikDialogPattern diag = new RubikDialogPattern();
-    Bundle bundle = new Bundle();
-    bundle.putInt("tab", mPatternOrdinal );
-    diag.setArguments(bundle);
-    diag.show( manager, RubikDialogPattern.getDialogTag() );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setTrioState(boolean enable)
-    {
-    int state = enable ? View.VISIBLE : View.INVISIBLE;
-
-    if( mPrevButton!=null ) mPrevButton.setVisibility(state);
-    if( mNextButton!=null ) mNextButton.setVisibility(state);
-    if( mMovesText !=null ) mMovesText.setVisibility(state);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final RubikActivity act, final float width)
-    {
-    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
-    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        FragmentManager mana = act.getSupportFragmentManager();
-        StateList.goBack(act);
-        showDialog(mana);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupPrevButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_left,R.drawable.ui_medium_left, R.drawable.ui_big_left, R.drawable.ui_huge_left);
-    mPrevButton = new TransparentImageButton(act,icon,width,0);
-
-    mPrevButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        RubikPattern pattern = RubikPattern.getInstance();
-        RubikPreRender pre = act.getPreRender();
-        pattern.backMove( pre, mPatternOrdinal, mCategory, mPattern);
-        int currMove = pattern.getCurMove(mPatternOrdinal, mCategory, mPattern);
-        mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupNextButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_right,R.drawable.ui_medium_right, R.drawable.ui_big_right, R.drawable.ui_huge_right);
-    mNextButton = new TransparentImageButton(act,icon,width,0);
-
-    mNextButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        RubikPattern pattern = RubikPattern.getInstance();
-        RubikPreRender pre = act.getPreRender();
-        pattern.makeMove( pre, mPatternOrdinal, mCategory, mPattern);
-        int currMove = pattern.getCurMove(mPatternOrdinal, mCategory, mPattern);
-        mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupTextView(final RubikActivity act, final float width)
-    {
-    int padding = (int)(width*RubikActivity.PADDING);
-    int margin  = (int)(width*RubikActivity.MARGIN);
-
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
-    params.topMargin    = margin;
-    params.bottomMargin = margin;
-    params.leftMargin   = margin;
-    params.rightMargin  = margin;
-
-    mMovesText = new TextView(act);
-    mMovesText.setTextSize(20);
-    mMovesText.setLayoutParams(params);
-    mMovesText.setPadding(padding,0,padding,0);
-    mMovesText.setGravity(Gravity.CENTER);
-    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
-    mMovesText.setText(act.getString(R.string.mo_placeholder,0,0));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void setPattern(final RubikActivity act, int ordinal, int category, int pattern)
-    {
-    mPatternOrdinal = ordinal;
-    mCategory       = category;
-    mPattern        = pattern;
-
-    setTrioState(true);
-
-    RubikPattern patt = RubikPattern.getInstance();
-    String patternName = patt.getPatternName(ordinal,category,pattern);
-    mText.setText(patternName);
-
-    mNumMoves   = patt.getNumMoves(ordinal,category,pattern);
-    int currMove= patt.getCurMove(ordinal,category,pattern);
-
-    mMovesText.setText(act.getString(R.string.mo_placeholder,currMove,mNumMoves));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStatePlay.java b/src/main/java/org/distorted/states/RubikStatePlay.java
deleted file mode 100644
index 1011f34b..00000000
--- a/src/main/java/org/distorted/states/RubikStatePlay.java
+++ /dev/null
@@ -1,592 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.drawable.BitmapDrawable;
-import android.os.Bundle;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.GridLayout;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-
-import org.distorted.dialogs.RubikDialogAbout;
-import org.distorted.dialogs.RubikDialogPattern;
-import org.distorted.dialogs.RubikDialogScores;
-import org.distorted.dialogs.RubikDialogTutorial;
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
-import org.distorted.objects.ObjectList;
-import org.distorted.network.RubikScores;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStatePlay extends RubikStateBase
-  {
-  public static final int LEVELS_SHOWN = 10;
-  public static final int DEF_OBJECT= ObjectList.CUBE.ordinal();
-  public static final int DEF_SIZE  =  3;
-
-  private static final int[] BUTTON_LABELS = { R.string.scores,
-                                               R.string.patterns,
-                                               R.string.control,
-                                               R.string.solver,
-                                               R.string.tutorials,
-                                               R.string.about };
-
-  private static final int NUM_BUTTONS = BUTTON_LABELS.length;
-  private static final float LAST_BUTTON = 1.5f;
-
-  private ImageButton mObjButton, mMenuButton, mSolveButton;
-  private Button mPlayButton;
-  private PopupWindow mObjectPopup, mMenuPopup, mPlayPopup;
-  private int mObject = DEF_OBJECT;
-  private int mSize   = DEF_SIZE;
-  private int mObjectSize, mMenuLayoutWidth, mMenuLayoutHeight, mPlayLayoutWidth;
-  private int mLevelValue;
-  private float mButtonSize, mMenuItemSize, mMenuTextSize;
-  private int mColCount, mRowCount;
-  private LinearLayout mPlayLayout;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-
-    mMenuTextSize = width*RubikActivity.MENU_MED_TEXT_SIZE;
-    mButtonSize   = width*RubikActivity.BUTTON_TEXT_SIZE;
-    mMenuItemSize = width*RubikActivity.MENU_ITEM_SIZE;
-
-    mRowCount = ObjectList.getRowCount();
-    mColCount = ObjectList.getColumnCount();
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-
-    setupObjectWindow(act,width);
-    setupObjectButton(act,width);
-    layoutTop.addView(mObjButton);
-
-    setupMenuWindow(act,width);
-    setupMenuButton(act,width);
-    layoutTop.addView(mMenuButton);
-
-    setupPlayWindow(act,width);
-    setupPlayButton(act,width);
-    layoutTop.addView(mPlayButton);
-
-    setupSolveButton(act,width);
-    createBottomPane(act,width,mSolveButton);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupObjectButton(final RubikActivity act, final float width)
-    {
-    final int margin  = (int)(width*RubikActivity.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);
-    mObjButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mObjButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View view)
-        {
-        if( act.getPreRender().canPlay() )
-          {
-          if( mObjectPopup==null )
-            {
-            // I completely don't understand it, but Firebase says occasionally mObjectPopup is null here. Recreate.
-            float width = act.getScreenWidthInPixels();
-            setupObjectWindow(act,width);
-            }
-
-          mObjectPopup.setFocusable(false);
-          mObjectPopup.update();
-
-          View popupView = mObjectPopup.getContentView();
-          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
-
-          mObjectPopup.showAsDropDown(view, margin, margin);
-          mObjectPopup.update(view, mObjectSize*mColCount, mObjectSize*mRowCount);
-
-          mObjectPopup.setFocusable(true);
-          mObjectPopup.update();
-          }
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupPlayButton(final RubikActivity act, final float width)
-    {
-    final int margin  = (int)(width*RubikActivity.MARGIN);
-    mPlayButton = new TransparentButton(act, R.string.play, mButtonSize, width);
-
-    mPlayButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View view)
-        {
-        if( act.getPreRender().canPlay() )
-          {
-          if( mPlayPopup==null )
-            {
-            // I completely don't understand it, but Firebase says occasionally mPlayPopup is null here. Recreate.
-            float width = act.getScreenWidthInPixels();
-            setupPlayWindow(act,width);
-            }
-
-          mPlayPopup.setFocusable(false);
-          mPlayPopup.update();
-
-          View popupView = mPlayPopup.getContentView();
-          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
-
-          final int sizeIndex = ObjectList.getSizeIndex(mObject,mSize);
-          final int maxLevel = ObjectList.getMaxLevel(mObject, sizeIndex);
-          final int levelsShown = Math.min(maxLevel,LEVELS_SHOWN);
-
-          mPlayPopup.showAsDropDown(view, margin, margin);
-          mPlayPopup.update(view, mPlayLayoutWidth, (int)(levelsShown*(mMenuItemSize+margin)+3*margin+mMenuItemSize*(LAST_BUTTON-1.0f)));
-          mPlayPopup.setFocusable(true);
-          mPlayPopup.update();
-          }
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupMenuButton(final RubikActivity act, final float width)
-    {
-    final int margin  = (int)(width*RubikActivity.MARGIN);
-    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_menu,R.drawable.ui_medium_menu, R.drawable.ui_big_menu, R.drawable.ui_huge_menu);
-    mMenuButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mMenuButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View view)
-        {
-        if( act.getPreRender().canPlay() )
-          {
-          if( mMenuPopup==null )
-            {
-            // I completely don't understand it, but Firebase says occasionally mMenuPopup is null here. Recreate.
-            float width = act.getScreenWidthInPixels();
-            setupMenuWindow(act,width);
-            }
-
-          mMenuPopup.setFocusable(false);
-          mMenuPopup.update();
-
-          View popupView = mMenuPopup.getContentView();
-          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
-
-          mMenuPopup.showAsDropDown(view, (int)(-width/12), margin, Gravity.CENTER);
-          mMenuPopup.update(view, mMenuLayoutWidth, mMenuLayoutHeight);
-          mMenuPopup.setFocusable(true);
-          mMenuPopup.update();
-          }
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupObjectWindow(final RubikActivity act, final float width)
-    {
-    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-    final View layout = layoutInflater.inflate(R.layout.popup_objects, null);
-    GridLayout objectGrid = layout.findViewById(R.id.objectGrid);
-
-    int[] indices = ObjectList.getIndices();
-
-    GridLayout.Spec[] rowSpecs = new GridLayout.Spec[mRowCount];
-    GridLayout.Spec[] colSpecs = new GridLayout.Spec[mColCount];
-
-    objectGrid.setColumnCount(mColCount);
-    objectGrid.setRowCount(mRowCount);
-
-    int[] nextInRow = new int[mRowCount];
-
-    for(int row=0; row<mRowCount; row++)
-      {
-      rowSpecs[row] = GridLayout.spec(row);
-      nextInRow[row]= 0;
-      }
-    for(int col=0; col<mColCount; col++)
-      {
-      colSpecs[col] = GridLayout.spec(col);
-      }
-
-    mObjectPopup = new PopupWindow(act);
-    mObjectPopup.setContentView(layout);
-    mObjectPopup.setFocusable(true);
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube2,R.drawable.ui_medium_cube2, R.drawable.ui_big_cube2, R.drawable.ui_huge_cube2);
-
-    BitmapDrawable bd = (BitmapDrawable) act.getResources().getDrawable(icon);
-    int cubeWidth = bd.getIntrinsicWidth();
-    int margin = (int)(width*RubikActivity.LARGE_MARGIN);
-    mObjectSize = (int)(cubeWidth + 2*margin + 0.5f);
-
-    for(int object=0; object< ObjectList.NUM_OBJECTS; object++)
-      {
-      final ObjectList list = ObjectList.getObject(object);
-      final int[] sizes = list.getSizes();
-      int[] icons = list.getIconIDs();
-      int len = sizes.length;
-      final int obj = object;
-      int row = indices[object];
-
-      for(int i=0; i<len; i++)
-        {
-        final int index = i;
-
-        ImageButton button = new ImageButton(act);
-        button.setBackgroundResource(icons[i]);
-        button.setOnClickListener( new View.OnClickListener()
-          {
-          @Override
-          public void onClick(View v)
-            {
-            if( act.getPreRender().canPlay() && StateList.getCurrentState()== StateList.PLAY )
-              {
-              mObject = obj;
-              mSize   = sizes[index];
-              act.changeObject(list,sizes[index], true);
-              adjustLevels(act);
-              mMoves.clear();
-              }
-
-            mObjectPopup.dismiss();
-            }
-          });
-
-        GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowSpecs[row],colSpecs[nextInRow[row]]);
-        params.bottomMargin = margin;
-        params.topMargin    = margin;
-        params.leftMargin   = margin;
-        params.rightMargin  = margin;
-
-        nextInRow[row]++;
-
-        objectGrid.addView(button, params);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupMenuWindow(final RubikActivity act, final float width)
-    {
-    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-    final View layout = layoutInflater.inflate(R.layout.popup_menu, null);
-    LinearLayout menuLayout = layout.findViewById(R.id.menuGrid);
-
-    mMenuPopup = new PopupWindow(act);
-    mMenuPopup.setContentView(layout);
-    mMenuPopup.setFocusable(true);
-    int margin  = (int)(width*RubikActivity.MARGIN);
-    int padding = (int)(width*RubikActivity.PADDING);
-
-    mMenuLayoutWidth = (int)(width/2);
-    mMenuLayoutHeight= (int)(2*margin + NUM_BUTTONS*(mMenuItemSize+margin));
-
-    LinearLayout.LayoutParams p = new LinearLayout.LayoutParams( mMenuLayoutWidth - 2*padding, (int)mMenuItemSize);
-
-    for(int i=0; i<NUM_BUTTONS; i++)
-      {
-      final int but = i;
-      Button button = new Button(act);
-      button.setLayoutParams(p);
-      button.setText(BUTTON_LABELS[i]);
-      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
-
-      button.setOnClickListener( new View.OnClickListener()
-        {
-        @Override
-        public void onClick(View v)
-          {
-          mMenuPopup.dismiss();
-          MenuAction(act,but);
-          }
-        });
-
-      menuLayout.addView(button);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupPlayWindow(final RubikActivity act, final float width)
-    {
-    LayoutInflater layoutInflater = (LayoutInflater)act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-    final View layout = layoutInflater.inflate(R.layout.popup_play, null);
-    mPlayLayout = layout.findViewById(R.id.playGrid);
-
-    mPlayLayoutWidth = (int)(width*0.4f);
-
-    mPlayPopup = new PopupWindow(act);
-    mPlayPopup.setContentView(layout);
-    mPlayPopup.setFocusable(true);
-
-    adjustLevels(act);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void controlTheCube()
-    {
-    android.util.Log.e("D", "Control");
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void MenuAction(RubikActivity act, int button)
-    {
-    switch(button)
-      {
-      case 0: RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
-              int object = play.getObject();
-              int size   = play.getSize();
-              int sizeIndex = ObjectList.getSizeIndex(object,size);
-              Bundle sBundle = new Bundle();
-              sBundle.putInt("tab", ObjectList.pack(object,sizeIndex) );
-              sBundle.putBoolean("submitting", false);
-              RubikDialogScores scores = new RubikDialogScores();
-              scores.setArguments(sBundle);
-              scores.show(act.getSupportFragmentManager(), null);
-              break;
-      case 1: RubikDialogPattern pDiag = new RubikDialogPattern();
-              Bundle pBundle = new Bundle();
-              int pOrd = getPatternOrdinal();
-              pBundle.putInt("tab", pOrd );
-              pDiag.setArguments(pBundle);
-              pDiag.show( act.getSupportFragmentManager(), RubikDialogPattern.getDialogTag() );
-              break;
-      case 2: controlTheCube();
-              break;
-      case 3: StateList.switchState(act, StateList.SVER);
-              break;
-      case 4: RubikDialogTutorial tDiag = new RubikDialogTutorial();
-              Bundle tBundle = new Bundle();
-              int tOrd = getTutorialOrdinal();
-              tBundle.putInt("tab", tOrd );
-              tDiag.setArguments(tBundle);
-              tDiag.show( act.getSupportFragmentManager(), RubikDialogTutorial.getDialogTag() );
-              break;
-      case 5: RubikDialogAbout aDiag = new RubikDialogAbout();
-              aDiag.show(act.getSupportFragmentManager(), null);
-              break;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setupSolveButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_solve,R.drawable.ui_medium_cube_solve, R.drawable.ui_big_cube_solve, R.drawable.ui_huge_cube_solve);
-    mSolveButton = new TransparentImageButton(act, icon, width,LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mSolveButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        act.getPreRender().solveObject();
-        mMoves.clear();
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-    editor.putInt("statePlay_object", mObject);
-    editor.putInt("statePlay_size"  , mSize);
-
-    if( mObjectPopup!=null )
-      {
-      mObjectPopup.dismiss();
-      mObjectPopup = null;
-      }
-
-    if( mMenuPopup!=null )
-      {
-      mMenuPopup.dismiss();
-      mMenuPopup = null;
-      }
-
-    if( mPlayPopup!=null )
-      {
-      mPlayPopup.dismiss();
-      mPlayPopup = null;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-    mObject= preferences.getInt("statePlay_object", DEF_OBJECT);
-    mSize  = preferences.getInt("statePlay_size"  , DEF_SIZE  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean setObjectAndSize(RubikActivity act, ObjectList obj, int size)
-    {
-    if( mObject!=obj.ordinal() || mSize != size )
-      {
-      boolean success = false;
-
-      for( int s: obj.getSizes() )
-        if( s==size )
-          {
-          success = true;
-          break;
-          }
-
-      if( success )
-        {
-        mObject = obj.ordinal();
-        mSize   = size;
-
-        if( mPlayLayout!=null ) adjustLevels(act);
-        }
-
-      return success;
-      }
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void adjustLevels(final RubikActivity act)
-    {
-    int sizeIndex = ObjectList.getSizeIndex(mObject,mSize);
-    int maxLevel = ObjectList.getMaxLevel(mObject, sizeIndex);
-    int numLevel = Math.min(maxLevel, LEVELS_SHOWN);
-    String[] levels = new String[numLevel];
-
-    for(int i=0; i<numLevel-1; i++)
-      {
-      levels[i] = act.getString(R.string.lv_placeholder,i+1);
-      }
-
-    levels[numLevel-1] = act.getString(R.string.level_full);
-
-    if( mLevelValue>maxLevel || mLevelValue<1 ||
-       (mLevelValue<maxLevel || mLevelValue>LEVELS_SHOWN ) )
-      {
-      mLevelValue=1;
-      }
-
-    float width  = act.getScreenWidthInPixels();
-    int margin   = (int)(width*RubikActivity.MARGIN);
-    int padding  = (int)(width*RubikActivity.PADDING);
-    int butWidth = mPlayLayoutWidth - 2*padding;
-    int butHeight= (int)mMenuItemSize;
-    int lastButH = (int)(mMenuItemSize*LAST_BUTTON) ;
-
-    LinearLayout.LayoutParams pM = new LinearLayout.LayoutParams( butWidth, butHeight );
-    pM.setMargins(margin, 0, margin, margin);
-    LinearLayout.LayoutParams pT = new LinearLayout.LayoutParams( butWidth, butHeight );
-    pT.setMargins(margin, margin, margin, margin);
-    LinearLayout.LayoutParams pB = new LinearLayout.LayoutParams( butWidth, lastButH  );
-    pB.setMargins(margin, margin, margin, 2*margin);
-
-    mPlayLayout.removeAllViews();
-
-    RubikScores scores = RubikScores.getInstance();
-
-    for(int i=0; i<numLevel; i++)
-      {
-      final int scrambles = i<numLevel-1 ? i+1 : maxLevel;
-      Button button = new Button(act);
-      button.setLayoutParams(i==0 ? pT : (i==numLevel-1 ? pB : pM));
-      button.setText(levels[i]);
-      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
-
-      int icon = scores.isSolved(mObject, sizeIndex, i) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
-      button.setCompoundDrawablesWithIntrinsicBounds(icon,0,0,0);
-
-      button.setOnClickListener( new View.OnClickListener()
-        {
-        @Override
-        public void onClick(View v)
-          {
-          RubikPreRender pre = act.getPreRender();
-
-          if( pre.canPlay() )
-            {
-            mPlayPopup.dismiss();
-            mLevelValue = scrambles;
-            pre.scrambleObject(mLevelValue);
-            }
-          }
-        });
-
-      mPlayLayout.addView(button);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getLevel()
-    {
-    return mLevelValue;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getObject()
-    {
-    return mObject;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getSize()
-    {
-    return mSize;
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStateReady.java b/src/main/java/org/distorted/states/RubikStateReady.java
deleted file mode 100644
index 286b295d..00000000
--- a/src/main/java/org/distorted/states/RubikStateReady.java
+++ /dev/null
@@ -1,96 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStateReady extends RubikStateBase
-  {
-  private ImageButton mBackButton;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-    float titleSize = width*RubikActivity.TITLE_TEXT_SIZE;
-    LayoutInflater inflater = act.getLayoutInflater();
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-    TextView label = (TextView)inflater.inflate(R.layout.upper_text, null);
-    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    label.setText(R.string.ready);
-    layoutTop.addView(label);
-
-    setupBackButton(act,width);
-    createBottomPane(act,width,mBackButton);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
-    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        StateList.goBack(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStateSolution.java b/src/main/java/org/distorted/states/RubikStateSolution.java
deleted file mode 100644
index bde960b2..00000000
--- a/src/main/java/org/distorted/states/RubikStateSolution.java
+++ /dev/null
@@ -1,295 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
-import org.distorted.objects.TwistyObject;
-import org.distorted.patterns.RubikPattern;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStateSolution extends RubikStateAbstract implements RubikPreRender.ActionFinishedListener
-  {
-  private static final int DURATION_MILLIS = 750;
-
-  private ImageButton mPrevButton, mNextButton, mBackButton;
-  private TextView mMovesText;
-  private int[][] mMoves;
-  private int mCurrMove, mNumMoves;
-  private boolean mCanRotate;
-  private float mButtonSize;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-    TwistyObject object = act.getObject();
-    object.solve();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-    mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
-    float titleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
-
-    LayoutInflater inflater = act.getLayoutInflater();
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-
-    final TextView text = (TextView)inflater.inflate(R.layout.upper_text, null);
-    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    text.setText(R.string.solution);
-    layoutTop.addView(text);
-
-    // BOT ////////////////////////////
-    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
-    layoutBot.removeAllViews();
-
-    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams((int)(width/2),LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams((int)(width/6),LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams((int)(width/3),LinearLayout.LayoutParams.MATCH_PARENT);
-
-    LinearLayout layoutLeft = new LinearLayout(act);
-    layoutLeft.setLayoutParams(paramsL);
-    LinearLayout layoutMid = new LinearLayout(act);
-    layoutMid.setLayoutParams(paramsM);
-    LinearLayout layoutRight = new LinearLayout(act);
-    layoutRight.setLayoutParams(paramsR);
-
-    setupPrevButton(act,width);
-    setupNextButton(act,width);
-    setupTextView(act,width);
-
-    layoutLeft.addView(mPrevButton);
-    layoutLeft.addView(mMovesText);
-    layoutLeft.addView(mNextButton);
-
-    setupBackButton(act,width);
-
-    layoutRight.addView(mBackButton);
-
-    layoutBot.addView(layoutLeft);
-    layoutBot.addView(layoutMid);
-    layoutBot.addView(layoutRight);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupPrevButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_left,R.drawable.ui_medium_left, R.drawable.ui_big_left, R.drawable.ui_huge_left);
-    mPrevButton = new TransparentImageButton(act,icon,width,0);
-
-    mPrevButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        RubikPreRender pre = act.getPreRender();
-        backMove(pre);
-        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupNextButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_right,R.drawable.ui_medium_right, R.drawable.ui_big_right, R.drawable.ui_huge_right);
-    mNextButton = new TransparentImageButton(act,icon,width,0);
-
-    mNextButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        RubikPreRender pre = act.getPreRender();
-        makeMove(pre);
-        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupTextView(final RubikActivity act, final float width)
-    {
-    int padding = (int)(width*RubikActivity.PADDING);
-    int margin  = (int)(width*RubikActivity.MARGIN);
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
-    params.topMargin    = margin;
-    params.bottomMargin = margin;
-    params.leftMargin   = margin;
-    params.rightMargin  = margin;
-
-    mMovesText = new TextView(act);
-    mMovesText.setTextSize(20);
-    mMovesText.setLayoutParams(params);
-    mMovesText.setPadding(padding,0,padding,0);
-    mMovesText.setGravity(Gravity.CENTER);
-    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
-    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final RubikActivity act, final float width)
-    {
-    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
-    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        StateList.goBack(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void makeMove(RubikPreRender pre)
-    {
-    if( mCanRotate )
-      {
-      mCurrMove++;
-
-      if( mCurrMove>mNumMoves )
-        {
-        mCurrMove= 0;
-        pre.initializeObject(null);
-        }
-      else
-        {
-        int axis     = mMoves[mCurrMove-1][0];
-		    int rowBitmap= mMoves[mCurrMove-1][1];
-		    int bareAngle= mMoves[mCurrMove-1][2];
-        int angle    = bareAngle*(360/pre.getObject().getBasicAngle());
-        int numRot   = Math.abs(bareAngle);
-
-        if( angle!=0 )
-          {
-          mCanRotate = false;
-          pre.addRotation(this, axis, rowBitmap, angle, numRot*DURATION_MILLIS);
-          }
-        else
-          {
-          android.util.Log.e("solution", "error: solution contains angle 0");
-          }
-        }
-      }
-    else
-      {
-      android.util.Log.e("solution", "failed to make move!");
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void backMove(RubikPreRender pre)
-    {
-    if( mCanRotate )
-      {
-      mCurrMove--;
-
-      if( mCurrMove<0 )
-        {
-        mCurrMove=mNumMoves;
-        pre.initializeObject(mMoves);
-        }
-      else
-        {
-        int axis     = mMoves[mCurrMove][0];
-		    int rowBitmap= mMoves[mCurrMove][1];
-		    int bareAngle= mMoves[mCurrMove][2];
-        int angle    = bareAngle*(360/pre.getObject().getBasicAngle());
-        int numRot   = Math.abs(bareAngle);
-
-        if( angle!=0 )
-          {
-          mCanRotate = false;
-          pre.addRotation(this, axis, rowBitmap, -angle, numRot*DURATION_MILLIS);
-          }
-        else
-          {
-          android.util.Log.e("solution", "error: solution contains angle 0");
-          }
-        }
-      }
-    else
-      {
-      android.util.Log.e("solution", "failed to back move!");
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setupMoves(final RubikActivity act, String moves)
-    {
-    mCanRotate= true;
-    mCurrMove = 0;
-    mNumMoves = moves.length()/4;
-    mMoves    = new int[mNumMoves][3];
-
-    RubikPattern.parseMoves(mMoves,mNumMoves,moves);
-
-    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onActionFinished(final long effectID)
-    {
-    mCanRotate = true;
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStateSolver.java b/src/main/java/org/distorted/states/RubikStateSolver.java
deleted file mode 100644
index 978b9c64..00000000
--- a/src/main/java/org/distorted/states/RubikStateSolver.java
+++ /dev/null
@@ -1,319 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import androidx.core.content.ContextCompat;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import org.distorted.dialogs.RubikDialogSolverError;
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-import org.distorted.main.RubikPreRender;
-import org.distorted.objects.TwistyObject;
-import org.distorted.objects.ObjectList;
-import org.distorted.solvers.ImplementedSolversList;
-import org.distorted.solvers.SolverMain;
-
-import java.lang.ref.WeakReference;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStateSolver extends RubikStateAbstract
-  {
-  private static Bitmap[] mBitmap;
-  private ImageButton[] mColorButton;
-  private ImageButton mBackButton, mSolveButton;
-  private boolean mSolving;
-  private int mCurrentColor;
-  private int[] mFaceColors;
-  private int mNumFaces;
-  private float mBitmapSize;
-
-  private ObjectList mCurrentObject;
-  private int mCurrentObjectSize;
-
-  private WeakReference<RubikActivity> mWeakAct;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-    float heigh = act.getScreenHeightInPixels();
-
-    int sizeV = (int)(heigh*RubikActivity.SOLVER_BMP_V_SIZE);
-    int sizeH = (int)(width*RubikActivity.SOLVER_BMP_H_SIZE);
-
-    mBitmapSize = Math.min(sizeV,sizeH);
-
-    mWeakAct = new WeakReference<>(act);
-
-    mSolving = false;
-
-    mCurrentObject     = ImplementedSolversList.getObject(0);
-    mCurrentObjectSize = ImplementedSolversList.getObjectSize(0);
-
-    act.setupObject(mCurrentObject, mCurrentObjectSize, null);
-    RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
-    play.setObjectAndSize(act, mCurrentObject, mCurrentObjectSize);
-
-    mFaceColors = ObjectList.retFaceColors(mCurrentObject);
-    mNumFaces   = mFaceColors!=null ? mFaceColors.length : 0;
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-
-    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
-
-    LinearLayout layoutLeft = new LinearLayout(act);
-    layoutLeft.setLayoutParams(paramsL);
-    LinearLayout layoutMid = new LinearLayout(act);
-    layoutMid.setLayoutParams(paramsL);
-    LinearLayout layoutRight = new LinearLayout(act);
-    layoutRight.setLayoutParams(paramsL);
-
-    if( mNumFaces>0 )
-      {
-      setupBitmaps();
-      setupColorButtons(act,width);
-      markButton(act);
-      }
-
-    for(ImageButton button: mColorButton) layoutTop.addView(button);
-
-    // BOT ////////////////////////////
-    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
-    layoutBot.removeAllViews();
-
-    setupSolveButton(act,width);
-    setupBackButton(act,width);
-
-    layoutLeft.addView(mSolveButton);
-    layoutRight.addView(mBackButton);
-
-    layoutBot.addView(layoutLeft);
-    layoutBot.addView(layoutMid);
-    layoutBot.addView(layoutRight);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBitmaps()
-    {
-    final int SIZE = (int)mBitmapSize;
-    final float R = SIZE*0.15f;
-    final float M = SIZE*0.08f;
-
-    mBitmap = new Bitmap[mNumFaces];
-
-    Paint paint = new Paint();
-    paint.setColor(0xff008800);
-    paint.setStyle(Paint.Style.FILL);
-
-    paint.setAntiAlias(true);
-    paint.setTextAlign(Paint.Align.CENTER);
-    paint.setStyle(Paint.Style.FILL);
-
-    for(int i=0; i<mNumFaces; i++)
-      {
-      mBitmap[i] = Bitmap.createBitmap(SIZE, SIZE, Bitmap.Config.ARGB_8888);
-      Canvas canvas = new Canvas(mBitmap[i]);
-
-      paint.setColor(0xff000000);
-      canvas.drawRect(0, 0, SIZE, SIZE, paint);
-
-      paint.setColor(mFaceColors[i]);
-      canvas.drawRoundRect( M, M, SIZE-M, SIZE-M, R, R, paint);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupColorButtons(final RubikActivity act, final float width)
-    {
-    mColorButton = new ImageButton[mNumFaces];
-    int padding = (int)(width*RubikActivity.PADDING);
-    int margin  = (int)(width*RubikActivity.MARGIN);
-
-    for(int i=0; i<mNumFaces; i++)
-      {
-      final int ii = i;
-      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
-      params.topMargin    = margin;
-      params.bottomMargin = margin;
-      params.leftMargin   = margin;
-      params.rightMargin  = margin;
-
-      mColorButton[i] = new ImageButton(act);
-      mColorButton[i].setLayoutParams(params);
-      mColorButton[i].setPadding(padding,0,padding,0);
-      mColorButton[i].setImageBitmap(mBitmap[i]);
-
-      mColorButton[i].setOnClickListener( new View.OnClickListener()
-        {
-        @Override
-        public void onClick(View view)
-          {
-          mCurrentColor = ii;
-          markButton(act);
-          }
-        });
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupSolveButton(final RubikActivity act, final float width)
-    {
-    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_solve,R.drawable.ui_medium_solve, R.drawable.ui_big_solve, R.drawable.ui_huge_solve);
-    mSolveButton = new TransparentImageButton(act,icon,width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mSolveButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        if( !mSolving )
-          {
-          mSolving = true;
-          TwistyObject object = act.getObject();
-          String objectString = object.retObjectString();
-          SolverMain solver = new SolverMain( act.getResources(), mCurrentObject, mCurrentObjectSize, objectString );
-          solver.start();
-          }
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final RubikActivity act, final float width)
-    {
-    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
-    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        RubikPreRender pre = act.getPreRender();
-        pre.resetAllTextureMaps();
-        StateList.goBack(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void markButton(RubikActivity act)
-    {
-    for(int b=0; b<mNumFaces; b++)
-      {
-      Drawable d = mColorButton[b].getBackground();
-
-      if( b==mCurrentColor )
-        {
-        d.setColorFilter(ContextCompat.getColor(act,R.color.red), PorterDuff.Mode.MULTIPLY);
-        }
-      else
-        {
-        d.clearColorFilter();
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-    editor.putInt("stateSolver_color", mCurrentColor);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-    mCurrentColor = preferences.getInt("stateSolver_color", 0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getCurrentColor()
-    {
-    return mCurrentColor;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void setSolved(final String moves)
-    {
-    mSolving = false;
-    final RubikActivity act = mWeakAct.get();
-
-    if( act!=null )
-      {
-      act.runOnUiThread(new Runnable()
-        {
-        @Override
-        public void run()
-          {
-          StateList.switchState(act, StateList.SOLU);
-          RubikStateSolution solution = (RubikStateSolution) StateList.SOLU.getStateClass();
-          solution.setupMoves(act, moves);
-          }
-        });
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void displayErrorDialog( String message)
-    {
-    mSolving = false;
-    RubikActivity act = mWeakAct.get();
-
-    if( act!=null )
-      {
-      RubikDialogSolverError dialog = new RubikDialogSolverError();
-      Bundle bundle = new Bundle();
-      bundle.putString("error", message );
-      dialog.setArguments(bundle);
-      dialog.show( act.getSupportFragmentManager(), null);
-      }
-    }
-  }
diff --git a/src/main/java/org/distorted/states/RubikStateSolving.java b/src/main/java/org/distorted/states/RubikStateSolving.java
deleted file mode 100644
index 495af9ad..00000000
--- a/src/main/java/org/distorted/states/RubikStateSolving.java
+++ /dev/null
@@ -1,194 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
-import org.distorted.objects.ObjectList;
-import org.distorted.network.RubikScores;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikStateSolving extends RubikStateBase
-  {
-  private TextView mTime;
-  private Timer mTimer;
-  private long mStartTime;
-  private boolean mRunning;
-  private RubikScores mScores;
-  private long mElapsed;
-  private ImageButton mBackButton;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RubikStateSolving()
-    {
-    mScores = RubikScores.getInstance();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void leaveState(RubikActivity act)
-    {
-    stopCounting();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void enterState(final RubikActivity act)
-    {
-    float width = act.getScreenWidthInPixels();
-    float titleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
-
-    startCounting(act);
-
-    LayoutInflater inflater = act.getLayoutInflater();
-
-    // TOP ////////////////////////////
-    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
-    layoutTop.removeAllViews();
-    mTime = (TextView)inflater.inflate(R.layout.upper_text, null);
-    int elapsed = (int)mElapsed/1000;
-    mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    mTime.setText(act.getString(R.string.tm_placeholder,elapsed/60,elapsed%60));
-    layoutTop.addView(mTime);
-
-    setupBackButton(act,width);
-    createBottomPane(act,width,mBackButton);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final RubikActivity act, final float width)
-    {
-    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
-    mBackButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        StateList.goBack(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(SharedPreferences.Editor editor)
-    {
-    mElapsed = System.currentTimeMillis()-mStartTime;
-    editor.putLong("stateSolving_elapsed" , mElapsed);
-    mScores.savePreferences(editor);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(SharedPreferences preferences)
-    {
-    mElapsed = preferences.getLong("stateSolving_elapsed" , 0 );
-    mScores.restorePreferences(preferences);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void startCounting(final RubikActivity act)
-    {
-    if( !mRunning )
-      {
-      mRunning = true;
-      mStartTime = System.currentTimeMillis() - mElapsed;
-      mTimer = new Timer();
-
-      mTimer.scheduleAtFixedRate(new TimerTask()
-        {
-        @Override
-        public void run()
-          {
-          act.runOnUiThread(new Runnable()
-            {
-            @Override
-            public void run()
-              {
-              int elapsed = (int)(System.currentTimeMillis()-mStartTime)/1000;
-              mTime.setText(act.getString(R.string.tm_placeholder,elapsed/60,elapsed%60));
-              }
-            });
-          }
-        }, 0, 1000);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void stopCounting()
-    {
-    if( mTimer!=null )
-      {
-      mTimer.cancel();
-      mTimer = null;
-      }
-
-    mRunning = false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public long getRecord()
-    {
-    if( mRunning )
-      {
-      stopCounting();
-
-      mElapsed = System.currentTimeMillis()-mStartTime;
-
-      RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
-      int object  = play.getObject();
-      int size    = play.getSize();
-      int level   = play.getLevel();
-      int realSize= ObjectList.getSizeIndex(object,size);
-
-      boolean isNew = mScores.setRecord(object, realSize, level, mElapsed);
-
-      return isNew ? mElapsed : -mElapsed;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void resetElapsed()
-    {
-    mElapsed = 0;
-    }
-  }
diff --git a/src/main/java/org/distorted/states/StateList.java b/src/main/java/org/distorted/states/StateList.java
deleted file mode 100644
index a869cfb9..00000000
--- a/src/main/java/org/distorted/states/StateList.java
+++ /dev/null
@@ -1,182 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-import com.google.firebase.analytics.FirebaseAnalytics;
-
-import org.distorted.main.RubikActivity;
-import static org.distorted.main.RubikSurfaceView.*;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public enum StateList
-  {
-  PLAY ( null , MODE_ROTATE , new RubikStatePlay()     ),
-  SOLV ( PLAY , MODE_ROTATE , new RubikStateSolving()  ),
-  PATT ( PLAY , MODE_DRAG   , new RubikStatePattern()  ),
-  SVER ( PLAY , MODE_REPLACE, new RubikStateSolver()   ),
-  SOLU ( SVER , MODE_DRAG   , new RubikStateSolution() ),
-  READ ( PLAY , MODE_ROTATE , new RubikStateReady()    ),
-  DONE ( PLAY , MODE_DRAG   , new RubikStateDone()     ),
-  ;
-
-  public static final int LENGTH = values().length;
-  private final StateList mBack;
-  private int mMode;
-  private final RubikStateAbstract mClass;
-  private static final StateList[] states;
-
-  private static StateList mCurrState;
-
-  static
-    {
-    int i = 0;
-    states = new StateList[LENGTH];
-
-    for(StateList state: StateList.values())
-      {
-      states[i] = state;
-      i++;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static StateList getState(int ordinal)
-    {
-    return ordinal>=0 && ordinal<LENGTH ?  states[ordinal] : PLAY;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static StateList getStateFromName(String name)
-    {
-    for(int i=0; i<LENGTH; i++)
-      {
-      if( name.equals(states[i].name()) )
-        {
-        return states[i];
-        }
-      }
-
-    return PLAY;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static StateList getCurrentState()
-    {
-    return mCurrState;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static int getMode()
-    {
-    return mCurrState.mMode;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void savePreferences(SharedPreferences.Editor editor)
-    {
-    editor.putString("curr_state_name", mCurrState.name() );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void restorePreferences(SharedPreferences preferences)
-    {
-    String currStateName = preferences.getString("curr_state_name", StateList.PLAY.name() );
-    mCurrState = getStateFromName(currStateName);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void goBack(RubikActivity act)
-    {
-    switchState(act, mCurrState.mBack );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void setState(RubikActivity act)
-    {
-    mCurrState.enterState(act);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void switchState(RubikActivity act, StateList next)
-    {
-    if( next!=null )
-      {
-      FirebaseAnalytics analytics = act.getAnalytics();
-
-      if( analytics!=null )
-        {
-        Bundle bundle = new Bundle();
-        bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, next.toString());
-        analytics.logEvent(FirebaseAnalytics.Event.LEVEL_START, bundle);
-        }
-
-      if( mCurrState!=null ) mCurrState.leaveState(act);
-      next.enterState(act);
-      mCurrState = next;
-      }
-    else
-      {
-      act.finish();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  StateList(StateList back, int mode, RubikStateAbstract clazz)
-    {
-    mBack = back;
-    mMode = mode;
-    mClass= clazz;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public RubikStateAbstract getStateClass()
-    {
-    return mClass;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void leaveState(RubikActivity act)
-    {
-    mClass.leaveState(act);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void enterState(RubikActivity act)
-    {
-    mClass.enterState(act);
-    }
-  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/states/TransparentButton.java b/src/main/java/org/distorted/states/TransparentButton.java
deleted file mode 100644
index 6325a434..00000000
--- a/src/main/java/org/distorted/states/TransparentButton.java
+++ /dev/null
@@ -1,56 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.TypedValue;
-import android.widget.LinearLayout;
-
-import org.distorted.main.RubikActivity;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-@SuppressLint("ViewConstructor")
-public class TransparentButton extends androidx.appcompat.widget.AppCompatButton
-{
-   public TransparentButton(Context context, int resId, float size, float scrWidth)
-      {
-      super(context);
-
-      final int padding = (int)(scrWidth*RubikActivity.PADDING);
-      final int margin  = (int)(scrWidth*RubikActivity.MARGIN);
-
-      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
-      params.topMargin    = margin;
-      params.bottomMargin = margin;
-      params.leftMargin   = margin;
-      params.rightMargin  = margin;
-
-      setLayoutParams(params);
-      setPadding(padding,0,padding,0);
-      setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
-      setText(resId);
-
-      TypedValue outValue = new TypedValue();
-      context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
-      setBackgroundResource(outValue.resourceId);
-      }
-}
diff --git a/src/main/java/org/distorted/states/TransparentImageButton.java b/src/main/java/org/distorted/states/TransparentImageButton.java
deleted file mode 100644
index 22384a9d..00000000
--- a/src/main/java/org/distorted/states/TransparentImageButton.java
+++ /dev/null
@@ -1,56 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.states;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.TypedValue;
-import android.widget.LinearLayout;
-
-import org.distorted.main.RubikActivity;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-@SuppressLint("ViewConstructor")
-public class TransparentImageButton extends androidx.appcompat.widget.AppCompatImageButton
-{
-  public TransparentImageButton(Context context, int icon, float scrWidth, int butWidth)
-      {
-      super(context);
-
-      final int padding = (int)(scrWidth*RubikActivity.PADDING);
-      final int margin  = (int)(scrWidth*RubikActivity.MARGIN);
-
-      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(butWidth,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-
-      params.topMargin    = margin;
-      params.bottomMargin = margin;
-      params.leftMargin   = margin;
-      params.rightMargin  = margin;
-
-      setLayoutParams(params);
-      setPadding(padding,0,padding,0);
-      setImageResource(icon);
-
-      TypedValue outValue = new TypedValue();
-      context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
-      setBackgroundResource(outValue.resourceId);
-      }
-}
diff --git a/src/main/java/org/distorted/tutorials/TutorialActivity.java b/src/main/java/org/distorted/tutorials/TutorialActivity.java
index f4011f95..87f94f40 100644
--- a/src/main/java/org/distorted/tutorials/TutorialActivity.java
+++ b/src/main/java/org/distorted/tutorials/TutorialActivity.java
@@ -37,7 +37,7 @@ import org.distorted.library.main.DistortedLibrary;
 import org.distorted.main.R;
 import org.distorted.objects.ObjectList;
 import org.distorted.objects.TwistyObject;
-import org.distorted.states.StateList;
+import org.distorted.screens.ScreenList;
 
 import static org.distorted.main.RubikRenderer.BRIGHTNESS;
 
@@ -339,9 +339,9 @@ public class TutorialActivity extends AppCompatActivity
 
     public boolean isLocked()
       {
-      StateList state = StateList.getCurrentState();
+      ScreenList state = ScreenList.getCurrentState();
 
-      if( state== StateList.PLAY || state== StateList.READ || state== StateList.SOLV )
+      if( state== ScreenList.PLAY || state== ScreenList.READ || state== ScreenList.SOLV )
         {
         return mIsLocked;
         }
diff --git a/src/main/java/org/distorted/tutorials/TutorialState.java b/src/main/java/org/distorted/tutorials/TutorialState.java
index 2ff848ce..4b9c063d 100644
--- a/src/main/java/org/distorted/tutorials/TutorialState.java
+++ b/src/main/java/org/distorted/tutorials/TutorialState.java
@@ -28,9 +28,9 @@ import org.distorted.main.RubikActivity;
 import org.distorted.main.RubikPreRender;
 import org.distorted.objects.ObjectList;
 import org.distorted.objects.TwistyObject;
-import org.distorted.states.RubikStatePlay;
-import org.distorted.states.StateList;
-import org.distorted.states.TransparentImageButton;
+import org.distorted.screens.RubikScreenPlay;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.TransparentImageButton;
 
 import java.util.ArrayList;
 
@@ -176,7 +176,7 @@ public class TutorialState implements RubikPreRender.ActionFinishedListener
       @Override
       public void onClick(View v)
         {
-        RubikStatePlay play = (RubikStatePlay) StateList.PLAY.getStateClass();
+        RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getStateClass();
         int size = play.getSize();
         int object= play.getObject();
         int sizeIndex = ObjectList.getSizeIndex(object,size);
