commit 15ed3429f1b950cdf93b4979498f0e642c51791a
Author: leszek <leszek@koltunski.pl>
Date:   Tue Nov 14 00:40:08 2023 +0100

    Progress with the generic PlayActivity.

diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index 51f9fc78..d25b5097 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -409,7 +409,7 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
     public void switchToPlay(RubikObject object, int scrambles, boolean free)
       {
       Intent intent = new Intent(this, PlayActivity.class);
-      intent.putExtra("name", object.getLowerName());
+      intent.putExtra("name", object.getUpperName());
       intent.putExtra("scrambles", scrambles);
       intent.putExtra("local", object.isLocal() );
       intent.putExtra("ordinal", object.getObjectOrdinal() );
diff --git a/src/main/java/org/distorted/playui/PlayActivity.java b/src/main/java/org/distorted/playui/PlayActivity.java
index 963c4bb0..73d63adc 100644
--- a/src/main/java/org/distorted/playui/PlayActivity.java
+++ b/src/main/java/org/distorted/playui/PlayActivity.java
@@ -40,14 +40,17 @@ import org.distorted.os.OSInterface;
 
 public class PlayActivity extends AppCompatActivity
 {
+    public static final int FLAGS            = MainActivity.FLAGS;
+    public static final float TITLE_TEXT_SIZE= 0.060f;
     private static final int ACTIVITY_NUMBER = 6;
     private static final float RATIO_BAR     = MainActivity.RATIO_BAR;
     private static final float RATIO_INSET   = 0.09f;
-    public static final int FLAGS            = MainActivity.FLAGS;
+
+    private static final String KEY_FREE = "movesController_free";
+    private static final String KEY_SOLV = "movesController_solv";
 
     private static int mScreenWidth, mScreenHeight;
     private int mCurrentApiVersion;
-    private PlayScreen mScreen;
     private String mObjectName;
     private int mNumScrambles;
     private int mHeightUpperBar;
@@ -205,9 +208,11 @@ public class PlayActivity extends AppCompatActivity
       PlayView view = findViewById(R.id.playView);
       view.onResume();
 
-      if( mScreen==null ) mScreen = new PlayScreen();
-      mScreen.onAttachedToWindow(this, mObjectName);
-      restorePreferences();
+      ScreenList.switchScreen(this,ScreenList.FREE);
+
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      restorePreferences(preferences);
+      restoreMoves(preferences);
 
       if( mObjectName.length()>0 )
         {
@@ -230,17 +235,58 @@ public class PlayActivity extends AppCompatActivity
     {
     SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
     SharedPreferences.Editor editor = preferences.edit();
-    mScreen.savePreferences(this,editor);
+
+    for( int i=0; i<ScreenList.LENGTH; i++ )
+      {
+      ScreenList.getScreen(i).getScreenClass().savePreferences(editor);
+      }
+
+    ScreenList.savePreferences(editor);
+
+    ScreenList curr = ScreenList.getCurrentScreen();
+
+    if( curr==ScreenList.FREE )
+      {
+      ScreenFree free = (ScreenFree) ScreenList.FREE.getScreenClass();
+      free.saveMovePreferences(KEY_FREE,editor);
+      }
+    if( curr==ScreenList.SOLV )
+      {
+      ScreenSolving solv = (ScreenSolving) ScreenList.SOLV.getScreenClass();
+      solv.saveMovePreferences(KEY_SOLV,editor);
+      }
 
     editor.apply();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void restorePreferences()
+  private void restorePreferences(SharedPreferences preferences)
     {
-    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-    mScreen.restorePreferences(this,preferences);
+    for( int i=0; i<ScreenList.LENGTH; i++)
+      {
+      ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
+      }
+
+    ScreenList.restorePreferences(preferences);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void restoreMoves(SharedPreferences preferences)
+    {
+    ScreenList curr = ScreenList.getCurrentScreen();
+
+    if( curr==ScreenList.FREE )
+      {
+      ScreenFree free = (ScreenFree) ScreenList.FREE.getScreenClass();
+      free.restoreMovePreferences(this,KEY_FREE,preferences);
+      }
+    if( curr==ScreenList.SOLV )
+      {
+      ScreenSolving solv = (ScreenSolving) ScreenList.SOLV.getScreenClass();
+      solv.restoreMovePreferences(this,KEY_SOLV,preferences);
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -255,8 +301,6 @@ public class PlayActivity extends AppCompatActivity
 
     private void changeIfDifferent(String upperName, boolean local, int ordinal, ObjectControl control)
       {
-      android.util.Log.e("D", "changing to "+upperName+" local: "+local+" ordinal: "+ordinal);
-
       if( local )
         {
         RubikFiles files = RubikFiles.getInstance();
@@ -309,11 +353,4 @@ public class PlayActivity extends AppCompatActivity
       PlayView view = findViewById(R.id.playView);
       return view.getObjectControl();
       }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public PlayScreen getScreen()
-      {
-      return mScreen;
-      }
 }
diff --git a/src/main/java/org/distorted/playui/PlayLibInterface.java b/src/main/java/org/distorted/playui/PlayLibInterface.java
index 8f63aeab..2576349a 100644
--- a/src/main/java/org/distorted/playui/PlayLibInterface.java
+++ b/src/main/java/org/distorted/playui/PlayLibInterface.java
@@ -51,8 +51,10 @@ public class PlayLibInterface implements ObjectLibInterface
 
     if( act!=null )
       {
-      PlayScreen play = act.getScreen();
-      play.addMove(act,axis,row,angle);
+      ScreenList screen = ScreenList.getCurrentScreen();
+
+      if( screen==ScreenList.FREE ) ((ScreenFree   )screen.getScreenClass()).addMove(act,axis,row,angle);
+      if( screen==ScreenList.SOLV ) ((ScreenSolving)screen.getScreenClass()).addMove(act,axis,row,angle);
       }
     }
 
@@ -64,8 +66,10 @@ public class PlayLibInterface implements ObjectLibInterface
 
     if( act!=null )
       {
-      PlayScreen play = act.getScreen();
-      play.reddenLock(act);
+      ScreenList screen = ScreenList.getCurrentScreen();
+
+      if( screen==ScreenList.FREE ) ((ScreenFree   )screen.getScreenClass()).reddenLock(act);
+      if( screen==ScreenList.SOLV ) ((ScreenSolving)screen.getScreenClass()).reddenLock(act);
       }
     }
 
diff --git a/src/main/java/org/distorted/playui/PlayScreen.java b/src/main/java/org/distorted/playui/PlayScreen.java
deleted file mode 100644
index 2eb7b379..00000000
--- a/src/main/java/org/distorted/playui/PlayScreen.java
+++ /dev/null
@@ -1,190 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2023 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.playui;
-
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.os.OSInterface;
-import org.distorted.helpers.LockController;
-import org.distorted.helpers.MovesController;
-import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.R;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PlayScreen
-{
-  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton;
-  private final LockController mLockController;
-  private final MovesController mMovesController;
-  private String mKey;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public PlayScreen()
-    {
-    mLockController = new LockController();
-    mMovesController= new MovesController();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final PlayActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
-    mBackButton = new TransparentImageButton(act,R.drawable.ui_smallback,params);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        act.finish();
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupSolveButton(final PlayActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-    mSolveButton = new TransparentImageButton(act,R.drawable.ui_cube_solve,params);
-
-    mSolveButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        ObjectControl control = act.getControl();
-        control.solveObject();
-        mMovesController.clearMoves(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupScrambleButton(final PlayActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-    mScrambleButton = new TransparentImageButton(act,R.drawable.ui_cube_scramble,params);
-
-    mScrambleButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        ObjectControl control = act.getControl();
-        int duration = BaseEffect.Type.FAST_SCRAMBLE.getDuration();
-        int numScrambles = act.getNumScrambles();
-        control.fastScrambleObject(duration,numScrambles);
-        mMovesController.clearMoves(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void onAttachedToWindow(final PlayActivity act, String objectName)
-    {
-    mKey = "moveController_"+objectName;
-
-    int width = act.getScreenWidthInPixels();
-
-    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, 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);
-
-    setupBackButton(act);
-    ObjectControl control = act.getControl();
-    mMovesController.setupButton(act,control);
-    layoutLeft.addView(mMovesController.getButton());
-    mLockController.setupButton(act,control);
-    layoutMid.addView(mLockController.getButton());
-
-    layoutRight.addView(mBackButton);
-
-    LinearLayout layoutLower = act.findViewById(R.id.lowerBar);
-    layoutLower.removeAllViews();
-    layoutLower.addView(layoutLeft);
-    layoutLower.addView(layoutMid);
-    layoutLower.addView(layoutRight);
-
-    setupSolveButton(act);
-    setupScrambleButton(act);
-
-    LinearLayout layoutUpper = act.findViewById(R.id.upperBar);
-
-    LinearLayout layoutLeftU = new LinearLayout(act);
-    layoutLeftU.setLayoutParams(paramsL);
-    LinearLayout layoutMidU  = new LinearLayout(act);
-    layoutMidU.setLayoutParams(paramsM);
-    LinearLayout layoutRightU= new LinearLayout(act);
-    layoutRightU.setLayoutParams(paramsR);
-
-    layoutLeftU.addView(mSolveButton);
-    layoutRightU.addView(mScrambleButton);
-
-    layoutUpper.removeAllViews();
-    layoutUpper.addView(layoutLeftU);
-    layoutUpper.addView(layoutMidU);
-    layoutUpper.addView(layoutRightU);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reddenLock(final PlayActivity act)
-    {
-    ObjectControl control = act.getControl();
-    mLockController.reddenLock(act,control);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void addMove(Activity act, int axis, int row, int angle)
-    {
-    mMovesController.addMove(act,axis,row,angle);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(PlayActivity act, SharedPreferences.Editor editor)
-    {
-    mMovesController.savePreferences(mKey,editor);
-    ObjectControl control = act.getControl();
-    OSInterface os = (OSInterface)control.getOS();
-    os.setEditor(editor);
-    control.savePreferences();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(PlayActivity act, SharedPreferences preferences)
-    {
-    mMovesController.restorePreferences(act,mKey,preferences);
-    ObjectControl control = act.getControl();
-    OSInterface os = (OSInterface)control.getOS();
-    os.setPreferences(preferences);
-    control.restorePreferences();
-    }
-}
diff --git a/src/main/java/org/distorted/playui/ScreenAbstract.java b/src/main/java/org/distorted/playui/ScreenAbstract.java
new file mode 100644
index 00000000..3bd3520f
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenAbstract.java
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import android.content.SharedPreferences;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class ScreenAbstract
+  {
+  abstract void enterScreen(PlayActivity act);
+  abstract void leaveScreen(PlayActivity act);
+  public abstract void savePreferences(SharedPreferences.Editor editor);
+  public abstract void restorePreferences(SharedPreferences preferences);
+  }
diff --git a/src/main/java/org/distorted/playui/ScreenBase.java b/src/main/java/org/distorted/playui/ScreenBase.java
new file mode 100644
index 00000000..7b1568f7
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenBase.java
@@ -0,0 +1,104 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import android.content.SharedPreferences;
+import android.widget.LinearLayout;
+
+import org.distorted.helpers.LockController;
+import org.distorted.helpers.MovesController;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.objectlib.main.ObjectControl;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+abstract class ScreenBase extends ScreenAbstract
+  {
+  private final LockController mLockController;
+  protected MovesController mMovesController;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void createBottomPane(final PlayActivity act, TransparentImageButton butt)
+    {
+    mMovesController.clearMoves(act);
+
+    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);
+
+    ObjectControl control = act.getControl();
+    mMovesController.setupButton(act,control);
+    layoutLeft.addView(mMovesController.getButton());
+    mLockController.setupButton(act,control);
+    layoutMid.addView(mLockController.getButton());
+
+    if( butt !=null ) layoutRight.addView(butt);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setLockState(final PlayActivity act)
+    {
+    boolean locked = act.getControl().retLocked();
+    mLockController.setState(act,locked);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+
+  public ScreenBase()
+    {
+    mLockController = new LockController();
+    mMovesController= new MovesController();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void addMove(PlayActivity act, int axis, int row, int angle)
+    {
+    mMovesController.addMove(act,axis,row,angle);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reddenLock(final PlayActivity act)
+    {
+    ObjectControl control = act.getControl();
+    mLockController.reddenLock(act,control);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void saveMovePreferences(String key,SharedPreferences.Editor editor)
+    {
+    mMovesController.savePreferences(key,editor);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restoreMovePreferences(PlayActivity act, String key, SharedPreferences preferences)
+    {
+    mMovesController.restorePreferences(act,key,preferences);
+    }
+  }
diff --git a/src/main/java/org/distorted/playui/ScreenDone.java b/src/main/java/org/distorted/playui/ScreenDone.java
new file mode 100644
index 00000000..fd61e289
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenDone.java
@@ -0,0 +1,111 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+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.helpers.TransparentImageButton;
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenDone extends ScreenAbstract
+  {
+  private TransparentImageButton mBackButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(PlayActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final PlayActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float titleSize = width*PlayActivity.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);
+
+    layoutRight.addView(mBackButton);
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_back,params);
+
+    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/playui/ScreenFree.java b/src/main/java/org/distorted/playui/ScreenFree.java
new file mode 100644
index 00000000..fbe86d08
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenFree.java
@@ -0,0 +1,140 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import android.content.SharedPreferences;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.objectlib.effects.BaseEffect;
+import org.distorted.objectlib.main.ObjectControl;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenFree extends ScreenBase
+  {
+  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(PlayActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final PlayActivity act)
+    {
+    int width = act.getScreenWidthInPixels();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    // TOP ////////////////////////////
+    setupSolveButton(act);
+    setupScrambleButton(act);
+
+    LinearLayout layoutUpper = act.findViewById(R.id.upperBar);
+
+    LinearLayout layoutLeftU = new LinearLayout(act);
+    layoutLeftU.setLayoutParams(paramsL);
+    LinearLayout layoutMidU  = new LinearLayout(act);
+    layoutMidU.setLayoutParams(paramsM);
+    LinearLayout layoutRightU= new LinearLayout(act);
+    layoutRightU.setLayoutParams(paramsR);
+
+    layoutLeftU.addView(mSolveButton);
+    layoutRightU.addView(mScrambleButton);
+
+    layoutUpper.removeAllViews();
+    layoutUpper.addView(layoutLeftU);
+    layoutUpper.addView(layoutMidU);
+    layoutUpper.addView(layoutRightU);
+
+    // BOT ////////////////////////////
+    setupBackButton(act);
+    createBottomPane(act,mBackButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_smallback,params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.finish();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupSolveButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mSolveButton = new TransparentImageButton(act,R.drawable.ui_cube_solve,params);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        control.solveObject();
+        mMovesController.clearMoves(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupScrambleButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mScrambleButton = new TransparentImageButton(act,R.drawable.ui_cube_scramble,params);
+
+    mScrambleButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        int duration = BaseEffect.Type.FAST_SCRAMBLE.getDuration();
+        int numScrambles = act.getNumScrambles();
+        control.fastScrambleObject(duration,numScrambles);
+        mMovesController.clearMoves(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/playui/ScreenList.java b/src/main/java/org/distorted/playui/ScreenList.java
new file mode 100644
index 00000000..01f975f3
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenList.java
@@ -0,0 +1,152 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import static org.distorted.objectlib.main.ObjectControl.MODE_DRAG;
+import static org.distorted.objectlib.main.ObjectControl.MODE_ROTATE;
+
+import android.content.SharedPreferences;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public enum ScreenList
+  {
+  FREE ( null , MODE_ROTATE , new ScreenFree()       ),
+  SCRA ( null , MODE_ROTATE , new ScreenScrambling() ),
+  SOLV ( null , MODE_ROTATE , new ScreenSolving()    ),
+  DONE ( null , MODE_DRAG   , new ScreenDone()       ),
+  ;
+
+  public static final int LENGTH = values().length;
+  private static final ScreenList[] screens;
+  private final ScreenList mBack;
+  private final int mMode;
+  private final ScreenAbstract mClass;
+
+  private static ScreenList mCurrScreen;
+
+  static
+    {
+    int i = 0;
+    screens = new ScreenList[LENGTH];
+    for(ScreenList state: ScreenList.values()) screens[i++] = state;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getScreen(int ordinal)
+    {
+    return ordinal>=0 && ordinal<LENGTH ?  screens[ordinal] : SCRA;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getScreenFromName(String name)
+    {
+    for(int i=0; i<LENGTH; i++)
+      {
+      if( name.equals(screens[i].name()) )
+        {
+        return screens[i];
+        }
+      }
+
+    return SCRA;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getCurrentScreen()
+    {
+    return mCurrScreen;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getMode()
+    {
+    return mCurrScreen.mMode;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putString("curr_state_name", mCurrScreen.name() );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void restorePreferences(SharedPreferences preferences)
+    {
+    String currScreenName = preferences.getString("curr_state_name", ScreenList.SCRA.name() );
+    mCurrScreen = getScreenFromName(currScreenName);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void goBack(PlayActivity act)
+    {
+    switchScreen(act, mCurrScreen.mBack );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void setScreen(PlayActivity act)
+    {
+    mCurrScreen.enterScreen(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void switchScreen(PlayActivity act, ScreenList next)
+    {
+    if( next!=null )
+      {
+      if( mCurrScreen !=null ) mCurrScreen.leaveScreen(act);
+      next.enterScreen(act);
+      mCurrScreen = next;
+      }
+    else
+      {
+      act.finish();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ScreenList(ScreenList back, int mode, ScreenAbstract clazz)
+    {
+    mBack = back;
+    mMode = mode;
+    mClass= clazz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ScreenAbstract getScreenClass()
+    {
+    return mClass;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void leaveScreen(PlayActivity act)
+    {
+    mClass.leaveScreen(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void enterScreen(PlayActivity act)
+    {
+    mClass.enterScreen(act);
+    }
+  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/playui/ScreenScrambling.java b/src/main/java/org/distorted/playui/ScreenScrambling.java
new file mode 100644
index 00000000..eb4d4868
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenScrambling.java
@@ -0,0 +1,85 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenScrambling extends ScreenBase
+  {
+  private TransparentImageButton mBackButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(PlayActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final PlayActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float titleSize = width*PlayActivity.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);
+    createBottomPane(act,mBackButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_back,params);
+
+    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/playui/ScreenSolving.java b/src/main/java/org/distorted/playui/ScreenSolving.java
new file mode 100644
index 00000000..27596bcd
--- /dev/null
+++ b/src/main/java/org/distorted/playui/ScreenSolving.java
@@ -0,0 +1,202 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.playui;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.dialogs.RubikDialogAbandon;
+import org.distorted.external.RubikScores;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.objects.RubikObjectList;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenSolving extends ScreenBase
+  {
+  private static final int MOVES_THRESHHOLD = 10;
+
+  private TextView mTime;
+  private Timer mTimer;
+  private long mStartTime;
+  private boolean mRunning;
+  private final RubikScores mScores;
+  private long mElapsed;
+  private TransparentImageButton mBackButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ScreenSolving()
+    {
+    mScores = RubikScores.getInstance();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(PlayActivity act)
+    {
+    stopCounting();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final PlayActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    float titleSize  = width*PlayActivity.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);
+    createBottomPane(act,mBackButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_back,params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        if( mMovesController.getNumMoves() > MOVES_THRESHHOLD )
+          {
+          RubikDialogAbandon abaDiag = new RubikDialogAbandon();
+          abaDiag.show(act.getSupportFragmentManager(), null);
+          }
+        else
+          {
+          ScreenList.goBack(act);
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    stopCounting();
+
+    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 PlayActivity 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 int stopTimerAndGetRecord()
+    {
+    if( mRunning )
+      {
+      stopCounting();
+      mElapsed = System.currentTimeMillis()-mStartTime;
+      return (int)mElapsed;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int setRecord()
+    {
+    /*
+    ScreenPlay play = (ScreenPlay) ScreenList.PLAY.getScreenClass();
+    int level = play.getLevel()-1;
+    */
+
+    int level = 0;
+    android.util.Log.e("D", "TODO: implement level!!");
+
+    int object = RubikObjectList.getCurrObject();
+    return mScores.setRecord(object, level, (int)mElapsed);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void resetElapsed()
+    {
+    mElapsed = 0;
+    }
+  }
