#56 Multiple logins

Merged
fynngodau merged 18 commits from fynngodau/logins into fynngodau/master 4 years ago

+ 0 - 1
app/src/debug/res/values/debug.xml

@@ -21,7 +21,6 @@
   -->
 
 <resources>
-    <bool name="reauthenticate_button">true</bool>
     <bool name="authenticate_quit_on_cancel">false</bool>
     <bool name="school_name_as_window_title_default">true</bool>
     <bool name="news_check_button">true</bool>

+ 9 - 4
app/src/main/AndroidManifest.xml

@@ -2,6 +2,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="godau.fynn.dsbdirect">
 
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.INTERNET" />
 
@@ -30,7 +31,7 @@
             </intent-filter>
         </activity>
         <activity
-            android:name=".activity.SettingsActivity"
+            android:name=".activity.settings.MainSettingsActivity"
             android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
             android:exported="false"
             android:label="@string/title_activity_settings"
@@ -39,12 +40,16 @@
                 android:name="android.support.PARENT_ACTIVITY"
                 android:value="godau.fynn.dsbdirect.activity.MainActivity" />
         </activity>
-        <activity android:name=".activity.StyleSettingsActivity"
+        <activity android:name=".activity.settings.StyleSettingsActivity"
             android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
             android:exported="false"
             android:label="@string/settings_view_styling"
-            android:parentActivityName=".activity.SettingsActivity"/>
-
+            android:parentActivityName=".activity.settings.MainSettingsActivity"/>
+        <activity android:name=".activity.settings.LoginSettingsActivity"
+            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
+            android:exported="false"
+            android:label="@string/settings_login_manage"
+            android:parentActivityName=".activity.settings.MainSettingsActivity"/>
         <service
             android:name=".table.PollingService"
             android:exported="false"

+ 119 - 0
app/src/main/java/godau/fynn/dsbdirect/Login.java

@@ -0,0 +1,119 @@
+package godau.fynn.dsbdirect;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import javax.annotation.Nullable;
+
+public class Login {
+
+    private String id, pass;
+    @Nullable private String displayName;
+
+    public Login(String id, String pass) {
+        this.id = id;
+        this.pass = pass;
+    }
+
+    public Login(String id, String pass, String displayName) {
+        this.id = id;
+        this.pass = pass;
+        this.displayName = displayName;
+    }
+
+    /**
+     * Deserialize a serialized login in this format: {@code "display name" id / pass}
+     * @param serialized A String in the above format
+     */
+    public Login(String serialized) {
+
+        if (serialized.startsWith("\"")) {
+            String[] split = serialized.split("\"");
+            displayName = split[1];
+            serialized = serialized.replace("\"" + displayName + "\" ", "");
+        }
+
+        String[] split = serialized.split(" / ");
+
+        // Check whether amount is valid
+        if (split.length < 2) {
+            throw new IllegalArgumentException("serialized string does not contain at least one \" / \" divider");
+        }
+
+        pass = "";
+
+        for (String s :
+                split) {
+            if (id == null) id = s;
+            else pass = pass + s;
+        }
+
+    }
+
+    /**
+     * @return The name that this login should be displayed as
+     */
+    public String getDisplayName() {
+        if (displayName != null) {
+            return displayName;
+        } else {
+            return id;
+        }
+    }
+
+    /**
+     * @return Whether {@link #displayName} is not null
+     */
+    public boolean hasDisplayName() {
+        return displayName != null;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * @return {@code true} if {@link #id} and {@link #pass} both have a length larger than 0, otherwise {@code false}
+     */
+    public boolean isNonZeroLength() {
+        return id.length() > 0 && pass.length() > 0;
+    }
+
+    /**
+     * Inserts credentials into query json body
+     * @param body Body to insert credentials into
+     */
+    public void put(JSONObject body) throws JSONException {
+        body.put("UserId", id)
+                .put("UserPw", pass);
+
+    }
+
+    /**
+     * Serialize this login to a String
+     * @return The login serialized to the format {@code "displayName" id / pass} or {@code id / pass}
+     */
+    public String serialize() {
+        if (displayName == null) {
+            return id + " / " + pass;
+        } else {
+            return "\"" + displayName + "\" " + id + " / " + pass;
+        }
+    }
+
+    /**
+     * @return {@code true} if object is a login with the same id, otherwise {@code super.equals(obj)}.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj.getClass().equals(Login.class)) {
+            return ((Login) obj).getId().equals(id);
+        } else {
+            return super.equals(obj);
+        }
+    }
+}

+ 2 - 2
app/src/main/java/godau/fynn/dsbdirect/Utility.java

@@ -75,8 +75,8 @@ public class Utility {
     // For convenience: in case you find sharing annoying, you can disable it
     public static final String SUPER_SECRET_SETTING_HOLD_TO_SHARE = "holdToShare";
 
-    // For remembering which school you're at: display school name as window title
-    public static final String SUPER_SECRET_SETTING_SCHOOL_NAME_AS_WINDOW_TITLE = "schoolNameAsWindowTitle";
+    // For remembering which school you're at: display school name as window title even if there is only one login
+    public static final String SUPER_SECRET_SETTING_FORCE_SCHOOL_NAME_AS_WINDOW_TITLE = "schoolNameAsWindowTitle";
 
     private Context mContext;
 

+ 24 - 21
app/src/main/java/godau/fynn/dsbdirect/activity/LoginActivity.java

@@ -23,27 +23,25 @@ package godau.fynn.dsbdirect.activity;
 
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.preference.PreferenceActivity;
 import android.support.design.widget.TextInputEditText;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
 import android.view.KeyEvent;
 import android.view.View;
 import android.widget.Button;
+import godau.fynn.dsbdirect.Login;
 import godau.fynn.dsbdirect.NewsQuery;
 import godau.fynn.dsbdirect.R;
 import godau.fynn.dsbdirect.Utility;
+import godau.fynn.dsbdirect.activity.settings.MainSettingsActivity;
 import godau.fynn.dsbdirect.manager.DownloadManager;
 import godau.fynn.dsbdirect.manager.FileManager;
+import godau.fynn.dsbdirect.manager.LoginManager;
 import godau.fynn.dsbdirect.table.Table;
-import godau.fynn.dsbdirect.table.reader.Reader;
-import org.json.JSONArray;
-import org.json.JSONException;
 
 import java.io.IOException;
 
@@ -60,13 +58,13 @@ public class LoginActivity extends AppCompatActivity {
         Button loginAvh = findViewById(R.id.loginAvh);
 
         final Utility u = new Utility(LoginActivity.this);
-        final SharedPreferences sharedPreferences = u.getSharedPreferences();
 
         login.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                final String id = idEditText.getText().toString();
-                final String pass = passEditText.getText().toString();
+                String id = idEditText.getText().toString();
+                String pass = passEditText.getText().toString();
+                final Login login = new Login(id, pass);
 
                 // Indicate that something is happening
                 indicateProgress(true);
@@ -78,17 +76,24 @@ public class LoginActivity extends AppCompatActivity {
                     public void run() {
                         try {
 
-                            JSONArray json = downloadManager.downloadTimetableList(id, pass);
+                            Table[] tables = downloadManager.downloadTimetableList(login);
 
                             // If no exception occurred, the credentials are correct
-                            sharedPreferences.edit()
-                                    .putString("id", idEditText.getText().toString())
-                                    .putString("pass", passEditText.getText().toString())
-                                    .apply();
+                            LoginManager loginManager = new LoginManager(LoginActivity.this);
+
+                            // Only prompt user for their preferences when they log in with their first login
+                            boolean promptPreferences = loginManager.getLoginCount() <= 0;
+
+                            loginManager.addLogin(login);
 
                             setResult(RESULT_OK);
 
-                            potentiallyPromptForFilter(downloadManager, u, id, json);
+                            if (promptPreferences) {
+                                potentiallyPromptForFilter(downloadManager, u, login.getId(), tables);
+                            } else {
+                                // Skip questions
+                                finish();
+                            }
 
                         } catch (DownloadManager.LoginFailureException e) {
 
@@ -110,7 +115,7 @@ public class LoginActivity extends AppCompatActivity {
                                             .setNegativeButton(R.string.credentials_popup_open_preferences, new DialogInterface.OnClickListener() {
                                                 @Override
                                                 public void onClick(DialogInterface dialog, int which) {
-                                                    startActivity(new Intent(LoginActivity.this, SettingsActivity.class));
+                                                    startActivity(new Intent(LoginActivity.this, MainSettingsActivity.class));
                                                 }
                                             })
                                             .show();
@@ -127,7 +132,7 @@ public class LoginActivity extends AppCompatActivity {
                                     // Error
                                     new AlertDialog.Builder(LoginActivity.this)
                                             .setTitle(R.string.network_generic_error)
-                                            .setMessage(R.string.network_generic_error_popup_message)
+                                            .setMessage(R.string.network_generic_error_credentials)
                                             .setPositiveButton(R.string.ok, null)
                                             .show();
                                     indicateProgress(false);
@@ -164,12 +169,10 @@ public class LoginActivity extends AppCompatActivity {
         });
     }
 
-    private void potentiallyPromptForFilter(final DownloadManager downloadManager, final Utility u, final String id, JSONArray json) {
+    private void potentiallyPromptForFilter(final DownloadManager downloadManager, final Utility u, final String id, Table[] tables) {
 
         try {
 
-            Table[] tables = Reader.readTableList(json);
-
             if (tables[0].isHtml()) {
                 FileManager fileManager = new FileManager(LoginActivity.this);
 
@@ -182,7 +185,7 @@ public class LoginActivity extends AppCompatActivity {
                         @Override
                         public void run() {
                             indicateProgress(false);
-                            SettingsActivity.setFilterPopup(LoginActivity.this, new Handler(new Handler.Callback() {
+                            MainSettingsActivity.setFilterPopup(LoginActivity.this, new Handler(new Handler.Callback() {
                                 @Override
                                 public boolean handleMessage(Message msg) {
                                     // Ensure parse and filter are on
@@ -211,7 +214,7 @@ public class LoginActivity extends AppCompatActivity {
             }
 
 
-        } catch (DownloadManager.NoContentException | JSONException e) {
+        } catch (DownloadManager.NoContentException e) {
 
             promptForNotificationsOnUiThreadAndFinish();
 

+ 74 - 48
app/src/main/java/godau/fynn/dsbdirect/activity/MainActivity.java

@@ -45,12 +45,12 @@ import android.webkit.WebView;
 import android.widget.*;
 import com.ortiz.touchview.TouchImageView;
 import com.wefika.horizontalpicker.HorizontalPicker;
-import godau.fynn.dsbdirect.BuildConfig;
-import godau.fynn.dsbdirect.NewsQuery;
-import godau.fynn.dsbdirect.R;
-import godau.fynn.dsbdirect.Utility;
+import godau.fynn.dsbdirect.*;
+import godau.fynn.dsbdirect.activity.settings.MainSettingsActivity;
+import godau.fynn.dsbdirect.activity.settings.SettingsActivity;
 import godau.fynn.dsbdirect.manager.DownloadManager;
 import godau.fynn.dsbdirect.manager.FileManager;
+import godau.fynn.dsbdirect.manager.LoginManager;
 import godau.fynn.dsbdirect.table.Adapter;
 import godau.fynn.dsbdirect.table.Entry;
 import godau.fynn.dsbdirect.table.Table;
@@ -60,19 +60,21 @@ import humanize.Humanize;
 import humanize.time.TimeMillis;
 import org.json.JSONArray;
 import org.json.JSONException;
-import org.json.JSONObject;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
 
 public class MainActivity extends AppCompatActivity {
 
     private static final int REQUEST_LOGIN = 1;
 
     private Table mCurrentTable;
-    private String credentialsId;
-    private String credentialsPass;
+
+    private Login login;
 
     private Table[] mTables;
     private Date mTimetabledate;
@@ -82,6 +84,7 @@ public class MainActivity extends AppCompatActivity {
     private Utility u;
     private FileManager mFileManager;
     private DownloadManager mDownloadManager;
+    private LoginManager mLoginManager;
 
     private ArrayList<Thread> mReaderThreads = new ArrayList<>();
 
@@ -114,8 +117,6 @@ public class MainActivity extends AppCompatActivity {
         Log.d("DRAW", "rendering layout took " + (drawTimeEnd - drawTimeStart) + " milliseconds");
 
         final SharedPreferences sharedPreferences = u.getSharedPreferences();
-        credentialsId = sharedPreferences.getString("id", "");
-        credentialsPass = sharedPreferences.getString("pass", "");
 
         mParse = sharedPreferences.getBoolean("parse", true);
         mMerge = sharedPreferences.getBoolean("merge", true);
@@ -132,6 +133,7 @@ public class MainActivity extends AppCompatActivity {
         // Get managers
         mFileManager = new FileManager(MainActivity.this);
         mDownloadManager = new DownloadManager(this);
+        mLoginManager = new LoginManager(MainActivity.this);
 
         int previousVersion = sharedPreferences.getInt("version", BuildConfig.VERSION_CODE);
 
@@ -141,11 +143,13 @@ public class MainActivity extends AppCompatActivity {
         }
 
         // Migrate Röntgen users from 2.0.3 (version code 17)
-        if ((previousVersion <= 17) && (credentialsId.equals("219261") || u.getParserUserSetting().equals(Utility.ROENTGEN))) {
+        if ((previousVersion <= 17) && (sharedPreferences.getString("id", "").equals("219261") || u.getParserUserSetting().equals(Utility.ROENTGEN))) {
             sharedPreferences.edit()
                     .putBoolean("login", false)
                     .putInt("version", BuildConfig.VERSION_CODE)
                     .remove("parser")
+                    .remove("id")
+                    .remove("pass")
                     .apply();
 
             new AlertDialog.Builder(MainActivity.this)
@@ -155,7 +159,7 @@ public class MainActivity extends AppCompatActivity {
                     .setNeutralButton(R.string.contact_app_developer, new DialogInterface.OnClickListener() {
                         @Override
                         public void onClick(DialogInterface dialog, int which) {
-                            SettingsActivity.emailTheDev(MainActivity.this);
+                            MainSettingsActivity.emailTheDev(MainActivity.this);
                             finish();
                         }
                     })
@@ -173,11 +177,20 @@ public class MainActivity extends AppCompatActivity {
             return;
         }
 
-        // Delete useless auth token from versions from 2.4.1 (version code 22)
+        // Delete useless auth token from versions up to 2.4.1 (version code 22)
         if (previousVersion <= 22) {
             sharedPreferences.edit().remove("token").apply();
         }
 
+        // Migrate login from up to version 2.5.5 (version code 29)
+        if (previousVersion <= 29) {
+            mLoginManager.addLogin(new Login(
+                    sharedPreferences.getString("id", ""),
+                    sharedPreferences.getString("pass", "")
+            ));
+            sharedPreferences.edit().remove("pass").apply();
+        }
+
         // Migrate users to most recent version by wiping news after update
         if (previousVersion < BuildConfig.VERSION_CODE) {
             Log.d("MIGRATION", "wiping news");
@@ -185,8 +198,9 @@ public class MainActivity extends AppCompatActivity {
         }
 
         // Start loading or show login screen
-        boolean login = sharedPreferences.getBoolean("login", false);
-        if (login && !credentialsId.isEmpty() && !credentialsPass.isEmpty()) {
+        if (mLoginManager.canLogin()) {
+            login = mLoginManager.getActiveLogin();
+
             // Start loading
             new Thread(new Runnable() {
                 @Override
@@ -235,10 +249,7 @@ public class MainActivity extends AppCompatActivity {
     private void getTimetables() {
 
         try {
-            final JSONArray json = mDownloadManager.downloadTimetableList(credentialsId, credentialsPass);
-
-            // Parse JSON
-            mTables = Reader.readTableList(json);
+            mTables = mDownloadManager.downloadTimetableList(login);
 
             // No content
             if (mTables.length == 0) {
@@ -327,7 +338,7 @@ public class MainActivity extends AppCompatActivity {
                     });
                 }
             });
-        } catch (DownloadManager.UnexpectedResponseException | JSONException e) {
+        } catch (DownloadManager.UnexpectedResponseException e) {
             runOnUiThread(new Runnable() {
                 @Override
                 public void run() {
@@ -442,7 +453,7 @@ public class MainActivity extends AppCompatActivity {
 
             // Create runnable
             final ReaderRunnable readerRunnable = new ReaderRunnable(MainActivity.this, response,
-                    credentialsId, mReaderThreads
+                    login.getId(), mReaderThreads
             );
             readerRunnable.addHandlers(
                     new Handler(new Handler.Callback() {
@@ -451,9 +462,21 @@ public class MainActivity extends AppCompatActivity {
                             // Display parsed entries
                             displayEntries(readerRunnable.getResult());
 
-                            if (u.getSharedPreferences().getBoolean(Utility.SUPER_SECRET_SETTING_SCHOOL_NAME_AS_WINDOW_TITLE, getResources().getBoolean(R.bool.school_name_as_window_title_default))) {
-                                setTitle(readerRunnable.getSchoolName());
+                            String schoolName = readerRunnable.getSchoolName();
+                            if (schoolName != null) {
+                                // Display school name as window title if configured via preference, is debug build or multiple logins are configured
+                                if (u.getSharedPreferences().getBoolean(Utility.SUPER_SECRET_SETTING_FORCE_SCHOOL_NAME_AS_WINDOW_TITLE, getResources().getBoolean(R.bool.school_name_as_window_title_default))
+                                        || mLoginManager.getLoginCount() > 1) {
+                                    setTitle(schoolName);
+                                }
+
+                                // Set login display name to school name if doesn't already have one
+                                if (!mLoginManager.getActiveLogin().hasDisplayName()) {
+                                    mLoginManager.getActiveLogin().setDisplayName(schoolName);
+                                    mLoginManager.write();
+                                }
                             }
+
                             return false;
                         }
                     }),
@@ -689,30 +712,14 @@ public class MainActivity extends AppCompatActivity {
         // Don't merge
         mMerge = false;
 
-        // Find every file
-        final File[] files = mFileManager.listFiles();
+        // Find every file, sorted descending by last modified
+        final File[] files = mFileManager.getFilesSorted();
 
         if (files.length == 0) {
             // We're done
             return;
         }
 
-        // Sort files descending by last modified
-        Arrays.sort(files, new Comparator<File>() {
-            @Override
-            public int compare(File o1, File o2) {
-                if (o1.lastModified() < o2.lastModified()) {
-                    return 1;
-                } else if (o1.lastModified() == o2.lastModified()) {
-                    return 0;
-                } else if (o1.lastModified() > o2.lastModified()) {
-                    return -1;
-                }
-                // We should never get here but whatever
-                return 0;
-            }
-        });
-
         // Read every file to a Table
         mTables = new Table[files.length];
 
@@ -826,13 +833,20 @@ public class MainActivity extends AppCompatActivity {
     public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.menu, menu);
 
-        if (getResources().getBoolean(R.bool.reauthenticate_button)) {
-            menu.add(Menu.NONE, 3, Menu.NONE, R.string.action_reauth);
-        }
         if (getResources().getBoolean(R.bool.news_check_button)) {
             menu.add(Menu.NONE, 4, Menu.NONE, R.string.action_check_news);
         }
 
+        // Add all inactive logins as a submenu if there is one
+        if (mLoginManager.getLoginCount() > 1) {
+            SubMenu loginMenu = menu.addSubMenu(R.string.action_switch_login);
+            for (Login l :
+                    mLoginManager.getInactiveLogins()) {
+                loginMenu.add(Menu.NONE, Integer.parseInt(l.getId()), Menu.NONE, l.getDisplayName());
+            }
+            loginMenu.add(Menu.NONE, 5, Menu.NONE, R.string.action_add_login).setIcon(R.drawable.ic_add_black_24dp);
+        }
+
         // Pass it around!
         mMenu = menu;
 
@@ -855,7 +869,7 @@ public class MainActivity extends AppCompatActivity {
                 }
                 break;
             case R.id.action_settings:
-                Intent settingsIntent = new Intent(MainActivity.this, SettingsActivity.class);
+                Intent settingsIntent = new Intent(MainActivity.this, MainSettingsActivity.class);
 
                 // Pass along whether plan contains html files and whether plan only consists of html files
                 if (mTables != null) {
@@ -877,15 +891,27 @@ public class MainActivity extends AppCompatActivity {
                 }
 
                 startActivityForResult(settingsIntent,
-                        SettingsActivity.class.getName().length() // I needed a constant number, so I chose something simple & easy to remember
+                        MainSettingsActivity.class.getName().length() // I needed a constant number, so I chose something simple & easy to remember
                 );
                 break;
-            case 3:
+            case 5:
                 startActivityForResult(new Intent(MainActivity.this, LoginActivity.class), REQUEST_LOGIN);
                 break;
             case 4:
                 new Thread(new NewsQuery(MainActivity.this, mDownloadManager)).start();
+                break;
             default:
+                // Find out whether this id fits any login id
+                for (Login l :
+                        mLoginManager.getLogins()) {
+                    if (item.getItemId() == Integer.parseInt(l.getId())) {
+                        Log.d("LOGINSWITCH", "to login " + l.getDisplayName());
+                        mLoginManager.setActiveLogin(l);
+                        recreate();
+                        return true;
+                    }
+                }
+
                 // If we got here, the user's action was not recognized.
                 // Invoke the superclass to handle it.
                 return super.onOptionsItemSelected(item);
@@ -926,7 +952,7 @@ public class MainActivity extends AppCompatActivity {
         } else if (resultCode == RESULT_CANCELED && requestCode == REQUEST_LOGIN) {
             // Login was cancelled, quit if not debug
             if (getResources().getBoolean(R.bool.authenticate_quit_on_cancel))
-            finish();
+                finish();
         }
 
         super.onActivityResult(requestCode, resultCode, data);

+ 130 - 0
app/src/main/java/godau/fynn/dsbdirect/activity/settings/LoginSettingsActivity.java

@@ -0,0 +1,130 @@
+package godau.fynn.dsbdirect.activity.settings;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import godau.fynn.dsbdirect.Login;
+import godau.fynn.dsbdirect.R;
+import godau.fynn.dsbdirect.activity.LoginActivity;
+import godau.fynn.dsbdirect.manager.LoginManager;
+
+public class LoginSettingsActivity extends SettingsActivity {
+
+    private static final int REQUEST_LOGIN = 0;
+
+    public PreferenceFragment getPreferenceFragment() {
+        return new PreferenceFragment();
+    }
+
+    public static class PreferenceFragment extends SettingsActivity.PreferenceFragment {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            final LoginManager loginManager = new LoginManager(sharedPreferences);
+
+            // Load correct sharedPreferences
+            getPreferenceManager().setSharedPreferencesName("default");
+
+            // Thanks, https://stackoverflow.com/a/37745292
+            addPreferencesFromResource(R.xml.preferences_none);
+            PreferenceScreen preferenceScreen = this.getPreferenceScreen();
+
+            // Add logins
+            for (final Login l : loginManager.getLogins()) {
+                Preference loginPreference = new Preference(getActivity());
+
+                loginPreference.setTitle(l.getDisplayName());
+                if (l.hasDisplayName()) {
+                    loginPreference.setSummary(l.getId());
+                }
+
+                // Prompt for new display name
+                loginPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                    @Override
+                    public boolean onPreferenceClick(Preference preference) {
+
+                        // Spawn editText layout
+                        final View promptView = LayoutInflater.from(getActivity()).inflate(R.layout.edittext, null);
+                        final EditText editText = promptView.findViewById(R.id.editText);
+
+                        if (l.hasDisplayName()) {
+                            editText.setText(l.getDisplayName());
+                        }
+                        new AlertDialog.Builder(getActivity())
+                                .setTitle(getActivity().getString(R.string.settings_login_alias_popup, l.getId()))
+                                .setMessage(R.string.settings_login_alias_popup_message)
+                                .setView(promptView)
+                                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                                    @Override
+                                    public void onClick(DialogInterface dialog, int which) {
+                                        if (editText.length() <= 0) {
+                                            // To allow display name to be set to school name automatically
+                                            l.setDisplayName(null);
+                                        } else {
+                                            l.setDisplayName(editText.getText().toString());
+                                        }
+                                        // Write changed login alias to shared preferences
+                                        loginManager.write();
+
+                                        // Reload list by recreating
+                                        getActivity().recreate();
+                                    }
+                                })
+                                .setNegativeButton(R.string.cancel, null)
+                                .setNeutralButton(R.string.settings_login_remove, new DialogInterface.OnClickListener() {
+                                    @Override
+                                    public void onClick(DialogInterface dialog, int which) {
+
+                                        loginManager.removeLogin(l);
+
+                                        // Reload list by recreating
+                                        getActivity().recreate();
+                                    }
+                                })
+                                .show();
+                        return true;
+                    }
+                });
+
+                preferenceScreen.addPreference(loginPreference);
+            }
+
+            // Add Add login button
+
+            Preference newLoginPreference = new Preference(getActivity());
+            newLoginPreference.setTitle(R.string.action_add_login);
+
+            // TODO: This button could have a '+' icon, but depending on the theme, it would need to be black or white.
+
+            newLoginPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+
+                    startActivityForResult(new Intent(getActivity(), LoginActivity.class), REQUEST_LOGIN);
+
+                    // TODO: A drawback of this button is that school name can only be set as display alias when its plan is viewed with parsing enabled, not immediately when added here.
+
+                    return true;
+                }
+            });
+
+            preferenceScreen.addPreference(newLoginPreference);
+
+        }
+
+        @Override
+        public void onActivityResult(int requestCode, int resultCode, Intent data) {
+            if (resultCode == RESULT_OK) {
+                getActivity().recreate();
+            }
+        }
+    }
+}

+ 80 - 41
app/src/main/java/godau/fynn/dsbdirect/activity/SettingsActivity.java

@@ -19,10 +19,10 @@
  * developer of the DSB platform.
  */
 
-package godau.fynn.dsbdirect.activity;
+package godau.fynn.dsbdirect.activity.settings;
 
-import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.ProgressDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -34,7 +34,6 @@ import android.os.Handler;
 import android.os.Message;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
-import android.preference.PreferenceFragment;
 import android.support.annotation.Nullable;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -42,61 +41,37 @@ import android.widget.EditText;
 import godau.fynn.dsbdirect.BuildConfig;
 import godau.fynn.dsbdirect.R;
 import godau.fynn.dsbdirect.Utility;
+import godau.fynn.dsbdirect.activity.AboutLibrariesActivity;
 import godau.fynn.dsbdirect.manager.DownloadManager;
 import godau.fynn.dsbdirect.manager.FileManager;
+import godau.fynn.dsbdirect.manager.LoginManager;
+import godau.fynn.dsbdirect.table.Table;
 import humanize.Humanize;
 
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
 // Thanks, https://stackoverflow.com/a/12806877
 
-public class SettingsActivity extends Activity {
-
-    public static final String EXTRA_CONTAINS_HTML = "htmlTable";
-    public static final String EXTRA_HTML_ONLY = "htmlOnlyTable";
+public class MainSettingsActivity extends SettingsActivity {
 
     private static final int REQUEST_CODE_STYLING = 1;
 
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        new Utility(SettingsActivity.this).stylize();
-
-        // Display the fragment as the main content
-        getFragmentManager().beginTransaction().replace(android.R.id.content,
-                new PrefsFragment()).commit();
-
+    public PreferenceFragment getPreferenceFragment() {
+        return new PreferenceFragment();
     }
 
-    @Override
-    public void onBackPressed() {
-        setResult(RESULT_OK);
-
-        super.onBackPressed();
-    }
-
-    public static class PrefsFragment extends PreferenceFragment {
+    public static class PreferenceFragment extends SettingsActivity.PreferenceFragment {
 
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
 
-            // Load correct sharedPreferences
-            getPreferenceManager().setSharedPreferencesName("default");
-
-            final Utility u = new Utility(getActivity());
-            final SharedPreferences sharedPreferences = u.getSharedPreferences();
-
             // Load the preferences from an XML resource
             addPreferencesFromResource(R.xml.preferences);
 
-            // Display value in id field
-            final Preference id = findPreference("id");
-            id.setSummary(sharedPreferences.getString("id", ""));
-
             // Behave if pictures are involved
             final boolean containsHtml = getActivity().getIntent().getBooleanExtra(EXTRA_CONTAINS_HTML, true);
             final boolean onlyHtml = getActivity().getIntent().getBooleanExtra(EXTRA_HTML_ONLY, true);
@@ -179,6 +154,15 @@ public class SettingsActivity extends Activity {
                 findPreference("polling_description").setEnabled(false);
             }
 
+            // Manage logins by tappig Manage logins
+            findPreference("login_manage").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    startActivity(new Intent(getActivity(), LoginSettingsActivity.class));
+                    return true;
+                }
+            });
+
             // Gray out Delete all files now when no files are available
             final Preference clearCache = findPreference("clear_cache_now");
             final FileManager fileManager = new FileManager(getActivity());
@@ -254,15 +238,70 @@ public class SettingsActivity extends Activity {
                             .setTitle(R.string.request_parser_popup_title)
                             .setMessage(R.string.request_parser_popup_message)
                             .setNegativeButton(R.string.dont_request, null)
-                            .setPositiveButton(R.string.acknowledge, new DialogInterface.OnClickListener() {
+                            .setPositiveButton(R.string.transfer_now, new DialogInterface.OnClickListener() {
                                 @Override
                                 public void onClick(DialogInterface dialog, int which) {
-                                    Intent emailTheDev = new Intent(Intent.ACTION_VIEW);
-                                    emailTheDev.setData(Uri.parse(getString(R.string.parser_request_uri,
-                                            getString(R.string.parser_request_body, sharedPreferences.getString("id", "…there is already a parser for the default login"),
-                                                    sharedPreferences.getString("pass", "…you needn't request one")))));
 
-                                    startActivity(emailTheDev);
+                                    final ProgressDialog progressDialog = ProgressDialog.show(
+                                            getActivity(), null,
+                                            getActivity().getString(R.string.request_parser_uploading_message),
+                                            true
+                                    );
+
+                                    new Thread(new Runnable() {
+                                        @Override
+                                        public void run() {
+
+                                            try {
+
+                                                DownloadManager downloadManager = new DownloadManager(getActivity());
+                                                LoginManager loginManager = new LoginManager(sharedPreferences);
+
+                                                // Download timetable list
+                                                Table[] tables = downloadManager.downloadTimetableList(loginManager.getActiveLogin());
+
+                                                final boolean success = downloadManager.uploadParserRequest(tables[0].getUri());
+
+                                                getActivity().runOnUiThread(new Runnable() {
+                                                    @Override
+                                                    public void run() {
+
+                                                        progressDialog.dismiss();
+
+                                                        new AlertDialog.Builder(getActivity())
+                                                                .setTitle(success ?
+                                                                        R.string.request_parser_uploading_successful_title :
+                                                                        R.string.request_parser_uploading_failed_title
+                                                                )
+                                                                .setMessage(success ?
+                                                                        R.string.request_parser_uploading_successful_message :
+                                                                        R.string.request_parser_uploading_failed_message
+                                                                )
+                                                                .setPositiveButton(R.string.ok, null)
+                                                                .show();
+                                                    }
+                                                });
+
+                                            } catch (IOException e) {
+                                                e.printStackTrace();
+
+                                                getActivity().runOnUiThread(new Runnable() {
+                                                    @Override
+                                                    public void run() {
+                                                        progressDialog.dismiss();
+
+                                                        new AlertDialog.Builder(getActivity())
+                                                                .setTitle(R.string.network_generic_error)
+                                                                .setMessage(R.string.network_generic_error_request)
+                                                                .setPositiveButton(R.string.ok, null)
+                                                                .show();
+                                                    }
+                                                });
+                                            }
+                                        }
+                                    }).start();
+
+
                                 }
                             })
                             .show();

+ 53 - 0
app/src/main/java/godau/fynn/dsbdirect/activity/settings/SettingsActivity.java

@@ -0,0 +1,53 @@
+package godau.fynn.dsbdirect.activity.settings;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import godau.fynn.dsbdirect.Utility;
+
+public abstract class SettingsActivity extends Activity {
+
+    public static final String EXTRA_CONTAINS_HTML = "htmlTable";
+    public static final String EXTRA_HTML_ONLY = "htmlOnlyTable";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        new Utility(SettingsActivity.this).stylize();
+
+        // Display the fragment as the main content
+        getFragmentManager().beginTransaction().replace(android.R.id.content,
+                getPreferenceFragment()).commit();
+
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Possibly trigger recreate
+        setResult(RESULT_OK);
+
+        super.onBackPressed();
+    }
+
+    abstract PreferenceFragment getPreferenceFragment();
+
+    public static abstract class PreferenceFragment extends android.preference.PreferenceFragment {
+
+        protected Utility u;
+        protected SharedPreferences sharedPreferences;
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            // Load correct sharedPreferences
+            getPreferenceManager().setSharedPreferencesName("default");
+
+            u = new Utility(getActivity());
+            sharedPreferences = u.getSharedPreferences();
+
+        }
+    }
+
+}

+ 0 - 0
app/src/main/java/godau/fynn/dsbdirect/activity/StyleSettingsActivity.java


Some files were not shown because too many files changed in this diff