commit 85038b849c9e3b873fca398fcb35a31c0016bda7
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Jun 29 15:27:36 2022 +0200

    New UI

diff --git a/build.gradle b/build.gradle
index 0d105645..929d781c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,8 +15,8 @@ android {
         applicationId "org.distorted.magic"
         minSdkVersion 21
         targetSdkVersion 31
-        versionCode 56
-        versionName "1.10.5"
+        versionCode 57
+        versionName "2.0.0"
     }
 
     buildTypes {
@@ -38,10 +38,10 @@ dependencies {
     api project(':distorted-flags')
 
     implementation 'com.google.firebase:firebase-analytics:21.0.0'
-    implementation 'com.google.firebase:firebase-crashlytics:18.2.10'
+    implementation 'com.google.firebase:firebase-crashlytics:18.2.11'
     implementation 'com.google.android.play:core:1.10.3'
-    implementation 'androidx.appcompat:appcompat:1.4.1'
-    implementation 'com.google.android.material:material:1.6.0'
+    implementation 'androidx.appcompat:appcompat:1.4.2'
+    implementation 'com.google.android.material:material:1.6.1'
     implementation project(path: ':distorted-puzzle-jsons')
     implementation project(path: ':distorted-puzzle-dmesh')
 }
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 5ab9193d..f4ad764a 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -56,9 +56,9 @@ import org.distorted.external.RubikScores;
 import org.distorted.external.RubikNetwork;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
+import org.distorted.screens.RubikScreenFreePlay;
 import org.distorted.screens.RubikScreenSolving;
 import org.distorted.screens.ScreenList;
-import org.distorted.screens.RubikScreenPlay;
 import org.distorted.tutorials.TutorialActivity;
 
 import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
@@ -126,12 +126,10 @@ public class RubikActivity extends AppCompatActivity
       mIsChinese = localeIsChinese();
 
       DisplayMetrics displaymetrics = new DisplayMetrics();
-      getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
-      mScreenWidth =displaymetrics.widthPixels;
-      mScreenHeight=displaymetrics.heightPixels;
-      mScreenHeight = (int)(1.07f*mScreenHeight); // add 7% for the upper bar
-                                                  // which is not yet hidden.
-                                                  // TODO: figure this out exactly.
+      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+      mScreenWidth  = (int)(displaymetrics.widthPixels);
+      mScreenHeight = (int)(displaymetrics.heightPixels);
+
       hideNavigationBar();
       cutoutHack();
       computeBarHeights();
@@ -142,8 +140,7 @@ public class RubikActivity extends AppCompatActivity
 
     private void computeBarHeights()
       {
-      float height = mScreenHeight;
-      int barHeight = (int)(height*RATIO_BAR);
+      int barHeight = (int)(mScreenHeight*RATIO_BAR);
       mHeightLowerBar = barHeight;
       mHeightUpperBar = barHeight;
 
@@ -242,7 +239,7 @@ public class RubikActivity extends AppCompatActivity
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-    
+
     @Override
     protected void onResume() 
       {
@@ -325,10 +322,10 @@ public class RubikActivity extends AppCompatActivity
 
       ScreenList curr = ScreenList.getCurrentScreen();
 
-      if( curr==ScreenList.PLAY )
+      if( curr==ScreenList.FREE )
         {
-        RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
-        play.saveMovePreferences(KEY_PLAY,editor);
+        RubikScreenFreePlay free = (RubikScreenFreePlay)ScreenList.FREE.getScreenClass();
+        free.saveMovePreferences(KEY_PLAY,editor);
         }
       if( curr==ScreenList.SOLV )
         {
@@ -393,10 +390,10 @@ public class RubikActivity extends AppCompatActivity
       SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
       ScreenList curr = ScreenList.getCurrentScreen();
 
-      if( curr==ScreenList.PLAY )
+      if( curr==ScreenList.FREE )
         {
-        RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
-        play.restoreMovePreferences(this,KEY_PLAY,preferences);
+        RubikScreenFreePlay free = (RubikScreenFreePlay)ScreenList.FREE.getScreenClass();
+        free.restoreMovePreferences(this,KEY_PLAY,preferences);
         }
       if( curr==ScreenList.SOLV )
         {
@@ -421,14 +418,6 @@ public class RubikActivity extends AppCompatActivity
       errDiag.show(getSupportFragmentManager(), null);
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setScreenSize(int width, int height)
-      {
-      mScreenWidth = width;
-      mScreenHeight= height;
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -618,9 +607,18 @@ public class RubikActivity extends AppCompatActivity
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       ObjectControl control = view.getObjectControl();
       control.blockEverything(place);
+      ScreenList curr = ScreenList.getCurrentScreen();
 
-      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-      play.setLockState(this);
+      if( curr==ScreenList.FREE )
+        {
+        RubikScreenFreePlay free = (RubikScreenFreePlay)ScreenList.FREE.getScreenClass();
+        free.setLockState(this);
+        }
+      if( curr==ScreenList.SOLV )
+        {
+        RubikScreenSolving solv = (RubikScreenSolving)ScreenList.SOLV.getScreenClass();
+        solv.setLockState(this);
+        }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -630,9 +628,18 @@ public class RubikActivity extends AppCompatActivity
       RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
       ObjectControl control = view.getObjectControl();
       control.unblockEverything();
+      ScreenList curr = ScreenList.getCurrentScreen();
 
-      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-      play.setLockState(this);
+      if( curr==ScreenList.FREE )
+        {
+        RubikScreenFreePlay free = (RubikScreenFreePlay)ScreenList.FREE.getScreenClass();
+        free.setLockState(this);
+        }
+      if( curr==ScreenList.SOLV )
+        {
+        RubikScreenSolving solv = (RubikScreenSolving)ScreenList.SOLV.getScreenClass();
+        solv.setLockState(this);
+        }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -662,6 +669,24 @@ public class RubikActivity extends AppCompatActivity
       startActivity(intent);
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchRenderingOff()
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      RubikRenderer renderer = view.getRenderer();
+      renderer.switchRendering(false);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchRenderingOn()
+      {
+      RubikSurfaceView view  = findViewById(R.id.rubikSurfaceView);
+      RubikRenderer renderer = view.getRenderer();
+      renderer.switchRendering(true);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void reloadObject(String shortName)
diff --git a/src/main/java/org/distorted/main/RubikObjectLibInterface.java b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
index 3ca4eb1d..111de7af 100644
--- a/src/main/java/org/distorted/main/RubikObjectLibInterface.java
+++ b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
@@ -45,6 +45,7 @@ import org.distorted.dialogs.RubikDialogSolved;
 import org.distorted.external.RubikScores;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
+import org.distorted.screens.RubikScreenFreePlay;
 import org.distorted.screens.RubikScreenPlay;
 import org.distorted.screens.RubikScreenReady;
 import org.distorted.screens.RubikScreenSolver;
@@ -254,15 +255,17 @@ public class RubikObjectLibInterface implements ObjectLibInterface
 
   public void onFinishRotation(int axis, int row, int angle)
     {
-    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
+    ScreenList curr = ScreenList.getCurrentScreen();
+
+    if( curr==ScreenList.SOLV )
       {
       RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
       solv.addMove(mAct.get(), axis, row, angle);
       }
-    if( ScreenList.getCurrentScreen()== ScreenList.PLAY )
+    if( curr==ScreenList.FREE )
       {
-      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-      play.addMove(mAct.get(), axis, row, angle);
+      RubikScreenFreePlay free = (RubikScreenFreePlay) ScreenList.FREE.getScreenClass();
+      free.addMove(mAct.get(), axis, row, angle);
       }
     }
 
@@ -293,10 +296,10 @@ public class RubikObjectLibInterface implements ObjectLibInterface
     {
     ScreenList curr = ScreenList.getCurrentScreen();
 
-    if( curr==ScreenList.PLAY )
+    if( curr==ScreenList.FREE )
       {
-      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-      play.reddenLock(mAct.get());
+      RubikScreenFreePlay free = (RubikScreenFreePlay) ScreenList.FREE.getScreenClass();
+      free.reddenLock(mAct.get());
       }
     else if( curr==ScreenList.READ )
       {
diff --git a/src/main/java/org/distorted/main/RubikRenderer.java b/src/main/java/org/distorted/main/RubikRenderer.java
index 9216ab5c..e0570596 100644
--- a/src/main/java/org/distorted/main/RubikRenderer.java
+++ b/src/main/java/org/distorted/main/RubikRenderer.java
@@ -45,11 +45,12 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
    public static final float BRIGHTNESS = 0.30f;
 
    private final RubikSurfaceView mView;
-   private final DistortedScreen mScreen;
+   private final DistortedScreen mScreen, mEmptyScreen;
    private final ObjectControl mControl;
    private final Fps mFPS;
    private boolean mErrorShown;
    private boolean mDebugSent;
+   private boolean mRenderingOn;
 
    private static class Fps
      {
@@ -93,12 +94,16 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
 
    RubikRenderer(RubikSurfaceView v)
      {
+     mRenderingOn= true;
      mErrorShown = false;
      mView = v;
      mControl = v.getObjectControl();
      mFPS = new Fps();
+
      mScreen = new DistortedScreen();
      mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     mEmptyScreen = new DistortedScreen();
+     mEmptyScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -109,9 +114,17 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
    public void onDrawFrame(GL10 glUnused)
      {
      long time = System.currentTimeMillis();
-     mFPS.onRender(time);
-     mControl.preRender();
-     mScreen.render(time);
+
+     if( mRenderingOn )
+       {
+       mFPS.onRender(time);
+       mControl.preRender();
+       mScreen.render(time);
+       }
+     else
+       {
+       mEmptyScreen.render(time);
+       }
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -120,6 +133,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
    public void onSurfaceChanged(GL10 glUnused, int width, int height)
       {
       mScreen.resize(width,height);
+      mEmptyScreen.resize(width,height);
       mView.setScreenSize(width,height);
       }
 
@@ -188,6 +202,13 @@ public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.E
        }
      }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void switchRendering(boolean on)
+     {
+     mRenderingOn = on;
+     }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    float getFPS()
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index d305a71a..7d5541fe 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -59,16 +59,6 @@ public class RubikSurfaceView extends GLSurfaceView
         TwistyObjectNode objectNode = mObjectController.getNode();
         mRenderer.getScreen().attach(objectNode);
         }
-
-      try
-        {
-        RubikActivity act = (RubikActivity)getContext();
-        act.setScreenSize(width,height);
-        }
-      catch( Exception ex )
-        {
-        android.util.Log.e("D", "Failed to set screen size!");
-        }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/screens/RubikScreenDetails.java b/src/main/java/org/distorted/screens/RubikScreenDetails.java
new file mode 100644
index 00000000..87050771
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenDetails.java
@@ -0,0 +1,759 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
+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 android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.distorted.dialogs.RubikDialogAbout;
+import org.distorted.dialogs.RubikDialogPattern;
+import org.distorted.dialogs.RubikDialogScores;
+import org.distorted.dialogs.RubikDialogTutorial;
+import org.distorted.dialogs.RubikDialogUpdates;
+import org.distorted.external.RubikNetwork;
+import org.distorted.external.RubikScores;
+import org.distorted.external.RubikUpdates;
+import org.distorted.helpers.TransparentButton;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+import java.lang.ref.WeakReference;
+
+import static android.view.View.inflate;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenDetails extends RubikScreenBase implements RubikNetwork.Updatee
+  {
+  public static final int NUM_COLUMNS  = 5;
+  public static final int LEVELS_SHOWN = 10;
+
+  private static final int[] BUTTON_LABELS = { R.string.scores,
+                                               R.string.patterns,
+                                               R.string.solver,
+                                               R.string.tutorials,
+                                               R.string.bandaged,
+                                               R.string.about };
+  private static final int NUM_BUTTONS = BUTTON_LABELS.length;
+
+  private static final float LAST_BUTTON = 1.5f;
+  private static final int[] mLocation = new int[2];
+
+  private TransparentImageButton mObjButton, mMenuButton, mSolveButton, mScrambleButton;
+  private TransparentButton mPlayButton;
+  private PopupWindow mObjectPopup, mMenuPopup, mPlayPopup;
+  private LinearLayout mPlayLayout;
+  private TextView mBubbleUpdates;
+  private int mObjectSize, mMenuLayoutWidth, mMenuLayoutHeight, mPlayLayoutWidth;
+  private int mLevelValue;
+  private float mButtonSize, mMenuItemSize, mMenuTextSize;
+  private int mColCount, mRowCount, mMaxRowCount;
+  private int mUpperBarHeight;
+  private boolean mShouldReactToEndOfScrambling;
+  private int mBottomHeight;
+  private float mScreenWidth;
+  private WeakReference<RubikActivity> mWeakAct;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final RubikActivity act)
+    {
+    mWeakAct = new WeakReference<>(act);
+    int numObjects = RubikObjectList.getNumObjects();
+    mScreenWidth = act.getScreenWidthInPixels();
+    mUpperBarHeight = act.getHeightUpperBar();
+
+    mMenuTextSize = mScreenWidth*RubikActivity.MENU_MED_TEXT_SIZE;
+    mButtonSize   = mScreenWidth*RubikActivity.BUTTON_TEXT_SIZE;
+    mMenuItemSize = mScreenWidth*RubikActivity.MENU_ITEM_SIZE;
+
+    mRowCount = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
+    mColCount = NUM_COLUMNS;
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    setupObjectButton(act,mScreenWidth);
+    layoutTop.addView(mObjButton);
+
+    setupMenuButton(act,mScreenWidth);
+    layoutTop.addView(mMenuButton);
+
+    setupPlayButton(act,mScreenWidth);
+    layoutTop.addView(mPlayButton);
+
+    setupSolveButton(act);
+    setupScrambleButton(act);
+    createBottomPane(act,mSolveButton,mScrambleButton);
+    }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupObjectButton(final RubikActivity act, final float width)
+    {
+    final int margin  = (int)(width*RubikActivity.SMALL_MARGIN);
+    final int lMargin = (int)(width*RubikActivity.LARGE_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);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mObjButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mObjButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View view)
+        {
+        if( mObjectPopup==null )
+          {
+          float width = act.getScreenWidthInPixels();
+          float height= act.getScreenHeightInPixels();
+          setupObjectWindow(act,width,height);
+          }
+
+        if( act.getControl().isUINotBlocked())
+          {
+          int rowCount = Math.min(mMaxRowCount,mRowCount);
+          View popupView = mObjectPopup.getContentView();
+          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
+          displayPopup(act,view,mObjectPopup,mObjectSize*mColCount,mObjectSize*rowCount+mBottomHeight+2*lMargin+5*margin,margin,margin);
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPlayButton(final RubikActivity act, final float width)
+    {
+    final int margin = (int)(width*RubikActivity.SMALL_MARGIN);
+
+    mPlayButton = new TransparentButton(act, R.string.play, mButtonSize);
+
+    mPlayButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View view)
+        {
+        if( mPlayPopup==null )
+          {
+          float width = act.getScreenWidthInPixels();
+          setupPlayWindow(act,width);
+          }
+
+        if( act.getControl().isUINotBlocked())
+          {
+          adjustSolvedIcons();
+          float height= act.getScreenHeightInPixels();
+          final int maxHeight= (int)(0.9f*(height-mUpperBarHeight) );
+          View popupView = mPlayPopup.getContentView();
+          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
+          final int object  = RubikObjectList.getCurrObject();
+          final int dbLevel = RubikObjectList.getDBLevel(object);
+          final int levelsShown = Math.min(dbLevel,LEVELS_SHOWN);
+          final int popupHeight = (int)(levelsShown*(mMenuItemSize+margin)+3*margin+mMenuItemSize*(LAST_BUTTON-1.0f));
+          final int realHeight = Math.min(popupHeight,maxHeight);
+          displayPopup(act,view,mPlayPopup,mPlayLayoutWidth,realHeight,margin,margin);
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupMenuButton(final RubikActivity act, final float width)
+    {
+    final int margin = (int)(width*RubikActivity.SMALL_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);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mMenuButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mMenuButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View view)
+        {
+        if( mMenuPopup==null )
+          {
+          float width = act.getScreenWidthInPixels();
+          setupMenuWindow(act,width);
+          }
+
+        if( act.getControl().isUINotBlocked())
+          {
+          View popupView = mMenuPopup.getContentView();
+          popupView.setSystemUiVisibility(RubikActivity.FLAGS);
+          displayPopup(act,view,mMenuPopup,mMenuLayoutWidth,mMenuLayoutHeight,(int)(-width/12),margin);
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupObjectWindow(final RubikActivity act, final float width, final float height)
+    {
+    int cubeWidth = (int)(width/9);
+    int margin = (int)(width*RubikActivity.LARGE_MARGIN);
+    mObjectSize = (int)(cubeWidth + 2*margin + 0.5f);
+    mMaxRowCount = (int)((height-1.8f*mUpperBarHeight)/mObjectSize);
+
+    LinearLayout view = (LinearLayout)inflate( act, R.layout.popup_object, null);
+    GridLayout objectGrid = view.findViewById(R.id.objectGrid);
+
+    GridLayout.Spec[] rowSpecs = new GridLayout.Spec[mRowCount];
+    GridLayout.Spec[] colSpecs = new GridLayout.Spec[mColCount];
+
+    objectGrid.setColumnCount(mColCount);
+    objectGrid.setRowCount(mRowCount);
+
+    RelativeLayout bottomLayout = view.findViewById(R.id.bottomLayout);
+    setupBottomLayout(act,bottomLayout);
+
+    mObjectPopup = new PopupWindow(act);
+    mObjectPopup.setFocusable(true);
+    mObjectPopup.setContentView(view);
+
+    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);
+      }
+
+    int numObjects = RubikObjectList.getNumObjects();
+
+    for(int object=0; object<numObjects; object++)
+      {
+      final int ordinal = object;
+      final RubikObject rObject = RubikObjectList.getObject(object);
+      int row = object/NUM_COLUMNS;
+      ImageButton button = new ImageButton(act);
+      if( rObject!=null ) rObject.setIconTo(act,button);
+
+      button.setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+          if( act.getControl().isUINotBlocked() && ScreenList.getCurrentScreen()== ScreenList.PLAY )
+            {
+            RubikObjectList.setCurrObject(act,ordinal);
+            act.changeObject(ordinal,true);
+            if( mPlayLayout!=null ) adjustLevels(act);
+            mMovesController.clearMoves(act);
+            }
+
+          if( mObjectPopup!=null ) 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;
+
+      params.width = cubeWidth;
+      params.height= cubeWidth;
+
+      nextInRow[row]++;
+
+      objectGrid.addView(button, params);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBottomLayout(final RubikActivity act, final RelativeLayout layout)
+    {
+    int iconT = RubikActivity.getDrawable(R.drawable.ui_small_tutorial,R.drawable.ui_medium_tutorial, R.drawable.ui_big_tutorial, R.drawable.ui_huge_tutorial);
+    int iconD = RubikActivity.getDrawable(R.drawable.ui_small_download,R.drawable.ui_medium_download, R.drawable.ui_big_download, R.drawable.ui_huge_download);
+    int iconI = RubikActivity.getDrawable(R.drawable.ui_small_info,R.drawable.ui_medium_info, R.drawable.ui_big_info, R.drawable.ui_huge_info);
+
+    ImageButton buttonTut = layout.findViewById(R.id.buttonTut);
+    ImageButton buttonDow = layout.findViewById(R.id.buttonDow);
+    ImageButton buttonInf = layout.findViewById(R.id.buttonInf);
+
+    buttonTut.setImageResource(iconT);
+    buttonDow.setImageResource(iconD);
+    buttonInf.setImageResource(iconI);
+
+    Resources res = act.getResources();
+    BitmapDrawable bd = (BitmapDrawable)res.getDrawable(iconI);
+    mBottomHeight = bd.getIntrinsicHeight();
+
+    TypedValue outValue = new TypedValue();
+    act.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
+    buttonTut.setBackgroundResource(outValue.resourceId);
+    buttonDow.setBackgroundResource(outValue.resourceId);
+    buttonInf.setBackgroundResource(outValue.resourceId);
+
+    buttonTut.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        if( mObjectPopup!=null ) mObjectPopup.dismiss();
+        RubikDialogTutorial tDiag = new RubikDialogTutorial();
+        tDiag.show( act.getSupportFragmentManager(), RubikDialogTutorial.getDialogTag() );
+        }
+      });
+
+    buttonDow.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        if( mObjectPopup!=null ) mObjectPopup.dismiss();
+        RubikDialogUpdates uDiag = new RubikDialogUpdates();
+        uDiag.show( act.getSupportFragmentManager(), RubikDialogUpdates.getDialogTag() );
+        }
+      });
+
+    buttonInf.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        if( mObjectPopup!=null ) mObjectPopup.dismiss();
+        int currObject = RubikObjectList.getCurrObject();
+        act.switchConfig(currObject);
+        }
+      });
+
+    mBubbleUpdates = layout.findViewById(R.id.bubbleUpdates);
+    mBubbleUpdates.setVisibility(View.INVISIBLE);
+
+    RubikNetwork network = RubikNetwork.getInstance();
+    network.signUpForUpdates(this);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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.SMALL_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 MenuAction(RubikActivity act, int button)
+    {
+    switch(button)
+      {
+      case 0: Bundle sBundle = new Bundle();
+              int currObject = RubikObjectList.getCurrObject();
+              sBundle.putInt("tab", currObject );
+              sBundle.putBoolean("submitting", false);
+              RubikDialogScores scores = new RubikDialogScores();
+              scores.setArguments(sBundle);
+              scores.show(act.getSupportFragmentManager(), null);
+              break;
+      case 1: RubikDialogPattern pDiag = new RubikDialogPattern();
+              pDiag.show( act.getSupportFragmentManager(), RubikDialogPattern.getDialogTag() );
+              break;
+      case 2: ScreenList.switchScreen(act, ScreenList.SVER);
+              break;
+      case 3: RubikDialogTutorial tDiag = new RubikDialogTutorial();
+              tDiag.show( act.getSupportFragmentManager(), RubikDialogTutorial.getDialogTag() );
+              break;
+      case 4: act.switchToBandagedCreator();
+              break;
+      case 5: RubikDialogAbout aDiag = new RubikDialogAbout();
+              aDiag.show(act.getSupportFragmentManager(), null);
+              break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupSolveButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_solve_new,R.drawable.ui_medium_cube_solve_new, R.drawable.ui_big_cube_solve_new, R.drawable.ui_huge_cube_solve_new);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mSolveButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_END,params);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.getControl().solveObject();
+        mMovesController.clearMoves(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupScrambleButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_scramble_new,R.drawable.ui_medium_cube_scramble_new, R.drawable.ui_big_cube_scramble_new, R.drawable.ui_huge_cube_scramble_new);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mScrambleButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_START, params);
+
+    mScrambleButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        int currObject = RubikObjectList.getCurrObject();
+        RubikObject object = RubikObjectList.getObject(currObject);
+        int numScrambles = object==null ? 0 : object.getNumScramble();
+        mShouldReactToEndOfScrambling = false;
+        act.getControl().scrambleObject(numScrambles);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// This is necessary! Otherwise the ObjectPopup will not be re-created next time and we will still
+// hold a reference to the old instance of the RubikActivity class (because setupObjectWindow is not
+// going to be called)
+// An reference to the old instance of RubikActivity will cause all sorts of strange issues.
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putInt("play_LevelValue", mLevelValue );
+
+    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)
+    {
+    mLevelValue = preferences.getInt("play_LevelValue", 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setCurrObject(RubikActivity act)
+    {
+    if( mPlayLayout!=null ) adjustLevels(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// work around lame bugs in Android's version <= 10 pop-up and split-screen modes
+
+  private void displayPopup(RubikActivity act, View view, PopupWindow window, int w, int h, int xoff, int yoff)
+    {
+    View topLayout = act.findViewById(R.id.relativeLayout);
+    boolean isFullScreen;
+
+    if( topLayout!=null )
+      {
+      topLayout.getLocationOnScreen(mLocation);
+      isFullScreen = (mLocation[1]==0);
+      }
+    else
+      {
+      isFullScreen = true;
+      }
+
+    try
+      {
+      // if on Android 11 or we are fullscreen
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || isFullScreen )
+        {
+        window.showAsDropDown(view, xoff, yoff, Gravity.CENTER);
+        window.update(view, w, h);
+        }
+      else  // Android 10 or below in pop-up mode or split-screen mode
+        {
+        view.getLocationOnScreen(mLocation);
+        int width  = view.getWidth();
+        int height = view.getHeight();
+        int x = mLocation[0]+(width-w)/2;
+        int y = mLocation[1]+height+yoff;
+
+        window.showAsDropDown(view);
+        window.update(x,y,w,h);
+        }
+      }
+    catch( IllegalArgumentException iae )
+      {
+      // ignore, this means window is 'not attached to window manager' -
+      // which most probably is because we are already exiting the app.
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void adjustSolvedIcons()
+    {
+    if( mPlayLayout!=null )
+      {
+      int currObject = RubikObjectList.getCurrObject();
+      int dbLevel = RubikObjectList.getDBLevel(currObject);
+      int numLevel= Math.min(dbLevel, LEVELS_SHOWN);
+      RubikScores scores = RubikScores.getInstance();
+
+      for(int i=0; i<numLevel; i++)
+        {
+        int level = i<numLevel-1 ? i+1 : dbLevel;
+        Button button = (Button)mPlayLayout.getChildAt(i);
+        int icon = scores.isSolved(currObject, level-1) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
+        button.setCompoundDrawablesWithIntrinsicBounds(icon,0,0,0);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void adjustLevels(final RubikActivity act)
+    {
+    int currObject = RubikObjectList.getCurrObject();
+    int dbLevel = RubikObjectList.getDBLevel(currObject);
+    RubikObject object = RubikObjectList.getObject(currObject);
+    int numScrambles = object==null ? 0 : object.getNumScramble();
+    int numLevel = Math.min(dbLevel, 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);
+      }
+
+    if( numLevel>0 )
+      {
+      levels[numLevel-1] = act.getString(R.string.level_full);
+      }
+
+    if( mLevelValue>dbLevel || mLevelValue<1 ||
+       (mLevelValue<dbLevel || mLevelValue>LEVELS_SHOWN ) )
+      {
+      mLevelValue=1;
+      }
+
+    float width  = act.getScreenWidthInPixels();
+    int margin   = (int)(width*RubikActivity.SMALL_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 level     = i<numLevel-1 ? i+1 : dbLevel;
+      final int scrambles = i<numLevel-1 ? i+1 : numScrambles;
+      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(currObject, level-1) ? 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)
+          {
+          ObjectControl control = act.getControl();
+
+          if(control.isUINotBlocked())
+            {
+            if( mPlayPopup!=null ) mPlayPopup.dismiss();
+            mLevelValue = level;
+            mShouldReactToEndOfScrambling = true;
+            control.scrambleObject(scrambles);
+            }
+          }
+        });
+
+      mPlayLayout.addView(button);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getLevel()
+    {
+    return mLevelValue;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void recreatePopup()
+    {
+    mObjectPopup = null;
+
+    int numObjects = RubikObjectList.getNumObjects();
+    mRowCount = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
+    mColCount = NUM_COLUMNS;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean shouldReactToEndOfScrambling()
+    {
+    return mShouldReactToEndOfScrambling;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void receiveUpdate(RubikUpdates updates)
+    {
+    Activity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          int num = updates.getCompletedNumber();
+
+          if( num>0 )
+            {
+            String shownNum = String.valueOf(num);
+            mBubbleUpdates.setText(shownNum);
+            mBubbleUpdates.setVisibility(View.VISIBLE);
+            int height = (int)(0.05f*mScreenWidth);
+            mBubbleUpdates.setTextSize(TypedValue.COMPLEX_UNIT_PX,height);
+            }
+         else
+            {
+            mBubbleUpdates.setVisibility(View.INVISIBLE);
+            }
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void errorUpdate()
+    {
+    android.util.Log.e("D", "Screen: Error receiving update");
+    }
+  }
diff --git a/src/main/java/org/distorted/screens/RubikScreenFreePlay.java b/src/main/java/org/distorted/screens/RubikScreenFreePlay.java
new file mode 100644
index 00000000..26fa1c12
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenFreePlay.java
@@ -0,0 +1,173 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.bandaged.BandagedPlayActivity;
+import org.distorted.dialogs.RubikDialogAbandon;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenFreePlay extends RubikScreenBase
+  {
+  private static final int MOVES_THRESHHOLD = 10;
+  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikScreenFreePlay()
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final RubikActivity 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);
+
+    // BOTTOM //////////////////////////
+    setupBackButton(act);
+    createBottomPane(act,mBackButton,null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupSolveButton(final RubikActivity act)
+    {
+    int icon = BandagedPlayActivity.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);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mSolveButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE,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 RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_scramble,R.drawable.ui_medium_cube_scramble, R.drawable.ui_big_cube_scramble, R.drawable.ui_huge_cube_scramble);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mScrambleButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mScrambleButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        int currObject = RubikObjectList.getCurrObject();
+        RubikObject object = RubikObjectList.getObject(currObject);
+        int numScrambles = object==null ? 0 : object.getNumScramble();
+        //mShouldReactToEndOfScrambling = false;
+        act.getControl().scrambleObject(numScrambles);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_back,R.drawable.ui_medium_back, R.drawable.ui_big_back, R.drawable.ui_huge_back);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, 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)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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
index 8a89b179..781c4631 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -25,6 +25,7 @@ import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Build;
 import android.os.Bundle;
@@ -32,17 +33,21 @@ import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.GridLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 import android.widget.RelativeLayout;
+import android.widget.ScrollView;
 import android.widget.TextView;
 
+import org.distorted.config.ConfigActivity;
 import org.distorted.dialogs.RubikDialogUpdates;
 import org.distorted.external.RubikNetwork;
 import org.distorted.external.RubikUpdates;
+import org.distorted.main.RubikRenderer;
 import org.distorted.objectlib.main.ObjectControl;
 
 import org.distorted.main.R;
@@ -61,52 +66,49 @@ import static android.view.View.inflate;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Updatee
+public class RubikScreenPlay extends RubikScreenAbstract implements RubikNetwork.Updatee
   {
   public static final int NUM_COLUMNS  = 5;
   public static final int LEVELS_SHOWN = 10;
 
-  private static final int[] BUTTON_LABELS = { R.string.scores,
-                                               R.string.patterns,
-                                               R.string.solver,
-                                               R.string.tutorials,
-                                               R.string.bandaged,
-                                               R.string.about };
-  private static final int NUM_BUTTONS = BUTTON_LABELS.length;
-
   private static final float LAST_BUTTON = 1.5f;
   private static final int[] mLocation = new int[2];
 
-  private TransparentImageButton mObjButton, mMenuButton, mSolveButton, mScrambleButton;
-  private TransparentButton mPlayButton;
-  private PopupWindow mObjectPopup, mMenuPopup, mPlayPopup;
+  private ImageButton mAboutButton, mUpdatesButton, mExitButton;
+ // private PopupWindow mObjectPopup, mMenuPopup, mPlayPopup;
   private LinearLayout mPlayLayout;
   private TextView mBubbleUpdates;
+  private int mCurrentBubbleNumber;
   private int mObjectSize, mMenuLayoutWidth, mMenuLayoutHeight, mPlayLayoutWidth;
   private int mLevelValue;
   private float mButtonSize, mMenuItemSize, mMenuTextSize;
   private int mColCount, mRowCount, mMaxRowCount;
-  private int mUpperBarHeight;
+  private int mUpperBarHeight, mLowerBarHeight;
   private boolean mShouldReactToEndOfScrambling;
   private int mBottomHeight;
-  private float mScreenWidth;
+  private float mScreenWidth, mScreenHeight;
   private WeakReference<RubikActivity> mWeakAct;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void leaveScreen(RubikActivity act)
     {
-
+    act.switchRenderingOn();
+    LinearLayout hiddenView = act.findViewById(R.id.hiddenView);
+    hiddenView.removeAllViews();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void enterScreen(final RubikActivity act)
     {
+    act.switchRenderingOff();
     mWeakAct = new WeakReference<>(act);
     int numObjects = RubikObjectList.getNumObjects();
     mScreenWidth = act.getScreenWidthInPixels();
+    mScreenHeight= act.getScreenHeightInPixels();
     mUpperBarHeight = act.getHeightUpperBar();
+    mLowerBarHeight = act.getHeightLowerBar();
 
     mMenuTextSize = mScreenWidth*RubikActivity.MENU_MED_TEXT_SIZE;
     mButtonSize   = mScreenWidth*RubikActivity.BUTTON_TEXT_SIZE;
@@ -115,26 +117,181 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     mRowCount = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
     mColCount = NUM_COLUMNS;
 
+    float titleSize = mScreenWidth*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.app_name);
+    layoutTop.addView(label);
+
+    // BOTTOM /////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+    RelativeLayout relLayout = (RelativeLayout)inflater.inflate(R.layout.play_bottom_bar, null);
+    //relLayout.setBackgroundColor(Color.parseColor("#000000"));
+    layoutBot.addView(relLayout);
+
+    TypedValue outValue = new TypedValue();
+    act.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
+
+    setupAboutButton(act,relLayout,outValue);
+    setupUpdatesButtonAndBubble(act,relLayout,outValue);
+    setupExitButton(act,relLayout,outValue);
+
+    // POPUP //////////////////////////
+    LinearLayout hiddenView = act.findViewById(R.id.hiddenView);
+    hiddenView.removeAllViews();
 
-    setupObjectButton(act,mScreenWidth);
-    layoutTop.addView(mObjButton);
+    ViewGroup.LayoutParams paramsMid = hiddenView.getLayoutParams();
+    paramsMid.height = (int)(mScreenHeight-mUpperBarHeight-mLowerBarHeight);
+    hiddenView.setLayoutParams(paramsMid);
 
-    setupMenuButton(act,mScreenWidth);
-    layoutTop.addView(mMenuButton);
+label.setText("s:"+mScreenHeight+" u:"+mUpperBarHeight+" l:"+mLowerBarHeight);
 
-    setupPlayButton(act,mScreenWidth);
-    layoutTop.addView(mPlayButton);
+    ScrollView scroll = (ScrollView)inflater.inflate(R.layout.popup_object_simple, null);
+    hiddenView.addView(scroll);
 
-    setupSolveButton(act);
-    setupScrambleButton(act);
-    createBottomPane(act,mSolveButton,mScrambleButton);
+    setupScrollingObjects(act,scroll,mScreenWidth,mScreenHeight);
     }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
+  private void setupAboutButton(final RubikActivity act, RelativeLayout relLayout, TypedValue value)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_info,R.drawable.ui_medium_info, R.drawable.ui_big_info, R.drawable.ui_huge_info);
+    mAboutButton = relLayout.findViewById(R.id.buttonAbout);
+    mAboutButton.setImageResource(icon);
+    mAboutButton.setBackgroundResource(value.resourceId);
+
+    mAboutButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikDialogAbout aDiag = new RubikDialogAbout();
+        aDiag.show(act.getSupportFragmentManager(), null);
+        }
+      });
+    }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupUpdatesButtonAndBubble(final RubikActivity act, RelativeLayout relLayout, TypedValue value)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_download,R.drawable.ui_medium_download, R.drawable.ui_big_download, R.drawable.ui_huge_download);
+    mUpdatesButton = relLayout.findViewById(R.id.buttonDown);
+    mUpdatesButton.setImageResource(icon);
+    mUpdatesButton.setBackgroundResource(value.resourceId);
+
+    mUpdatesButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        RubikDialogUpdates uDiag = new RubikDialogUpdates();
+        uDiag.show( act.getSupportFragmentManager(), RubikDialogUpdates.getDialogTag() );
+        }
+      });
+
+    mBubbleUpdates = relLayout.findViewById(R.id.bubbleUpdates);
+    mBubbleUpdates.setVisibility(View.INVISIBLE);
+
+    RubikNetwork network = RubikNetwork.getInstance();
+    network.signUpForUpdates(this);
+    }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupExitButton(final RubikActivity act, RelativeLayout relLayout, TypedValue value)
+    {
+    final int icon = RubikActivity.getDrawable(R.drawable.ui_small_exit,R.drawable.ui_medium_exit, R.drawable.ui_big_exit, R.drawable.ui_huge_exit);
+    mExitButton = relLayout.findViewById(R.id.buttonExit);
+    mExitButton.setImageResource(icon);
+    mExitButton.setBackgroundResource(value.resourceId);
+
+    mExitButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.finish();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupScrollingObjects(final RubikActivity act, final ScrollView view, final float width, final float height)
+    {
+    int margin = (int)(width*RubikActivity.LARGE_MARGIN);
+    int cubeWidth = (int)(width/NUM_COLUMNS) - 2*margin;
+
+    mObjectSize = (int)(cubeWidth + 2*margin + 0.5f);
+    mMaxRowCount = (int)((height-mUpperBarHeight-mLowerBarHeight)/mObjectSize);
+
+    GridLayout objectGrid = view.findViewById(R.id.objectGrid);
+
+    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);
+      }
+
+    int numObjects = RubikObjectList.getNumObjects();
+
+    for(int object=0; object<numObjects; object++)
+      {
+      final int ordinal = object;
+      RubikObject rObject = RubikObjectList.getObject(ordinal);
+      int row = object/NUM_COLUMNS;
+      ImageButton button = new ImageButton(act);
+      if( rObject!=null ) rObject.setIconTo(act,button);
+
+      button.setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+          // TODO
+          android.util.Log.e("D", "object "+ordinal+" clicked");
+          }
+        });
+
+      GridLayout.Spec rowS = rowSpecs[row];
+      GridLayout.Spec colS = colSpecs[nextInRow[row]];
+      GridLayout.LayoutParams params = new GridLayout.LayoutParams(rowS,colS);
+      params.bottomMargin = margin;
+      params.topMargin    = margin;
+      params.leftMargin   = margin;
+      params.rightMargin  = margin;
+
+      params.width = cubeWidth;
+      params.height= cubeWidth;
+
+      nextInRow[row]++;
+
+      objectGrid.addView(button, params);
+      }
+    }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+/*
   private void setupObjectButton(final RubikActivity act, final float width)
     {
     final int margin  = (int)(width*RubikActivity.SMALL_MARGIN);
@@ -251,9 +408,6 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     objectGrid.setColumnCount(mColCount);
     objectGrid.setRowCount(mRowCount);
 
-    RelativeLayout bottomLayout = view.findViewById(R.id.bottomLayout);
-    setupBottomLayout(act,bottomLayout);
-
     mObjectPopup = new PopupWindow(act);
     mObjectPopup.setFocusable(true);
     mObjectPopup.setContentView(view);
@@ -285,15 +439,7 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
         @Override
         public void onClick(View v)
           {
-          if( act.getControl().isUINotBlocked() && ScreenList.getCurrentScreen()== ScreenList.PLAY )
-            {
-            RubikObjectList.setCurrObject(act,ordinal);
-            act.changeObject(ordinal,true);
-            if( mPlayLayout!=null ) adjustLevels(act);
-            mMovesController.clearMoves(act);
-            }
-
-          if( mObjectPopup!=null ) mObjectPopup.dismiss();
+          // TODO
           }
         });
 
@@ -506,34 +652,12 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
         }
       });
     }
-
+*/
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// This is necessary! Otherwise the ObjectPopup will not be re-created next time and we will still
-// hold a reference to the old instance of the RubikActivity class (because setupObjectWindow is not
-// going to be called)
-// An reference to the old instance of RubikActivity will cause all sorts of strange issues.
 
   public void savePreferences(SharedPreferences.Editor editor)
     {
     editor.putInt("play_LevelValue", mLevelValue );
-
-    if( mObjectPopup!=null )
-      {
-      mObjectPopup.dismiss();
-      mObjectPopup = null;
-      }
-
-    if( mMenuPopup!=null )
-      {
-      mMenuPopup.dismiss();
-      mMenuPopup = null;
-      }
-
-    if( mPlayPopup!=null )
-      {
-      mPlayPopup.dismiss();
-      mPlayPopup = null;
-      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -682,7 +806,7 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
           if(control.isUINotBlocked())
             {
-            if( mPlayPopup!=null ) mPlayPopup.dismiss();
+         //   if( mPlayPopup!=null ) mPlayPopup.dismiss();
             mLevelValue = level;
             mShouldReactToEndOfScrambling = true;
             control.scrambleObject(scrambles);
@@ -705,11 +829,28 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
   public void recreatePopup()
     {
-    mObjectPopup = null;
-
     int numObjects = RubikObjectList.getNumObjects();
     mRowCount = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
     mColCount = NUM_COLUMNS;
+
+    RubikActivity act = mWeakAct.get();
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    act.runOnUiThread(new Runnable()
+      {
+      @Override
+      public void run()
+        {
+        LinearLayout hiddenView = act.findViewById(R.id.hiddenView);
+        hiddenView.removeAllViews();
+        ScrollView scroll = (ScrollView)inflater.inflate(R.layout.popup_object_simple, null);
+        hiddenView.addView(scroll);
+        setupScrollingObjects(act,scroll,mScreenWidth,mScreenHeight);
+
+        mCurrentBubbleNumber--;
+        updateNumberOfNewObjects();
+        }
+      });
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -719,6 +860,24 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     return mShouldReactToEndOfScrambling;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void updateNumberOfNewObjects()
+    {
+    if( mCurrentBubbleNumber>0 )
+      {
+      String shownNum = String.valueOf(mCurrentBubbleNumber);
+      mBubbleUpdates.setText(shownNum);
+      mBubbleUpdates.setVisibility(View.VISIBLE);
+      int height = (int)(0.05f*mScreenWidth);
+      mBubbleUpdates.setTextSize(TypedValue.COMPLEX_UNIT_PX,height);
+      }
+    else
+      {
+      mBubbleUpdates.setVisibility(View.INVISIBLE);
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void receiveUpdate(RubikUpdates updates)
@@ -727,25 +886,14 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
     if( act!=null )
       {
+      mCurrentBubbleNumber = updates.getCompletedNumber();
+
       act.runOnUiThread(new Runnable()
         {
         @Override
         public void run()
           {
-          int num = updates.getCompletedNumber();
-
-          if( num>0 )
-            {
-            String shownNum = String.valueOf(num);
-            mBubbleUpdates.setText(shownNum);
-            mBubbleUpdates.setVisibility(View.VISIBLE);
-            int height = (int)(0.05f*mScreenWidth);
-            mBubbleUpdates.setTextSize(TypedValue.COMPLEX_UNIT_PX,height);
-            }
-         else
-            {
-            mBubbleUpdates.setVisibility(View.INVISIBLE);
-            }
+          updateNumberOfNewObjects();
           }
         });
       }
diff --git a/src/main/java/org/distorted/screens/ScreenList.java b/src/main/java/org/distorted/screens/ScreenList.java
index 61b2a7c5..dc02cf8b 100644
--- a/src/main/java/org/distorted/screens/ScreenList.java
+++ b/src/main/java/org/distorted/screens/ScreenList.java
@@ -31,13 +31,15 @@ import static org.distorted.objectlib.main.ObjectControl.*;
 
 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()   ),
+  PLAY ( null , MODE_NOTHING, new RubikScreenPlay()     ),
+  DETA ( PLAY , MODE_NOTHING, new RubikScreenDetails()  ),
+  SOLV ( DETA , MODE_ROTATE , new RubikScreenSolving()  ),
+  PATT ( DETA , MODE_DRAG   , new RubikScreenPattern()  ),
+  SVER ( DETA , MODE_REPLACE, new RubikScreenSolver()   ),
   SOLU ( SVER , MODE_DRAG   , new RubikScreenSolution() ),
-  READ ( PLAY , MODE_ROTATE , new RubikScreenReady()    ),
-  DONE ( PLAY , MODE_DRAG   , new RubikScreenDone()     ),
+  READ ( DETA , MODE_ROTATE , new RubikScreenReady()    ),
+  DONE ( DETA , MODE_DRAG   , new RubikScreenDone()     ),
+  FREE ( DETA , MODE_ROTATE , new RubikScreenFreePlay() ),
   ;
 
   public static final int LENGTH = values().length;
diff --git a/src/main/res/layout/main.xml b/src/main/res/layout/main.xml
index 882c41d7..b5b6daaf 100644
--- a/src/main/res/layout/main.xml
+++ b/src/main/res/layout/main.xml
@@ -31,6 +31,16 @@
         android:background="@android:color/transparent">
     </LinearLayout>
 
+    <LinearLayout
+        android:id="@+id/hiddenView"
+        android:layout_below="@id/upperBar"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
     <LinearLayout
         android:id="@+id/lowerBar"
         android:layout_alignParentBottom="true"
diff --git a/src/main/res/layout/play_bottom_bar.xml b/src/main/res/layout/play_bottom_bar.xml
new file mode 100644
index 00000000..cec3fcd3
--- /dev/null
+++ b/src/main/res/layout/play_bottom_bar.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+       android:id="@+id/bottomLayout"
+       android:layout_width="match_parent"
+       android:layout_height="wrap_content"
+       android:background="@android:color/transparent"
+       android:paddingEnd="10dp"
+       android:paddingStart="10dp">
+
+       <ImageButton
+           android:id="@+id/buttonAbout"
+           android:layout_alignParentStart="true"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"/>
+
+       <ImageButton
+           android:id="@+id/buttonDown"
+           android:layout_centerHorizontal="true"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"/>
+       <TextView
+           android:id="@+id/bubbleUpdates"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:layout_marginStart="100dp"
+           android:layout_alignTop="@+id/buttonDown"
+           android:layout_alignEnd="@+id/buttonDown"
+           android:textColor="#FFF"
+           android:textSize="16sp"
+           android:textStyle="bold"
+           android:background="@drawable/badge_circle"/>
+
+       <ImageButton
+           android:id="@+id/buttonExit"
+           android:layout_alignParentEnd="true"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"/>
+
+</RelativeLayout>
\ No newline at end of file
