commit 4895fff62f404b50a1cabd6df6951a6db74c3c4d
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Sun Mar 22 00:29:10 2020 +0000

    Beginnings of submitting one's high scores.

diff --git a/src/main/java/org/distorted/dialog/RubikDialogNewRecord.java b/src/main/java/org/distorted/dialog/RubikDialogNewRecord.java
index 57b53867..5a876c35 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogNewRecord.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogNewRecord.java
@@ -34,8 +34,9 @@ import android.widget.TextView;
 
 import org.distorted.magic.R;
 import org.distorted.magic.RubikActivity;
-import org.distorted.scores.RubikScoresDownloader;
+import org.distorted.object.RubikObjectList;
 import org.distorted.uistate.RubikState;
+import org.distorted.uistate.RubikStatePlay;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -72,10 +73,20 @@ public class RubikDialogNewRecord extends AppCompatDialogFragment
       @Override
       public void onClick(DialogInterface dialog, int which)
         {
-        RubikScoresDownloader downloader = RubikScoresDownloader.getInstance();
-        downloader.submit();
         RubikActivity act = (RubikActivity)getActivity();
         RubikState.switchState(act,RubikState.PLAY);
+
+        RubikStatePlay play = (RubikStatePlay) RubikState.PLAY.getStateClass();
+        int object = play.getObject();
+        int size   = play.getSize();
+
+        Bundle bundle = new Bundle();
+        bundle.putInt("tab", RubikObjectList.pack(object,size) );
+        bundle.putBoolean("submitting", true);
+
+        RubikDialogScores scores = new RubikDialogScores();
+        scores.setArguments(bundle);
+        scores.show(act.getSupportFragmentManager(), null);
         }
       });
     builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener()
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScores.java b/src/main/java/org/distorted/dialog/RubikDialogScores.java
index e3416c27..579a3f92 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScores.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScores.java
@@ -83,14 +83,17 @@ public class RubikDialogScores extends AppCompatDialogFragment
 
     Bundle args = getArguments();
     int curTab;
+    boolean isSubmitting;
 
     try
       {
       curTab = args.getInt("tab");
+      isSubmitting = args.getBoolean("submitting");
       }
     catch(Exception e)
       {
       curTab = 0;
+      isSubmitting = false;
       }
 
     LayoutInflater inflater = act.getLayoutInflater();
@@ -99,7 +102,7 @@ public class RubikDialogScores extends AppCompatDialogFragment
 
     ViewPager viewPager = view.findViewById(R.id.viewpager);
     TabLayout tabLayout = view.findViewById(R.id.sliding_tabs);
-    mPagerAdapter = new RubikDialogScoresPagerAdapter(act,viewPager);
+    mPagerAdapter = new RubikDialogScoresPagerAdapter(act,viewPager,isSubmitting);
     tabLayout.setupWithViewPager(viewPager);
 
     viewPager.setCurrentItem(curTab);
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java b/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
index 12cc7796..84bc1697 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScoresPagerAdapter.java
@@ -27,8 +27,6 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import org.distorted.magic.R;
-import org.distorted.scores.RubikScores;
 import org.distorted.scores.RubikScoresDownloader;
 import org.distorted.object.RubikObjectList;
 
@@ -42,6 +40,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
   private RubikDialogScoresView[] mViews;
   private ViewPager mViewPager;
   private int mNumTabs;
+  private boolean mIsSubmitting;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -116,7 +115,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void exception(final String exce)
+  public void message(final String mess)
     {
     mAct.runOnUiThread(new Runnable()
       {
@@ -125,7 +124,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
         {
         for(int i=0; i<mNumTabs; i++)
           {
-          mViews[i].exception(exce);
+          mViews[i].message(mess);
           }
         }
       });
@@ -133,12 +132,13 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  RubikDialogScoresPagerAdapter(FragmentActivity act, ViewPager viewPager)
+  RubikDialogScoresPagerAdapter(FragmentActivity act, ViewPager viewPager, boolean isSubmitting)
     {
     mAct = act;
     mNumTabs = RubikObjectList.getTotal();
     mViews = new RubikDialogScoresView[mNumTabs];
     mViewPager = viewPager;
+    mIsSubmitting = isSubmitting;
 
     viewPager.setAdapter(this);
     viewPager.setOffscreenPageLimit(mNumTabs-1);
@@ -150,7 +150,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
   @NonNull
   public Object instantiateItem(@NonNull ViewGroup collection, int position)
     {
-    mViews[position] = new RubikDialogScoresView(mAct);
+    mViews[position] = new RubikDialogScoresView(mAct, mIsSubmitting);
     collection.addView(mViews[position]);
 
     boolean allCreated = true;
@@ -166,9 +166,10 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikScoresD
 
     if( allCreated )
       {
-      RubikScores scores = RubikScores.getInstance();
       RubikScoresDownloader downloader = RubikScoresDownloader.getInstance();
-      downloader.download( this, scores.getName(), mAct.getString(R.string.app_version), scores.getNumRuns() );
+
+      if( mIsSubmitting )  downloader.submit  ( this, mAct );
+      else                 downloader.download( this, mAct );
       }
 
     return mViews[position];
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScoresView.java b/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
index d3f83ba9..b07079e5 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
@@ -57,12 +57,14 @@ public class RubikDialogScoresView extends FrameLayout
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public RubikDialogScoresView(Context context)
+  public RubikDialogScoresView(Context context, boolean isSubmitting)
     {
     super(context);
 
     View view = inflate(context, R.layout.dialog_scores_downloading, null);
     addView(view);
+    TextView text = findViewById(R.id.message_text);
+    text.setText( context.getString(isSubmitting ? R.string.submitting : R.string.downloading) );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -163,9 +165,9 @@ public class RubikDialogScoresView extends FrameLayout
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // needs to run on UI thread
 
-  void exception(final String exce)
+  void message(final String mess)
     {
-    TextView text = findViewById(R.id.downloading_text);
-    text.setText(exce);
+    TextView text = findViewById(R.id.message_text);
+    text.setText(mess);
     }
   }
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index c1f3cfe9..852aeeb3 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -210,6 +210,7 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
 
       Bundle bundle = new Bundle();
       bundle.putInt("tab", RubikObjectList.pack(object,size) );
+      bundle.putBoolean("submitting", false);
 
       RubikDialogScores scores = new RubikDialogScores();
       scores.setArguments(bundle);
diff --git a/src/main/java/org/distorted/scores/RubikScores.java b/src/main/java/org/distorted/scores/RubikScores.java
index acb11638..e39c1808 100644
--- a/src/main/java/org/distorted/scores/RubikScores.java
+++ b/src/main/java/org/distorted/scores/RubikScores.java
@@ -73,28 +73,6 @@ public class RubikScores
     mDeviceID= -1;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-/*
-  private static int computeHash()
-    {
-    final int MODULO = 227;
-    int ret = 0;
-
-    for(int i=0; i<MAX_CUBE-1; i++)
-      ret += (2*i+3)*passedlevel[i];
-
-    for(int i=0; i<MAX_CUBE-1; i++)
-      for(int j=0; j<MAX_LEVELS; j++)
-        ret += (i*i+3*j)*records[i][j];
-
-    int length = veriname==null ? 0 : veriname.length();
-
-    for(int i=0;i<length;i++)
-      ret += i*veriname.charAt(i);
-
-    return (ret%=MODULO);
-    }
-*/
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static RubikScores getInstance()
@@ -258,22 +236,16 @@ public class RubikScores
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // TODO
 
-  public void verifyName()
+  public void successfulSubmit()
     {
     mNameIsVerified = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
 
-  public void setSubmitted(int object, int size, int scramble)
-    {
-    int maxsize = RubikObjectList.getObject(object).getSizes().length;
-
-    if( object>=0 && object<NUM_OBJECTS && size>=0 && size<maxsize && scramble>=1 && scramble<=MAX_SCRAMBLE )
-      {
-      mSubmitted[object][size][scramble] = 1;
-      }
+    for(int i=0; i<NUM_OBJECTS; i++)
+      for(int j=0; j<MAX_SIZE   ; j++)
+        for(int k=0; k<MAX_SCRAMBLE; k++)
+          {
+          mSubmitted[i][j][k]=1;
+          }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -301,7 +273,7 @@ public class RubikScores
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private int getDeviceID()
+  int getDeviceID()
     {
     int id;
 
@@ -319,7 +291,6 @@ public class RubikScores
     return id<0 ? -id : id;
     }
 
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public long getRecord(int object, int size, int scramble)
@@ -349,24 +320,22 @@ public class RubikScores
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
 
-  public boolean isVerified()
+  boolean isVerified()
     {
     return mNameIsVerified;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
 
-  public int getNumPlays()
+  int getNumPlays()
     {
     return mNumPlays;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public int getNumRuns()
+  int getNumRuns()
     {
     return mNumRuns;
     }
@@ -384,4 +353,69 @@ public class RubikScores
     {
     return mCountry;
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String getUnsubmittedList(int mode)
+    {
+    RubikObjectList list;
+    StringBuilder builder = new StringBuilder();
+    boolean first = true;
+    int[] sizes;
+    int length;
+
+    for(int object=0; object<NUM_OBJECTS; object++)
+      {
+      list = RubikObjectList.getObject(object);
+      sizes = list.getSizes();
+      length = sizes.length;
+
+      for(int size=0; size<length; size++)
+        {
+        for(int scramble=0; scramble<MAX_SCRAMBLE; scramble++)
+          {
+          if( mSubmitted[object][size][scramble]==0 && mRecords[object][size][scramble]<NO_RECORD )
+            {
+            if( !first ) builder.append(',');
+            else         first=false;
+
+            switch(mode)
+              {
+              case 0: builder.append(list.name());
+                      builder.append("_");
+                      builder.append(sizes[size]);
+                      break;
+              case 1: builder.append(scramble);
+                      break;
+              case 2: builder.append(mRecords[object][size][scramble]);
+                      break;
+              }
+            }
+          }
+        }
+      }
+
+    return builder.toString();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  String getUnsubmittedObjlist()
+    {
+    return getUnsubmittedList(0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  String getUnsubmittedLevellist()
+    {
+    return getUnsubmittedList(1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  String getUnsubmittedTimelist()
+    {
+    return getUnsubmittedList(2);
+    }
   }
diff --git a/src/main/java/org/distorted/scores/RubikScoresDownloader.java b/src/main/java/org/distorted/scores/RubikScoresDownloader.java
index 2d040ebc..b4dacbb1 100644
--- a/src/main/java/org/distorted/scores/RubikScoresDownloader.java
+++ b/src/main/java/org/distorted/scores/RubikScoresDownloader.java
@@ -19,6 +19,9 @@
 
 package org.distorted.scores;
 
+import android.support.v4.app.FragmentActivity;
+
+import org.distorted.magic.R;
 import org.distorted.object.RubikObjectList;
 
 import java.io.InputStream;
@@ -35,7 +38,7 @@ public class RubikScoresDownloader implements Runnable
   public interface Receiver
     {
     void receive(String[][][] country, String[][][] name, float[][][] time);
-    void exception(String exception);
+    void message(String mess);
     }
 
   public static final int MAX_PLACES = 10;
@@ -82,8 +85,7 @@ public class RubikScoresDownloader implements Runnable
   private static boolean mRunning = false;
   private static int mMode = IDLE;
   private static Receiver mReceiver;
-  private static String mUserName, mVersion;
-  private static int mNumRuns;
+  private static String mVersion;
 
   private static int mTotal = RubikObjectList.getTotal();
   private static String mScores = "";
@@ -94,9 +96,26 @@ public class RubikScoresDownloader implements Runnable
   private static int[][] mPlaces = new int[mTotal][MAX_SCRAMBLE];
   private static RubikScoresDownloader mThis;
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  private static int computeHash(String name, String veri, String country, String lvllist,
+                                 String objlist, String timelist, int runnings)
+    {
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO: handle the fact that the server might return 'error' (3) or 'name taken' (2)
+
+  private void fillSubmittedValues()
+    {
+    fillDownloadedValues();
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void fillValues()
+  private void fillDownloadedValues()
     {
     int begin=-1 ,end, len = mScores.length();
 
@@ -197,15 +216,13 @@ public class RubikScoresDownloader implements Runnable
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private boolean doDownload()
+  private boolean network(String url)
     {
-    String message="https://distorted.org/magic/cgi-bin/download.cgi";
-    message += "?n="+URLencode(mUserName)+"&r="+mNumRuns+"&e="+mVersion+"d";
-    message += "&o="+RubikObjectList.getObjectList()+"&min=0&max="+MAX_SCRAMBLE+"&l="+MAX_PLACES;
+    android.util.Log.e("down", "url: "+url);
 
     try
       {
-      java.net.URL connectURL = new URL(message);
+      java.net.URL connectURL = new URL(url);
       HttpURLConnection conn = (HttpURLConnection)connectURL.openConnection();
 
       conn.setDoInput(true);
@@ -227,29 +244,29 @@ public class RubikScoresDownloader implements Runnable
         }
       catch( final Exception e)
         {
-        mReceiver.exception("Failed to get an answer from the High Scores server");
+        mReceiver.message("Failed to get an answer from the High Scores server");
         return false;
         }
       }
     catch( final UnknownHostException e )
       {
-      mReceiver.exception("No access to Internet");
+      mReceiver.message("No access to Internet");
       return false;
       }
     catch( final SecurityException e )
       {
-      mReceiver.exception("Application not authorized to connect to the Internet");
+      mReceiver.message("Application not authorized to connect to the Internet");
       return false;
       }
     catch( final Exception e )
       {
-      mReceiver.exception(e.getMessage());
+      mReceiver.message(e.getMessage());
       return false;
       }
 
     if( mScores.length()==0 )
       {
-      mReceiver.exception("Failed to download scores");
+      mReceiver.message("Failed to download scores");
       return false;
       }
 
@@ -270,20 +287,64 @@ public class RubikScoresDownloader implements Runnable
     {
     boolean success=true;
 
-    try
+    if( mMode==DOWNLOAD && gottaDownload() )
       {
-      if( gottaDownload() )
+      mRunning = true;
+
+      try
         {
-        mRunning = true;
-        success = doDownload();
+        RubikScores scores = RubikScores.getInstance();
+        String name = URLencode(scores.getName());
+        String veri = scores.isVerified() ? name : "";
+        int numRuns = scores.getNumRuns();
+        int numPlay = scores.getNumPlays();
+
+        String url="https://distorted.org/magic/cgi-bin/download.cgi";
+        url += "?n="+name+"&v="+veri+"&r="+numRuns+"&p="+numPlay+"&e="+mVersion+"d";
+        url += "&o="+RubikObjectList.getObjectList()+"&min=0&max="+MAX_SCRAMBLE+"&l="+MAX_PLACES;
+
+        success = network(url);
         }
+      catch( Exception e )
+        {
+        mReceiver.message("Exception downloading records: "+e.getMessage() );
+        }
+
+      fillDownloadedValues();
       }
-    catch( Exception e )
+
+    if( mMode==SUBMIT )
       {
-      mReceiver.exception("Exception downloading records: "+e.getMessage() );
-      }
+      mRunning = true;
 
-    fillValues();
+      try
+        {
+        RubikScores scores = RubikScores.getInstance();
+        String name = URLencode(scores.getName());
+        String veri = scores.isVerified() ? name : "";
+        int numRuns = scores.getNumRuns();
+        int numPlay = scores.getNumPlays();
+        int deviceID= scores.getDeviceID();
+        String objlist = scores.getUnsubmittedObjlist();
+        String lvllist = scores.getUnsubmittedLevellist();
+        String timlist = scores.getUnsubmittedTimelist();
+        String country = scores.getCountry();
+        int hash = computeHash(name,veri,country,lvllist,objlist,timlist,numRuns);
+
+        String url="https://distorted.org/magic/cgi-bin/submit.cgi";
+        url += "?n="+name+"&v="+veri+"&r="+numRuns+"&p="+numPlay+"&i="+deviceID+"&e="+mVersion+"d";
+        url += "&o="+objlist+"&l="+lvllist+"&t="+timlist+"&c="+country+"&h="+hash;
+        url += "&oo="+RubikObjectList.getObjectList()+"&min=0&max="+MAX_SCRAMBLE+"&lo="+MAX_PLACES;
+
+        success = network(url);
+        }
+      catch( Exception e )
+        {
+        mReceiver.message("Exception submitting records: "+e.getMessage() );
+        }
+
+      fillSubmittedValues();
+      }
 
     mRunning = false;
 
@@ -323,13 +384,11 @@ public class RubikScoresDownloader implements Runnable
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void download(Receiver receiver, String userName, String version, int numRuns)
+  public void download(Receiver receiver, FragmentActivity act)
     {
     mReceiver = receiver;
     mMode     = DOWNLOAD;
-    mUserName = userName;
-    mVersion  = version;
-    mNumRuns  = numRuns;
+    mVersion  = act.getString(R.string.app_version);
 
     Thread networkThrd = new Thread(this);
     networkThrd.start();
@@ -337,10 +396,13 @@ public class RubikScoresDownloader implements Runnable
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void submit()
+  public void submit(Receiver receiver, FragmentActivity act)
     {
-    mMode = SUBMIT;
+    mReceiver = receiver;
+    mMode     = SUBMIT;
+    mVersion  = act.getString(R.string.app_version);
 
-    android.util.Log.e("downloader", "submitting!");
+    Thread networkThrd = new Thread(this);
+    networkThrd.start();
     }
 }
\ No newline at end of file
diff --git a/src/main/res/layout/dialog_scores_downloading.xml b/src/main/res/layout/dialog_scores_downloading.xml
index dc4bb8a3..6e94c3d8 100644
--- a/src/main/res/layout/dialog_scores_downloading.xml
+++ b/src/main/res/layout/dialog_scores_downloading.xml
@@ -1,9 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
-  android:id="@+id/downloading_text"
+  android:id="@+id/message_text"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:textSize="32sp"
   android:gravity="center"
-  android:text="@string/downloading"
   android:padding="10dp"/>
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index d3c4dff7..1cb620fb 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -25,6 +25,7 @@
     <string name="duration">Duration:</string>
     <string name="type">Type:</string>
     <string name="downloading">Downloading…</string>
+    <string name="submitting">Submitting…</string>
     <string name="credits1">Open Source app developed using the Distorted graphics library. </string>
     <string name="credits2">Code, tutorials, learn how to write your own graphics effect: <a href="https://distorted.org/redmine/projects/magic-cube/wiki">Distorted.org</a></string>
 
