commit c820515c13ce1a7f22ac48256e5b806b936c24c4
Author: leszek <leszek@koltunski.pl>
Date:   Wed Jan 10 22:46:40 2024 +0100

    Progress with sorting the objects by various criteria.

diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index 53826eaa..5646f9d7 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -66,7 +66,7 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
     private FirebaseAnalytics mFirebaseAnalytics;
     private int mCurrentApiVersion;
     private String mOldVersion, mCurrVersion;
-    private int mScreenWidth;
+    private int mScreenWidth, mScreenHeight;
     private TextView mBubbleUpdates;
     private int mNumUpdates;
     private int mCurrentObject;
@@ -199,6 +199,7 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       float conv = ((float) dpi/DisplayMetrics.DENSITY_DEFAULT);
 
       mScreenWidth = (int) (conf.screenWidthDp*conv + 0.5f);
+      mScreenHeight= (int) (conf.screenHeightDp*conv + 0.5f);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -456,7 +457,13 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
 
     public void onSettings(View v)
       {
+      int sw = (int)(mScreenWidth*0.7);
+      int sh = (int)(mScreenHeight*0.2f);
 
+      int vw = v.getWidth();
+
+      MainSettingsPopup popup = new MainSettingsPopup(this,mScreenWidth,mScreenHeight);
+      popup.displayPopup(this,v,sw,sh,(int)((vw-sw)/2),0);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/main/MainSettingsPopup.java b/src/main/java/org/distorted/main/MainSettingsPopup.java
new file mode 100644
index 00000000..df80f004
--- /dev/null
+++ b/src/main/java/org/distorted/main/MainSettingsPopup.java
@@ -0,0 +1,171 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.main;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListPopupWindow;
+import android.widget.PopupWindow;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.lang.reflect.Field;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class MainSettingsPopup implements AdapterView.OnItemSelectedListener
+  {
+  private static final float MENU_TITLE_SIZE= 0.070f;
+  private static final float MENU_TEXT_SIZE = 0.060f;
+
+  private final PopupWindow mPopup;
+  private static final int[] mLocation = new int[2];
+  private int mCurrMethod;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this is supposed to prevent showing the navigational bar when we show the drop down list,
+// but it doesn't quite work... At least not in the emulator.
+// see https://gist.github.com/kakajika/a236ba721a5c0ad3c1446e16a7423a63
+//
+// EDIT: the bar does not appear on API 34 without the need to call this method. Must have been
+// fixed by Android developers.
+
+  public static void avoidSpinnerDropdownFocus(Spinner spinner)
+    {
+    try
+      {
+      Field listPopupField = Spinner.class.getDeclaredField("mPopup");
+      listPopupField.setAccessible(true);
+      Object listPopup = listPopupField.get(spinner);
+
+      if( listPopup instanceof ListPopupWindow )
+        {
+        Field popupField = ListPopupWindow.class.getDeclaredField("mPopup");
+        popupField.setAccessible(true);
+        Object popup = popupField.get((ListPopupWindow) listPopup);
+
+        if( popup instanceof PopupWindow )
+          {
+          ((PopupWindow) popup).setFocusable(false);
+          }
+        }
+      }
+    catch (NoSuchFieldException | IllegalAccessException ignored)
+      {
+
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  MainSettingsPopup(MainActivity act, int width, int height)
+    {
+    // due to bugs in Android API <=25, a Spinner inside a PopupWindow will crash once you click on it.
+    // solution: on those APIs, use a special Spinner in dialog mode (this does not crash)
+    int id = android.os.Build.VERSION.SDK_INT <= 25 ? R.layout.settings_popup_android25 : R.layout.settings_popup;
+
+    LayoutInflater layoutInflater = (LayoutInflater) act.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    final View layout = layoutInflater.inflate(id, null);
+
+    mPopup = new PopupWindow(act);
+    mPopup.setContentView(layout);
+    mPopup.setFocusable(true);
+
+    int titleSize = (int)(MENU_TITLE_SIZE*width);
+    int textSize  = (int)(MENU_TEXT_SIZE*width);
+
+    TextView title = layout.findViewById(R.id.sortTitle);
+    title.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    TextView text = layout.findViewById(R.id.sortText);
+    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+
+    Spinner actSpinner  = layout.findViewById(R.id.sortMethod);
+    actSpinner.setOnItemSelectedListener(this);
+
+    mCurrMethod = -1;
+
+    String[] actNames = { "Classic" , "Category" , "Difficulty", "Author" , "Year" };
+    ArrayAdapter<String> actAdapter = new ArrayAdapter<>(act, android.R.layout.simple_spinner_item, actNames);
+    actAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+    actSpinner.setAdapter(actAdapter);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// work around lame bugs in Android's version <= 10 pop-up and split-screen modes
+
+  public void displayPopup(MainActivity act, View view, int w, int h, int xoff, int yoff)
+    {
+    View v = mPopup.getContentView();
+    v.setSystemUiVisibility(MainActivity.FLAGS);
+
+    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_CODES.R<=Build.VERSION.SDK_INT || isFullScreen )
+        {
+        mPopup.showAsDropDown(view, xoff, yoff, Gravity.CENTER);
+        mPopup.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;
+        mPopup.showAsDropDown(view);
+        mPopup.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.
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
+    {
+    if( parent.getId()==R.id.sortMethod && mCurrMethod!=pos )
+      {
+      mCurrMethod = pos;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onNothingSelected(AdapterView<?> parent)
+    {
+
+    }
+  }
+
diff --git a/src/main/res/layout/settings_popup.xml b/src/main/res/layout/settings_popup.xml
new file mode 100644
index 00000000..0d32a3d9
--- /dev/null
+++ b/src/main/res/layout/settings_popup.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+   android:id="@+id/settingsGrid"
+   android:layout_width="wrap_content"
+   android:layout_height="wrap_content"
+   android:gravity="center"
+   android:orientation="vertical">
+
+   <TextView
+        android:id="@+id/sortTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/settings_title"
+        android:background="@color/light_grey"
+        android:gravity="center"/>
+
+   <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+       <TextView
+            android:id="@+id/sortText"
+            android:layout_marginStart="5dp"
+            android:layout_marginEnd="25dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:text="@string/sort_by"
+            android:gravity="start|center_vertical"/>
+
+       <Spinner
+            android:id="@+id/sortMethod"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="end|center_vertical"/>
+
+   </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/layout/settings_popup_android25.xml b/src/main/res/layout/settings_popup_android25.xml
new file mode 100644
index 00000000..daf5dafc
--- /dev/null
+++ b/src/main/res/layout/settings_popup_android25.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+   android:id="@+id/settingsGrid"
+   android:layout_width="wrap_content"
+   android:layout_height="wrap_content"
+   android:gravity="center"
+   android:orientation="vertical">
+
+   <TextView
+        android:id="@+id/sortTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/settings_title"
+        android:background="@color/light_grey"
+        android:gravity="center"/>
+
+   <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+       <TextView
+            android:id="@+id/sortText"
+            android:layout_marginStart="5dp"
+            android:layout_marginEnd="25dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:text="@string/sort_by"
+            android:gravity="start|center_vertical"/>
+
+        <Spinner
+            android:id="@+id/sortMethod"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:spinnerMode="dialog"
+            android:gravity="end|center_vertical"/>
+
+   </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 1cf31b50..3cae2bf1 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">Kontakt</string>
     <string name="email">Melden Sie einen Fehler, schlagen Sie eine Funktion vor:</string>
     <string name="exit_app">App beenden?</string>
+    <string name="sort_by">Ordnung</string>
+    <string name="settings_title">Einstellungen</string>
 
     <string name="object_solver">Löser</string>
     <string name="object_pattern">Muster</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index f01947d5..fc1cdb46 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">Contacto</string>
     <string name="email">Reportar un error, sugerir una función:</string>
     <string name="exit_app">¿Salir de la aplicación?</string>
+    <string name="sort_by">Ordenar</string>
+    <string name="settings_title">Ajustes</string>
 
     <string name="object_solver">Solucionador</string>
     <string name="object_pattern">Patrones</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index 71f3956d..fc42328e 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">Contactez-nous</string>
     <string name="email">Signaler un bug, suggérer une fonctionnalité :</string>
     <string name="exit_app">Quitter l\'application ?</string>
+    <string name="sort_by">Trier par</string>
+    <string name="settings_title">Paramètres</string>
 
     <string name="object_solver">Solveur</string>
     <string name="object_pattern">Motifs</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index f7466e94..0b8685cd 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">お問い合わせ</string>
     <string name="email">バグを報告し、機能を提案してください:</string>
     <string name="exit_app">アプリを終了しますか?</string>
+    <string name="sort_by">並べ替え</string>
+    <string name="settings_title">設定</string>
 
     <string name="object_solver">ソルバー</string>
     <string name="object_pattern">パターン</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index 8068499a..74a4b730 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">문의하기</string>
     <string name="email">버그 신고, 기능 제안:</string>
     <string name="exit_app">앱을 종료하시겠습니까?</string>
+    <string name="sort_by">정렬 기준</string>
+    <string name="settings_title">설정</string>
 
     <string name="object_solver">솔버</string>
     <string name="object_pattern">패턴</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 9e1a2a32..e44e8c4b 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">Kontakt</string>
     <string name="email">Zaraportuj błąd, zadaj pytanie:</string>
     <string name="exit_app">Wyjść z apki?</string>
+    <string name="sort_by">Sortuj po</string>
+    <string name="settings_title">Ustawienia</string>
 
     <string name="object_solver">Rozwiązywacz</string>
     <string name="object_pattern">Wzory</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index a17264c3..76e552ab 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">Контакт</string>
     <string name="email">Сообщить об ошибке, задать вопрос:</string>
     <string name="exit_app">Выйти из приложения?</string>
+    <string name="sort_by">Группа по</string>
+    <string name="settings_title">Настройки</string>
 
     <string name="object_solver">Решатель</string>
     <string name="object_pattern">Узоры</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index ef73e65e..56479de3 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">联系我们</string>
     <string name="email">报告错误，提出问题：</string>
     <string name="exit_app">退出应用程序？</string>
+    <string name="sort_by">排序方式</string>
+    <string name="settings_title">设置</string>
 
     <string name="object_solver">求解器</string>
     <string name="object_pattern">图案</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 9618c351..691f9d62 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -53,6 +53,8 @@
     <string name="contact">聯繫我們</string>
     <string name="email">报告错误，提出问题：</string>
     <string name="exit_app">退出應用程式？</string>
+    <string name="sort_by">排序方式</string>
+    <string name="settings_title">設定</string>
 
     <string name="object_solver">求解器</string>
     <string name="object_pattern">圖案</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index ecf571c3..fce5d383 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -55,6 +55,8 @@
     <string name="contact">Contact us</string>
     <string name="email">Report a bug, suggest a feature:</string>
     <string name="exit_app">Exit App?</string>
+    <string name="sort_by">Sort by</string>
+    <string name="settings_title">Settings</string>
 
     <string name="stars">Stars</string>
     <string name="scores">High Scores</string>
