New login UI (#29)
* Refactor activity names * Refactor login activity and async task * Add forgot password and create account links * Login without passing symbol in login form * Add heading text before login form * Refactor API/login * Add login loading steps * Remove unnecessary try catch * Refactor snp and add tests * Remove redudant throws clauses
This commit is contained in:
@ -19,7 +19,7 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/WulkanowyTheme">
|
||||
<activity
|
||||
android:name=".activity.started.StartedActivity"
|
||||
android:name=".activity.splash.SplashActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/WulkanowyTheme.noActionBar">
|
||||
@ -30,9 +30,9 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.main.MainActivity"
|
||||
android:name=".activity.login.LoginActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/login_text"
|
||||
android:label="@string/title_activity_login"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".activity.dashboard.DashboardActivity"
|
||||
@ -48,4 +48,4 @@
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
@ -0,0 +1,210 @@
|
||||
package io.github.wulkanowy.activity.login;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import io.github.wulkanowy.R;
|
||||
|
||||
/**
|
||||
* A login screen that offers login via email/password.
|
||||
*/
|
||||
public class LoginActivity extends Activity {
|
||||
|
||||
private float touchPosition;
|
||||
|
||||
private EditText emailView;
|
||||
|
||||
private EditText passwordView;
|
||||
|
||||
private AutoCompleteTextView symbolView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_login);
|
||||
|
||||
// Set up the login form.
|
||||
emailView = findViewById(R.id.email);
|
||||
passwordView = findViewById(R.id.password);
|
||||
symbolView = findViewById(R.id.symbol);
|
||||
|
||||
passwordView.setOnEditorActionListener(getTextViewSignInListener());
|
||||
symbolView.setOnEditorActionListener(getTextViewSignInListener());
|
||||
|
||||
populateAutoComplete();
|
||||
|
||||
Button signInButton = findViewById(R.id.action_sign_in);
|
||||
signInButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
attemptLogin();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.action_create_account).setOnClickListener(getButtonLinkListener(
|
||||
"https://cufs.vulcan.net.pl/Default/AccountManage/CreateAccount"
|
||||
));
|
||||
|
||||
findViewById(R.id.action_forgot_password).setOnClickListener(getButtonLinkListener(
|
||||
"https://cufs.vulcan.net.pl/Default/AccountManage/UnlockAccount"
|
||||
));
|
||||
}
|
||||
|
||||
private TextView.OnEditorActionListener getTextViewSignInListener() {
|
||||
return new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
|
||||
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private OnClickListener getButtonLinkListener(final String url) {
|
||||
return new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
builder.setToolbarColor(getResources().getColor(R.color.colorPrimary));
|
||||
customTabsIntent.launchUrl(view.getContext(), Uri.parse(url));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void populateAutoComplete() {
|
||||
// Get the string array
|
||||
String[] countries = getResources().getStringArray(R.array.symbols);
|
||||
// Create the adapter and set it to the AutoCompleteTextView
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
|
||||
countries);
|
||||
symbolView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to sign in the account specified by the login form.
|
||||
*/
|
||||
private void attemptLogin() {
|
||||
// Reset errors.
|
||||
emailView.setError(null);
|
||||
passwordView.setError(null);
|
||||
|
||||
// Store values at the time of the login attempt.
|
||||
String email = emailView.getText().toString();
|
||||
String password = passwordView.getText().toString();
|
||||
String symbol = symbolView.getText().toString();
|
||||
|
||||
boolean cancel = false;
|
||||
View focusView = null;
|
||||
|
||||
// Check for a valid password.
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
passwordView.setError(getString(R.string.error_field_required));
|
||||
focusView = passwordView;
|
||||
cancel = true;
|
||||
} else if (!isPasswordValid(password)) {
|
||||
passwordView.setError(getString(R.string.error_invalid_password));
|
||||
focusView = passwordView;
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
// Check for a valid email address.
|
||||
if (TextUtils.isEmpty(email)) {
|
||||
emailView.setError(getString(R.string.error_field_required));
|
||||
focusView = emailView;
|
||||
cancel = true;
|
||||
} else if (!isEmailValid(email)) {
|
||||
emailView.setError(getString(R.string.error_invalid_email));
|
||||
focusView = emailView;
|
||||
cancel = true;
|
||||
}
|
||||
|
||||
// Check for a valid symbol.
|
||||
if (TextUtils.isEmpty(symbol)) {
|
||||
symbol = "Default";
|
||||
}
|
||||
|
||||
String[] keys = this.getResources().getStringArray(R.array.symbols);
|
||||
String[] values = this.getResources().getStringArray(R.array.symbols_values);
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
for (int i = 0; i < Math.min(keys.length, values.length); ++i) {
|
||||
map.put(keys[i], values[i]);
|
||||
}
|
||||
|
||||
if (map.containsKey(symbol)) {
|
||||
symbol = map.get(symbol);
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
// There was an error; don't attempt login and focus the first
|
||||
// form field with an error.
|
||||
focusView.requestFocus();
|
||||
} else {
|
||||
// Show a progress spinner, and kick off a background task to
|
||||
// perform the user login attempt.
|
||||
LoginTask authTask = new LoginTask(this, email, password, symbol);
|
||||
authTask.showProgress(true);
|
||||
authTask.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmailValid(String email) {
|
||||
return email.contains("@");
|
||||
}
|
||||
|
||||
private boolean isPasswordValid(String password) {
|
||||
return password.length() > 7;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
touchPosition = ev.getY();
|
||||
}
|
||||
if (ev.getAction() == MotionEvent.ACTION_UP) {
|
||||
float releasePosition = ev.getY();
|
||||
|
||||
if (touchPosition - releasePosition == 0) {
|
||||
View view = getCurrentFocus();
|
||||
if (view != null && (ev.getAction() == MotionEvent.ACTION_UP
|
||||
|| ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText
|
||||
&& !view.getClass().getName().startsWith("android.webkit.")) {
|
||||
|
||||
int scrcoords[] = new int[2];
|
||||
view.getLocationOnScreen(scrcoords);
|
||||
float x = ev.getRawX() + view.getLeft() - scrcoords[0];
|
||||
float y = ev.getRawY() + view.getTop() - scrcoords[1];
|
||||
if (x < view.getLeft() || x > view.getRight() || y < view.getTop()
|
||||
|| y > view.getBottom()) {
|
||||
((InputMethodManager) this.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
|
||||
(this.getWindow().getDecorView().getApplicationWindowToken()), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package io.github.wulkanowy.activity.login;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.github.wulkanowy.R;
|
||||
import io.github.wulkanowy.activity.WulkanowyApp;
|
||||
import io.github.wulkanowy.activity.dashboard.DashboardActivity;
|
||||
import io.github.wulkanowy.api.Cookies;
|
||||
import io.github.wulkanowy.api.Vulcan;
|
||||
import io.github.wulkanowy.api.login.AccountPermissionException;
|
||||
import io.github.wulkanowy.api.login.BadCredentialsException;
|
||||
import io.github.wulkanowy.api.login.Login;
|
||||
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
|
||||
import io.github.wulkanowy.dao.entities.DaoSession;
|
||||
import io.github.wulkanowy.security.CryptoException;
|
||||
import io.github.wulkanowy.services.LoginSession;
|
||||
import io.github.wulkanowy.services.VulcanSynchronization;
|
||||
import io.github.wulkanowy.services.synchronisation.AccountRegistration;
|
||||
import io.github.wulkanowy.utilities.ConnectionUtilities;
|
||||
|
||||
/**
|
||||
* Represents an asynchronous login/registration task used to authenticate
|
||||
* the user.
|
||||
*/
|
||||
public class LoginTask extends AsyncTask<Void, String, Integer> {
|
||||
|
||||
private final String email;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final String symbol;
|
||||
|
||||
private Activity activity;
|
||||
|
||||
private View progressView;
|
||||
|
||||
private View loginFormView;
|
||||
|
||||
private TextView showText;
|
||||
|
||||
public LoginTask(Activity activity, String email, String password, String symbol) {
|
||||
this.activity = activity;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
showText = activity.findViewById(R.id.login_progress_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Void... params) {
|
||||
if (ConnectionUtilities.isOnline(activity)) {
|
||||
AccountRegistration accountRegistration = new AccountRegistration(
|
||||
new Login(new Cookies()),
|
||||
new Vulcan(),
|
||||
email, password, symbol);
|
||||
|
||||
DaoSession daoSession = ((WulkanowyApp) activity.getApplication()).getDaoSession();
|
||||
|
||||
try {
|
||||
publishProgress("1", activity.getResources().getString(R.string.step_connecting));
|
||||
String certificate = accountRegistration.connect();
|
||||
|
||||
publishProgress("2", activity.getResources().getString(R.string.step_login));
|
||||
LoginSession loginSession = accountRegistration.login(activity, daoSession, certificate);
|
||||
|
||||
publishProgress("3", activity.getResources().getString(R.string.step_synchronization));
|
||||
VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(loginSession);
|
||||
vulcanSynchronization.syncSubjectsAndGrades();
|
||||
|
||||
} catch (BadCredentialsException e) {
|
||||
return R.string.login_bad_credentials_text;
|
||||
} catch (AccountPermissionException e) {
|
||||
return R.string.error_bad_account_permission;
|
||||
} catch (CryptoException e) {
|
||||
return R.string.encrypt_failed_text;
|
||||
} catch (NotLoggedInErrorException | IOException e) {
|
||||
return R.string.login_denied_text;
|
||||
}
|
||||
|
||||
accountRegistration.scheduleSynchronization(activity);
|
||||
|
||||
return R.string.login_accepted_text;
|
||||
|
||||
} else {
|
||||
return R.string.noInternet_text;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... progress) {
|
||||
showText.setText(progress[0] + "/3 - " + progress[1] + "...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Integer messageID) {
|
||||
showProgress(false);
|
||||
|
||||
switch (messageID) {
|
||||
// if success
|
||||
case R.string.login_accepted_text:
|
||||
Intent intent = new Intent(activity, DashboardActivity.class);
|
||||
activity.finish();
|
||||
activity.startActivity(intent);
|
||||
break;
|
||||
|
||||
// if bad credentials entered
|
||||
case R.string.login_bad_credentials_text:
|
||||
EditText passwordView = activity.findViewById(R.id.password);
|
||||
passwordView.setError(activity.getString(R.string.error_incorrect_password));
|
||||
passwordView.requestFocus();
|
||||
break;
|
||||
|
||||
// if no permission
|
||||
case R.string.error_bad_account_permission:
|
||||
// Change to visible symbol input view
|
||||
TextInputLayout symbolLayout = activity.findViewById(R.id.to_symbol_input_layout);
|
||||
symbolLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
EditText symbolView = activity.findViewById(R.id.symbol);
|
||||
symbolView.setError(activity.getString(R.string.error_bad_account_permission));
|
||||
symbolView.requestFocus();
|
||||
break;
|
||||
|
||||
default:
|
||||
Snackbar
|
||||
.make(activity.findViewById(R.id.coordinatorLayout), messageID, Snackbar.LENGTH_LONG)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
showProgress(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress UI and hides the login form.
|
||||
*/
|
||||
public void showProgress(final boolean show) {
|
||||
loginFormView = activity.findViewById(R.id.login_form);
|
||||
progressView = activity.findViewById(R.id.login_progress);
|
||||
|
||||
int animTime = activity.getResources().getInteger(android.R.integer.config_shortAnimTime);
|
||||
|
||||
changeLoginFormVisibility(show, animTime);
|
||||
changeProgressVisibility(show, animTime);
|
||||
}
|
||||
|
||||
private void changeLoginFormVisibility(final boolean show, final int animTime) {
|
||||
loginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||
loginFormView.animate().setDuration(animTime).alpha(
|
||||
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
loginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void changeProgressVisibility(final boolean show, final int animTime) {
|
||||
progressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
progressView.animate().setDuration(animTime).alpha(
|
||||
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
progressView.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package io.github.wulkanowy.activity.main;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.github.wulkanowy.R;
|
||||
import io.github.wulkanowy.activity.WulkanowyApp;
|
||||
import io.github.wulkanowy.activity.dashboard.DashboardActivity;
|
||||
import io.github.wulkanowy.api.Vulcan;
|
||||
import io.github.wulkanowy.api.login.AccountPermissionException;
|
||||
import io.github.wulkanowy.api.login.BadCredentialsException;
|
||||
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
|
||||
import io.github.wulkanowy.dao.entities.DaoSession;
|
||||
import io.github.wulkanowy.security.CryptoException;
|
||||
import io.github.wulkanowy.services.LoginSession;
|
||||
import io.github.wulkanowy.services.VulcanSynchronization;
|
||||
import io.github.wulkanowy.services.jobs.GradeJob;
|
||||
import io.github.wulkanowy.utilities.ConnectionUtilities;
|
||||
|
||||
public class LoginTask extends AsyncTask<String, Integer, Integer> {
|
||||
|
||||
private Activity activity;
|
||||
|
||||
private ProgressDialog progress;
|
||||
|
||||
public LoginTask(Activity activity) {
|
||||
this.activity = activity;
|
||||
this.progress = new ProgressDialog(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
|
||||
progress.setTitle(activity.getText(R.string.login_text));
|
||||
progress.setMessage(activity.getText(R.string.please_wait_text));
|
||||
progress.setCancelable(false);
|
||||
progress.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(String... credentials) {
|
||||
|
||||
if (ConnectionUtilities.isOnline(activity)) {
|
||||
VulcanSynchronization vulcanSynchronization = new VulcanSynchronization(new LoginSession());
|
||||
DaoSession daoSession = ((WulkanowyApp) activity.getApplication()).getDaoSession();
|
||||
try {
|
||||
vulcanSynchronization
|
||||
.loginNewUser(credentials[0], credentials[1], credentials[2], activity, daoSession, new Vulcan());
|
||||
} catch (BadCredentialsException e) {
|
||||
return R.string.login_bad_credentials_text;
|
||||
} catch (AccountPermissionException e) {
|
||||
return R.string.login_bad_account_permission_text;
|
||||
} catch (CryptoException e) {
|
||||
return R.string.encrypt_failed_text;
|
||||
} catch (NotLoggedInErrorException | IOException e) {
|
||||
return R.string.login_denied_text;
|
||||
}
|
||||
|
||||
vulcanSynchronization.syncSubjectsAndGrades();
|
||||
|
||||
return R.string.login_accepted_text;
|
||||
|
||||
} else {
|
||||
return R.string.noInternet_text;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(Integer messageID) {
|
||||
super.onPostExecute(messageID);
|
||||
|
||||
GradeJob gradesSync = new GradeJob();
|
||||
gradesSync.scheduledJob(activity);
|
||||
|
||||
progress.dismiss();
|
||||
|
||||
Toast.makeText(activity, activity.getString(messageID), Toast.LENGTH_LONG).show();
|
||||
|
||||
if (messageID == R.string.login_accepted_text || messageID == R.string.root_failed_text
|
||||
|| messageID == R.string.encrypt_failed_text) {
|
||||
Intent intent = new Intent(activity, DashboardActivity.class);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
package io.github.wulkanowy.activity.main;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import io.github.wulkanowy.R;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private float mTouchPosition;
|
||||
|
||||
private float mReleasePosition;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.warning_label_text)
|
||||
.setMessage(R.string.warning_text)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.show();
|
||||
|
||||
autoComplete();
|
||||
}
|
||||
|
||||
private void autoComplete() {
|
||||
|
||||
// Get a reference to the AutoCompleteTextView in the layout
|
||||
AutoCompleteTextView textView = findViewById(R.id.symbolText);
|
||||
// Get the string array
|
||||
String[] countries = getResources().getStringArray(R.array.symbols);
|
||||
// Create the adapter and set it to the AutoCompleteTextView
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
|
||||
countries);
|
||||
textView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
public void login(View a) {
|
||||
String password = ((EditText) findViewById(R.id.passwordText)).getText().toString();
|
||||
String email = ((EditText) findViewById(R.id.emailText)).getText().toString();
|
||||
String symbol = ((EditText) findViewById(R.id.symbolText)).getText().toString();
|
||||
|
||||
String[] keys = this.getResources().getStringArray(R.array.symbols);
|
||||
String[] values = this.getResources().getStringArray(R.array.symbols_values);
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
for (int i = 0; i < Math.min(keys.length, values.length); ++i) {
|
||||
map.put(keys[i], values[i]);
|
||||
}
|
||||
|
||||
if (map.containsKey(symbol)) {
|
||||
symbol = map.get(symbol);
|
||||
}
|
||||
|
||||
if (!email.isEmpty() && !password.isEmpty() && !symbol.isEmpty()) {
|
||||
new LoginTask(this).execute(email, password, symbol);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.data_text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTouchPosition = ev.getY();
|
||||
}
|
||||
if (ev.getAction() == MotionEvent.ACTION_UP) {
|
||||
mReleasePosition = ev.getY();
|
||||
|
||||
if (mTouchPosition - mReleasePosition == 0) {
|
||||
View view = getCurrentFocus();
|
||||
if (view != null && (ev.getAction() == MotionEvent.ACTION_UP
|
||||
|| ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText
|
||||
&& !view.getClass().getName().startsWith("android.webkit.")) {
|
||||
|
||||
int scrcoords[] = new int[2];
|
||||
view.getLocationOnScreen(scrcoords);
|
||||
float x = ev.getRawX() + view.getLeft() - scrcoords[0];
|
||||
float y = ev.getRawY() + view.getTop() - scrcoords[1];
|
||||
if (x < view.getLeft() || x > view.getRight() || y < view.getTop()
|
||||
|| y > view.getBottom()) {
|
||||
((InputMethodManager) this.getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
|
||||
(this.getWindow().getDecorView().getApplicationWindowToken()), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package io.github.wulkanowy.activity.started;
|
||||
package io.github.wulkanowy.activity.splash;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -7,7 +7,7 @@ import android.widget.Toast;
|
||||
|
||||
import io.github.wulkanowy.R;
|
||||
import io.github.wulkanowy.activity.dashboard.DashboardActivity;
|
||||
import io.github.wulkanowy.activity.main.MainActivity;
|
||||
import io.github.wulkanowy.activity.login.LoginActivity;
|
||||
import io.github.wulkanowy.services.jobs.GradeJob;
|
||||
import io.github.wulkanowy.utilities.ConnectionUtilities;
|
||||
|
||||
@ -39,7 +39,7 @@ public class LoadingTask extends AsyncTask<Void, Void, Boolean> {
|
||||
}
|
||||
|
||||
if (context.getSharedPreferences("LoginData", Context.MODE_PRIVATE).getLong("userId", 0) == 0) {
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
Intent intent = new Intent(context, LoginActivity.class);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
GradeJob gradesSync = new GradeJob();
|
||||
@ -49,6 +49,5 @@ public class LoadingTask extends AsyncTask<Void, Void, Boolean> {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package io.github.wulkanowy.activity.started;
|
||||
package io.github.wulkanowy.activity.splash;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
@ -7,12 +7,12 @@ import android.widget.TextView;
|
||||
import io.github.wulkanowy.BuildConfig;
|
||||
import io.github.wulkanowy.R;
|
||||
|
||||
public class StartedActivity extends AppCompatActivity {
|
||||
public class SplashActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_started);
|
||||
setContentView(R.layout.activity_splash);
|
||||
|
||||
TextView versionName = findViewById(R.id.rawText);
|
||||
versionName.setText(getText(R.string.version_text) + BuildConfig.VERSION_NAME);
|
@ -1,5 +1,6 @@
|
||||
package io.github.wulkanowy.api;
|
||||
|
||||
import org.jsoup.Connection;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
@ -8,7 +9,7 @@ import java.util.Map;
|
||||
|
||||
public abstract class Api {
|
||||
|
||||
protected Cookies cookies;
|
||||
protected Cookies cookies = new Cookies();
|
||||
|
||||
public Cookies getCookiesObject() {
|
||||
return cookies;
|
||||
@ -18,20 +19,31 @@ public abstract class Api {
|
||||
return cookies.getItems();
|
||||
}
|
||||
|
||||
public Cookies setCookies(Map<String, String> cookies) {
|
||||
this.cookies.setItems(cookies);
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
public Cookies addCookies(Map<String, String> cookies) {
|
||||
this.cookies.addItems(cookies);
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
public Document getPageByUrl(String url) throws IOException {
|
||||
return Jsoup.connect(url)
|
||||
Connection.Response response = Jsoup.connect(url)
|
||||
.followRedirects(true)
|
||||
.cookies(getCookies())
|
||||
.get();
|
||||
.execute();
|
||||
|
||||
this.cookies.addItems(response.cookies());
|
||||
|
||||
return response.parse();
|
||||
}
|
||||
|
||||
public Document postPageByUrl(String url, String[][] params) throws IOException {
|
||||
Connection connection = Jsoup.connect(url);
|
||||
|
||||
for (String[] data : params) {
|
||||
connection.data(data[0], data[1]);
|
||||
}
|
||||
|
||||
Connection.Response response = connection.cookies(getCookies())
|
||||
.followRedirects(true)
|
||||
.method(Connection.Method.POST)
|
||||
.execute();
|
||||
|
||||
this.cookies.addItems(response.cookies());
|
||||
|
||||
return response.parse();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package io.github.wulkanowy.api;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Cookies {
|
||||
|
||||
private Map<String, String> cookies;
|
||||
private Map<String, String> cookies = new HashMap<>();
|
||||
|
||||
public Map<String, String> getItems() {
|
||||
return cookies;
|
||||
|
@ -1,7 +1,5 @@
|
||||
package io.github.wulkanowy.api;
|
||||
|
||||
import org.jsoup.Connection;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
@ -55,13 +53,7 @@ public class StudentAndParent extends Api {
|
||||
}
|
||||
|
||||
public void storeContextCookies() throws IOException, NotLoggedInErrorException {
|
||||
//get context cookie
|
||||
Connection.Response res = Jsoup.connect(getSnpPageUrl())
|
||||
.followRedirects(true)
|
||||
.cookies(getCookies())
|
||||
.execute();
|
||||
|
||||
cookies.addItems(res.cookies());
|
||||
getPageByUrl(getSnpPageUrl());
|
||||
}
|
||||
|
||||
public String getSnpPageUrl() throws IOException, NotLoggedInErrorException {
|
||||
|
@ -27,17 +27,21 @@ public class Vulcan extends Api {
|
||||
|
||||
private StudentAndParent snp;
|
||||
|
||||
public void login(String email, String password, String symbol)
|
||||
throws BadCredentialsException, AccountPermissionException, LoginErrorException {
|
||||
Login login = new Login(new Cookies());
|
||||
login.login(email, password, symbol);
|
||||
|
||||
public void login(Cookies cookies, String symbol) {
|
||||
this.cookies = cookies;
|
||||
this.symbol = symbol;
|
||||
this.cookies = login.getCookiesObject();
|
||||
}
|
||||
|
||||
public void login(String email, String password, String symbol)
|
||||
throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException {
|
||||
Login login = new Login(new Cookies());
|
||||
String realSymbol = login.login(email, password, symbol);
|
||||
|
||||
login(login.getCookiesObject(), realSymbol);
|
||||
}
|
||||
|
||||
public void login(String email, String password, String symbol, String id)
|
||||
throws BadCredentialsException, AccountPermissionException, LoginErrorException {
|
||||
throws BadCredentialsException, AccountPermissionException, LoginErrorException, IOException {
|
||||
login(email, password, symbol);
|
||||
|
||||
this.id = id;
|
||||
@ -52,11 +56,7 @@ public class Vulcan extends Api {
|
||||
return snp;
|
||||
}
|
||||
|
||||
if (null == id) {
|
||||
snp = new StudentAndParent(cookies, symbol);
|
||||
} else {
|
||||
snp = new StudentAndParent(cookies, symbol, id);
|
||||
}
|
||||
snp = createSnp(cookies, symbol, id);
|
||||
|
||||
snp.storeContextCookies();
|
||||
|
||||
@ -65,6 +65,14 @@ public class Vulcan extends Api {
|
||||
return snp;
|
||||
}
|
||||
|
||||
public StudentAndParent createSnp(Cookies cookies, String symbol, String id) {
|
||||
if (null == id) {
|
||||
return new StudentAndParent(cookies, symbol);
|
||||
}
|
||||
|
||||
return new StudentAndParent(cookies, symbol, id);
|
||||
}
|
||||
|
||||
public AttendanceStatistics getAttendanceStatistics() throws IOException, NotLoggedInErrorException {
|
||||
return new AttendanceStatistics(getStudentAndParent());
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public class GradesList {
|
||||
return getAll("");
|
||||
}
|
||||
|
||||
public List<Grade> getAll(String semester) throws IOException, LoginErrorException, ParseException {
|
||||
public List<Grade> getAll(String semester) throws IOException, ParseException {
|
||||
Document gradesPage = snp.getSnPPageDocument(getGradesPageUrl() + semester);
|
||||
Elements gradesRows = gradesPage.select(".ocenySzczegoly-table > tbody > tr");
|
||||
Semester currentSemester = snp.getCurrentSemester(snp.getSemesters(gradesPage));
|
||||
|
@ -21,7 +21,7 @@ public class SubjectsList {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public List<Subject> getAll() throws IOException, LoginErrorException {
|
||||
public List<Subject> getAll() throws IOException {
|
||||
Document subjectPage = snp.getSnPPageDocument(subjectsPageUrl);
|
||||
|
||||
Elements rows = subjectPage.select(".ocenyZwykle-table > tbody > tr");
|
||||
|
@ -1,8 +1,9 @@
|
||||
package io.github.wulkanowy.api.login;
|
||||
|
||||
import org.jsoup.Connection;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.parser.Parser;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -11,12 +12,10 @@ import io.github.wulkanowy.api.Cookies;
|
||||
|
||||
public class Login extends Api {
|
||||
|
||||
private String loginPageUrl = "https://cufs.vulcan.net.pl/{symbol}/Account/LogOn";
|
||||
|
||||
private String certificatePageUrl = "https://cufs.vulcan.net.pl/{symbol}"
|
||||
+ "/FS/LS?wa=wsignin1.0&wtrealm=https://uonetplus.vulcan.net.pl/{symbol}"
|
||||
+ "/LoginEndpoint.aspx&wctx=https://uonetplus.vulcan.net.pl/{symbol}"
|
||||
+ "/LoginEndpoint.aspx";
|
||||
private String loginPageUrl = "https://cufs.vulcan.net.pl/{symbol}/Account/LogOn" +
|
||||
"?ReturnUrl=%2F{symbol}%2FFS%2FLS%3Fwa%3Dwsignin1.0%26wtrealm%3D" +
|
||||
"https%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx%26wctx%3D" +
|
||||
"https%253a%252f%252fuonetplus.vulcan.net.pl%252f{symbol}%252fLoginEndpoint.aspx";
|
||||
|
||||
private String loginEndpointPageUrl =
|
||||
"https://uonetplus.vulcan.net.pl/{symbol}/LoginEndpoint.aspx";
|
||||
@ -25,62 +24,48 @@ public class Login extends Api {
|
||||
this.cookies = cookies;
|
||||
}
|
||||
|
||||
public boolean login(String email, String password, String symbol)
|
||||
throws BadCredentialsException, LoginErrorException, AccountPermissionException {
|
||||
try {
|
||||
sendCredentials(email, password, symbol);
|
||||
String[] certificate = getCertificateData(symbol);
|
||||
sendCertificate(certificate[0], certificate[1], symbol);
|
||||
} catch (IOException e) {
|
||||
throw new LoginErrorException();
|
||||
}
|
||||
|
||||
return true;
|
||||
public String getLoginPageUrl() {
|
||||
return loginPageUrl;
|
||||
}
|
||||
|
||||
private void sendCredentials(String email, String password, String symbol)
|
||||
public String getLoginEndpointPageUrl() {
|
||||
return loginEndpointPageUrl;
|
||||
}
|
||||
|
||||
public String login(String email, String password, String symbol)
|
||||
throws BadCredentialsException, LoginErrorException, AccountPermissionException, IOException {
|
||||
String certificate = sendCredentials(email, password, symbol);
|
||||
|
||||
return sendCertificate(certificate, symbol);
|
||||
}
|
||||
|
||||
public String sendCredentials(String email, String password, String symbol)
|
||||
throws IOException, BadCredentialsException {
|
||||
loginPageUrl = loginPageUrl.replace("{symbol}", symbol);
|
||||
loginPageUrl = getLoginPageUrl().replace("{symbol}", symbol);
|
||||
|
||||
Connection.Response response = Jsoup.connect(loginPageUrl)
|
||||
.data("LoginName", email)
|
||||
.data("Password", password)
|
||||
.method(Connection.Method.POST)
|
||||
.execute();
|
||||
Document html = postPageByUrl(loginPageUrl, new String[][]{
|
||||
{"LoginName", email},
|
||||
{"Password", password}
|
||||
});
|
||||
|
||||
setCookies(response.cookies());
|
||||
Document document = response.parse();
|
||||
|
||||
if (null != document.select(".ErrorMessage").first()) {
|
||||
if (null != html.select(".ErrorMessage").first()) {
|
||||
throw new BadCredentialsException();
|
||||
}
|
||||
|
||||
return html.select("input[name=wresult]").attr("value");
|
||||
}
|
||||
|
||||
private String[] getCertificateData(String symbol) throws IOException {
|
||||
certificatePageUrl = certificatePageUrl.replace("{symbol}", symbol);
|
||||
|
||||
Document certificatePage = getPageByUrl(certificatePageUrl);
|
||||
|
||||
return new String[]{
|
||||
certificatePage.select("input[name=wa]").attr("value"),
|
||||
certificatePage.select("input[name=wresult]").attr("value")
|
||||
};
|
||||
}
|
||||
|
||||
private void sendCertificate(String protocolVersion, String certificate, String symbol)
|
||||
public String sendCertificate(String certificate, String defaultSymbol)
|
||||
throws IOException, LoginErrorException, AccountPermissionException {
|
||||
loginEndpointPageUrl = loginEndpointPageUrl.replace("{symbol}", symbol);
|
||||
String symbol = findSymbol(defaultSymbol, certificate);
|
||||
|
||||
Connection.Response response = Jsoup.connect(loginEndpointPageUrl)
|
||||
.data("wa", protocolVersion)
|
||||
.data("wresult", certificate)
|
||||
.cookies(getCookies())
|
||||
.followRedirects(true)
|
||||
.method(Connection.Method.POST)
|
||||
.execute();
|
||||
loginEndpointPageUrl = getLoginEndpointPageUrl()
|
||||
.replace("{symbol}", symbol);
|
||||
|
||||
addCookies(response.cookies());
|
||||
Document html = response.parse();
|
||||
Document html = postPageByUrl(loginEndpointPageUrl, new String[][]{
|
||||
{"wa", "wsignin1.0"},
|
||||
{"wresult", certificate}
|
||||
});
|
||||
|
||||
if (html.getElementsByTag("title").text().equals("Logowanie")) {
|
||||
throw new AccountPermissionException();
|
||||
@ -89,5 +74,26 @@ public class Login extends Api {
|
||||
if (!html.select("title").text().equals("Uonet+")) {
|
||||
throw new LoginErrorException();
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public String findSymbol(String symbol, String certificate) {
|
||||
if ("Default".equals(symbol)) {
|
||||
return findSymbolInCertificate(certificate);
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
public String findSymbolInCertificate(String certificate) {
|
||||
Elements els = Jsoup.parse(certificate.replaceAll(":", ""), "", Parser.xmlParser())
|
||||
.select("[AttributeName=\"UserInstance\"] samlAttributeValue");
|
||||
|
||||
if (0 == els.size()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return els.get(1).text();
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public class AchievementsList {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public List<String> getAllAchievements() throws LoginErrorException, IOException {
|
||||
public List<String> getAllAchievements() throws IOException {
|
||||
Element pageFragment = snp.getSnPPageDocument(notesPageUrl)
|
||||
.select(".mainContainer > div").get(1);
|
||||
Elements items = pageFragment.select("article");
|
||||
|
@ -22,7 +22,7 @@ public class NotesList {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public List<Note> getAllNotes() throws LoginErrorException, IOException {
|
||||
public List<Note> getAllNotes() throws IOException {
|
||||
Element pageFragment = snp.getSnPPageDocument(notesPageUrl)
|
||||
.select(".mainContainer > div").get(0);
|
||||
Elements items = pageFragment.select("article");
|
||||
|
@ -17,7 +17,7 @@ public class SchoolInfo {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public SchoolData getSchoolData() throws IOException, LoginErrorException {
|
||||
public SchoolData getSchoolData() throws IOException {
|
||||
Element e = snp.getSnPPageDocument(schoolPageUrl)
|
||||
.select(".mainContainer > article").get(0);
|
||||
|
||||
|
@ -3,8 +3,11 @@ package io.github.wulkanowy.api.school;
|
||||
import java.util.List;
|
||||
|
||||
public class TeachersData {
|
||||
|
||||
private String className = "";
|
||||
|
||||
private String[] classTeacher;
|
||||
|
||||
private List<Subject> subjects;
|
||||
|
||||
public String getClassName() {
|
||||
|
@ -21,7 +21,7 @@ public class TeachersInfo {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public TeachersData getTeachersData() throws IOException, LoginErrorException {
|
||||
public TeachersData getTeachersData() throws IOException {
|
||||
Document doc = snp.getSnPPageDocument(schoolPageUrl);
|
||||
Elements rows = doc.select(".mainContainer > table tbody tr");
|
||||
String description = doc.select(".mainContainer > p").first().text();
|
||||
|
@ -25,7 +25,7 @@ public class Timetable {
|
||||
return getWeekTable("");
|
||||
}
|
||||
|
||||
public Week getWeekTable(String tick) throws IOException, LoginErrorException {
|
||||
public Week getWeekTable(String tick) throws IOException {
|
||||
Element table = snp.getSnPPageDocument(timetablePageUrl + tick)
|
||||
.select(".mainContainer .presentData").first();
|
||||
|
||||
|
@ -20,7 +20,7 @@ public class BasicInformation {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public Document getStudentDataPageDocument() throws IOException, LoginErrorException {
|
||||
public Document getStudentDataPageDocument() throws IOException {
|
||||
if (null == studentDataPageDocument) {
|
||||
studentDataPageDocument = snp.getSnPPageDocument(studentDataPageUrl);
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ public class FamilyInformation {
|
||||
|
||||
private String studentDataPageUrl = "Uczen.mvc/DanePodstawowe";
|
||||
|
||||
public FamilyInformation(StudentAndParent snp) throws IOException, LoginErrorException {
|
||||
public FamilyInformation(StudentAndParent snp) {
|
||||
this.snp = snp;
|
||||
}
|
||||
|
||||
public List<FamilyMember> getFamilyMembers() throws IOException, LoginErrorException {
|
||||
public List<FamilyMember> getFamilyMembers() throws IOException {
|
||||
Elements membersElements = snp.getSnPPageDocument(studentDataPageUrl)
|
||||
.select(".mainContainer > article:nth-of-type(n+4)");
|
||||
|
||||
|
@ -27,6 +27,9 @@ public class Account {
|
||||
@Property(nameInDb = "SYMBOL")
|
||||
private String symbol;
|
||||
|
||||
@Property(nameInDb = "SNPID")
|
||||
private String snpId;
|
||||
|
||||
@ToMany(referencedJoinProperty = "userId")
|
||||
private List<Subject> subjectList;
|
||||
|
||||
@ -45,14 +48,15 @@ public class Account {
|
||||
@Generated(hash = 335469827)
|
||||
private transient AccountDao myDao;
|
||||
|
||||
@Generated(hash = 1514643300)
|
||||
public Account(Long id, String name, String email, String password,
|
||||
String symbol) {
|
||||
@Generated(hash = 735765217)
|
||||
public Account(Long id, String name, String email, String password, String symbol,
|
||||
String snpId) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.symbol = symbol;
|
||||
this.snpId = snpId;
|
||||
}
|
||||
|
||||
@Generated(hash = 882125521)
|
||||
@ -104,6 +108,15 @@ public class Account {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSnpId() {
|
||||
return this.snpId;
|
||||
}
|
||||
|
||||
public Account setSnpId(String snpId) {
|
||||
this.snpId = snpId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* To-many relationship, resolved on first access (and after reset).
|
||||
* Changes to to-many relations are not persisted, make changes to the target entity.
|
||||
|
@ -355,9 +355,7 @@ public class Grade implements Parcelable {
|
||||
myDao.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* called by internal mechanisms, do not call yourself.
|
||||
*/
|
||||
/** called by internal mechanisms, do not call yourself. */
|
||||
@Generated(hash = 1187286414)
|
||||
public void __setDaoSession(DaoSession daoSession) {
|
||||
this.daoSession = daoSession;
|
||||
|
@ -9,11 +9,10 @@ import io.github.wulkanowy.api.Vulcan;
|
||||
import io.github.wulkanowy.api.login.AccountPermissionException;
|
||||
import io.github.wulkanowy.api.login.BadCredentialsException;
|
||||
import io.github.wulkanowy.api.login.LoginErrorException;
|
||||
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
|
||||
import io.github.wulkanowy.dao.entities.DaoSession;
|
||||
import io.github.wulkanowy.security.CryptoException;
|
||||
import io.github.wulkanowy.services.jobs.VulcanJobHelper;
|
||||
import io.github.wulkanowy.services.synchronisation.AccountSynchronisation;
|
||||
import io.github.wulkanowy.services.synchronisation.AccountAuthorization;
|
||||
import io.github.wulkanowy.services.synchronisation.GradesSynchronisation;
|
||||
import io.github.wulkanowy.services.synchronisation.SubjectsSynchronisation;
|
||||
|
||||
@ -26,18 +25,10 @@ public class VulcanSynchronization {
|
||||
}
|
||||
|
||||
public void loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan)
|
||||
throws CryptoException, BadCredentialsException, AccountPermissionException, IOException, LoginErrorException {
|
||||
throws CryptoException, BadCredentialsException, AccountPermissionException, LoginErrorException, IOException {
|
||||
|
||||
AccountSynchronisation accountSynchronisation = new AccountSynchronisation();
|
||||
loginSession = accountSynchronisation.loginCurrentUser(context, daoSession, vulcan);
|
||||
}
|
||||
|
||||
public void loginNewUser(String email, String password, String symbol,
|
||||
Context context, DaoSession daoSession, Vulcan vulcan)
|
||||
throws BadCredentialsException, NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException {
|
||||
|
||||
AccountSynchronisation accountSynchronisation = new AccountSynchronisation();
|
||||
loginSession = accountSynchronisation.loginNewUser(email, password, symbol, context, daoSession, vulcan);
|
||||
AccountAuthorization accountAuthorization = new AccountAuthorization(context, daoSession, vulcan);
|
||||
loginSession = accountAuthorization.loginCurrentUser();
|
||||
}
|
||||
|
||||
public boolean syncGrades() {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package io.github.wulkanowy.services.synchronisation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -10,8 +9,6 @@ import io.github.wulkanowy.api.Vulcan;
|
||||
import io.github.wulkanowy.api.login.AccountPermissionException;
|
||||
import io.github.wulkanowy.api.login.BadCredentialsException;
|
||||
import io.github.wulkanowy.api.login.LoginErrorException;
|
||||
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
|
||||
import io.github.wulkanowy.api.user.PersonalData;
|
||||
import io.github.wulkanowy.dao.entities.Account;
|
||||
import io.github.wulkanowy.dao.entities.AccountDao;
|
||||
import io.github.wulkanowy.dao.entities.DaoSession;
|
||||
@ -20,9 +17,21 @@ import io.github.wulkanowy.security.Safety;
|
||||
import io.github.wulkanowy.services.LoginSession;
|
||||
import io.github.wulkanowy.services.jobs.VulcanJobHelper;
|
||||
|
||||
public class AccountSynchronisation {
|
||||
public class AccountAuthorization {
|
||||
|
||||
public LoginSession loginCurrentUser(Context context, DaoSession daoSession, Vulcan vulcan) throws CryptoException,
|
||||
private final Context context;
|
||||
|
||||
private final DaoSession daoSession;
|
||||
|
||||
private final Vulcan vulcan;
|
||||
|
||||
public AccountAuthorization(Context context, DaoSession daoSession, Vulcan vulcan) {
|
||||
this.context = context;
|
||||
this.daoSession = daoSession;
|
||||
this.vulcan = vulcan;
|
||||
}
|
||||
|
||||
public LoginSession loginCurrentUser() throws CryptoException,
|
||||
BadCredentialsException, AccountPermissionException, IOException, LoginErrorException {
|
||||
|
||||
AccountDao accountDao = daoSession.getAccountDao();
|
||||
@ -38,7 +47,8 @@ public class AccountSynchronisation {
|
||||
vulcan.login(
|
||||
account.getEmail(),
|
||||
safety.decrypt(account.getEmail(), account.getPassword()),
|
||||
account.getSymbol()
|
||||
account.getSymbol(),
|
||||
account.getSnpId()
|
||||
);
|
||||
|
||||
return new LoginSession()
|
||||
@ -50,36 +60,4 @@ public class AccountSynchronisation {
|
||||
throw new IOException("Can't find user with index 0");
|
||||
}
|
||||
}
|
||||
|
||||
public LoginSession loginNewUser(String email, String password, String symbol, Context context, DaoSession daoSession, Vulcan vulcan)
|
||||
throws BadCredentialsException, NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException {
|
||||
|
||||
long userId;
|
||||
|
||||
vulcan.login(email, password, symbol);
|
||||
|
||||
PersonalData personalData = vulcan.getBasicInformation().getPersonalData();
|
||||
AccountDao accountDao = daoSession.getAccountDao();
|
||||
Safety safety = new Safety();
|
||||
|
||||
Account account = new Account()
|
||||
.setName(personalData.getFirstAndLastName())
|
||||
.setEmail(email)
|
||||
.setPassword(safety.encrypt(email, password, context))
|
||||
.setSymbol(symbol);
|
||||
|
||||
userId = accountDao.insert(account);
|
||||
|
||||
Log.d(VulcanJobHelper.DEBUG_TAG, "Login and save new user id=" + String.valueOf(userId));
|
||||
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putLong("userId", userId);
|
||||
editor.apply();
|
||||
|
||||
return new LoginSession()
|
||||
.setVulcan(vulcan)
|
||||
.setUserId(userId)
|
||||
.setDaoSession(daoSession);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package io.github.wulkanowy.services.synchronisation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.github.wulkanowy.api.Vulcan;
|
||||
import io.github.wulkanowy.api.login.AccountPermissionException;
|
||||
import io.github.wulkanowy.api.login.BadCredentialsException;
|
||||
import io.github.wulkanowy.api.login.Login;
|
||||
import io.github.wulkanowy.api.login.NotLoggedInErrorException;
|
||||
import io.github.wulkanowy.dao.entities.Account;
|
||||
import io.github.wulkanowy.dao.entities.AccountDao;
|
||||
import io.github.wulkanowy.dao.entities.DaoSession;
|
||||
import io.github.wulkanowy.security.CryptoException;
|
||||
import io.github.wulkanowy.security.Safety;
|
||||
import io.github.wulkanowy.services.LoginSession;
|
||||
import io.github.wulkanowy.services.jobs.GradeJob;
|
||||
|
||||
public class AccountRegistration {
|
||||
|
||||
|
||||
private final Login login;
|
||||
|
||||
private final Vulcan vulcan;
|
||||
|
||||
private final String email;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final String symbol;
|
||||
|
||||
public AccountRegistration(Login login, Vulcan vulcan, String email, String password, String symbol) {
|
||||
this.login = login;
|
||||
this.vulcan = vulcan;
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
public String connect()
|
||||
throws BadCredentialsException, IOException {
|
||||
return login.sendCredentials(email, password, symbol);
|
||||
}
|
||||
|
||||
public LoginSession login(Context context, DaoSession daoSession, String certificate)
|
||||
throws NotLoggedInErrorException, AccountPermissionException, IOException, CryptoException {
|
||||
|
||||
long userId;
|
||||
|
||||
String realSymbol = login.sendCertificate(certificate, symbol);
|
||||
|
||||
vulcan.login(login.getCookiesObject(), realSymbol);
|
||||
|
||||
AccountDao accountDao = daoSession.getAccountDao();
|
||||
Safety safety = new Safety();
|
||||
Account account = new Account()
|
||||
.setName(vulcan.getBasicInformation().getPersonalData().getFirstAndLastName())
|
||||
.setEmail(email)
|
||||
.setPassword(safety.encrypt(email, password, context))
|
||||
.setSymbol(vulcan.getStudentAndParent().getSymbol())
|
||||
.setSnpId(vulcan.getStudentAndParent().getId());
|
||||
|
||||
userId = accountDao.insert(account);
|
||||
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("LoginData", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putLong("userId", userId);
|
||||
editor.apply();
|
||||
|
||||
return new LoginSession()
|
||||
.setVulcan(vulcan)
|
||||
.setUserId(userId)
|
||||
.setDaoSession(daoSession);
|
||||
}
|
||||
|
||||
public void scheduleSynchronization(Context context) {
|
||||
GradeJob gradesSync = new GradeJob();
|
||||
gradesSync.scheduledJob(context);
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ import io.github.wulkanowy.utilities.ConversionVulcanObject;
|
||||
public class SubjectsSynchronisation {
|
||||
|
||||
public void sync(LoginSession loginSession) throws IOException,
|
||||
ParseException, NotLoggedInErrorException {
|
||||
NotLoggedInErrorException {
|
||||
|
||||
SubjectsList subjectsList = loginSession.getVulcan().getSubjectsList();
|
||||
SubjectDao subjectDao = loginSession.getDaoSession().getSubjectDao();
|
||||
|
12
app/src/main/res/drawable/ic_arrow_down.xml
Normal file
12
app/src/main/res/drawable/ic_arrow_down.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="35dp"
|
||||
android:height="35dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
|
||||
<path android:pathData="M0-0.75h24v24H0z" />
|
||||
</vector>
|
155
app/src/main/res/layout/activity_login.xml
Normal file
155
app/src/main/res/layout/activity_login.xml
Normal file
@ -0,0 +1,155 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:id="@+id/coordinatorLayout"
|
||||
tools:context="io.github.wulkanowy.activity.login.LoginActivity"
|
||||
>
|
||||
|
||||
<!-- Login progress -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/login_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="gone"
|
||||
>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/login_progress_horizontal"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="91dp"
|
||||
android:minHeight="30dp"
|
||||
android:indeterminate="true"
|
||||
android:minWidth="220dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/login_progress_horizontal"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="42dp"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/login_form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/email_login_form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/login_heading"
|
||||
android:layout_marginBottom="24dp"
|
||||
/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/to_email_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_email"
|
||||
android:inputType="textEmailAddress"
|
||||
android:maxLines="1"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/to_password_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_password"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
android:imeActionLabel="@string/action_sign_in"
|
||||
android:imeOptions="actionDone"
|
||||
android:fontFamily="sans-serif"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/to_symbol_input_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
>
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/symbol"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_symbol"
|
||||
android:inputType="textAutoComplete"
|
||||
android:maxLines="1"
|
||||
android:imeActionLabel="@string/action_sign_in"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="noExcludeDescendants"
|
||||
/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/action_sign_in"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/action_sign_in"
|
||||
android:textStyle="bold"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/action_create_account"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_create_account"
|
||||
android:layout_marginBottom="24dp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/action_forgot_password"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_forgot_password"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="15dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="40sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/emailText"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:ems="10"
|
||||
android:hint="@string/email_hint_text"
|
||||
android:inputType="textEmailAddress" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/passwordText"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:ems="10"
|
||||
android:fontFamily="sans-serif"
|
||||
android:hint="@string/pass_hint_text"
|
||||
android:inputType="textPassword"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/symbolText"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="10dp"
|
||||
android:hint="@string/symbol_hint_text"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="text" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/agreeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:onClick="login"
|
||||
android:text="@string/login_button_text" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -1,18 +1,28 @@
|
||||
<resources>
|
||||
<string name="app_name">Wulkanowy</string>
|
||||
<string name="login_text">Logowanie</string>
|
||||
<string name="pass_hint_text">Hasło</string>
|
||||
<string name="email_hint_text">E-mail</string>
|
||||
<string name="login_button_text">Zaloguj</string>
|
||||
<string name="symbol_hint_text">Symbol</string>
|
||||
<string name="warning_text">Aplikacja ta nie jest ukończona, więc mogą występować różnego rodzaju błędy lub dane funkcje nie bedą działać. Prosimy o cierpliwość i wyrozumiałość.</string>
|
||||
<string name="warning_label_text">Ostrzeżenie</string>
|
||||
<string name="data_text">Brak danych logowania</string>
|
||||
|
||||
<!-- Strings related to login -->
|
||||
<string name="title_activity_login">Zaloguj się</string>
|
||||
<string name="login_heading">Zaloguj się za pomocą konta VULCAN</string>
|
||||
<string name="prompt_email">Email</string>
|
||||
<string name="prompt_password">Hasło</string>
|
||||
<string name="prompt_symbol">Symbol</string>
|
||||
<string name="action_sign_in">Zaloguj</string>
|
||||
<string name="step_connecting">Łączenie z dziennikiem</string>
|
||||
<string name="step_login">Logowanie</string>
|
||||
<string name="step_synchronization">Synchronizacja</string>
|
||||
<string name="error_invalid_email">Ten adres email nie jest poprawny</string>
|
||||
<string name="error_invalid_password">To hasło jest za krótkie</string>
|
||||
<string name="error_incorrect_password">To hasło jest niepoprawne</string>
|
||||
<string name="error_field_required">To pole jest wymagane</string>
|
||||
<string name="error_bad_account_permission">Brak dostępu do dziennika. Sprawdź inny symbol</string>
|
||||
<string name="login_accepted_text">Pomyślnie zalogowano</string>
|
||||
<string name="login_bad_credentials_text">Niepoprawny e-mail lub hasło</string>
|
||||
<string name="login_bad_account_permission_text">Brak uprawnień do otwarcia dziennika. Sprawdź wprowadzoną nazwę powiatu</string>
|
||||
<string name="login_denied_text">Logowanie nie powiodło się</string>
|
||||
<string name="please_wait_text">Proszę czekać…</string>
|
||||
<string name="login_denied_text">Logowanie nie powiodło się. Spróbuj zrestartować aplikację</string>
|
||||
<string name="action_create_account">Nie masz jeszcze konta? Załóż je</string>
|
||||
<string name="action_forgot_password">Zapomniałeś hasła?</string>
|
||||
|
||||
<string name="activity_dashboard_text">Aktywność dashboard</string>
|
||||
<string name="dashboard_text">Dashboard</string>
|
||||
<string name="grades_text">Oceny</string>
|
||||
|
@ -1,3 +1,7 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
|
@ -1,18 +1,28 @@
|
||||
<resources>
|
||||
<string name="app_name">Wulkanowy</string>
|
||||
<string name="login_text">Login</string>
|
||||
<string name="pass_hint_text">Password</string>
|
||||
<string name="email_hint_text">E-mail</string>
|
||||
<string name="login_button_text">Log in</string>
|
||||
<string name="symbol_hint_text">Symbol</string>
|
||||
<string name="warning_text">This application is not complete, so there may be a variety of errors or features that will not work. Please be patient and understanding.</string>
|
||||
<string name="warning_label_text">Warning</string>
|
||||
<string name="data_text">No login data</string>
|
||||
|
||||
<!-- Strings related to login -->
|
||||
<string name="title_activity_login">Sign in</string>
|
||||
<string name="login_heading">Sign in with VULCAN account</string>
|
||||
<string name="prompt_email">Email</string>
|
||||
<string name="prompt_password">Password</string>
|
||||
<string name="prompt_symbol">Symbol</string>
|
||||
<string name="action_sign_in">Sign in</string>
|
||||
<string name="step_connecting">Connecting with log</string>
|
||||
<string name="step_login">Login</string>
|
||||
<string name="step_synchronization">Synchronization</string>
|
||||
<string name="error_invalid_email">This email address is invalid</string>
|
||||
<string name="error_invalid_password">This password is too short</string>
|
||||
<string name="error_incorrect_password">This password is incorrect</string>
|
||||
<string name="error_field_required">This field is required</string>
|
||||
<string name="error_bad_account_permission">No permission to open log. Check another symbol</string>
|
||||
<string name="login_accepted_text">Login is successful</string>
|
||||
<string name="login_bad_credentials_text">Bad e-mail or password</string>
|
||||
<string name="login_bad_account_permission_text">No permission to open log. Check entered symbol</string>
|
||||
<string name="login_denied_text">Login is failed</string>
|
||||
<string name="please_wait_text">Please wait…</string>
|
||||
<string name="login_denied_text">Login is failed. Try restart the app</string>
|
||||
<string name="action_create_account">No account yet? Create one</string>
|
||||
<string name="action_forgot_password">Forgot password?</string>
|
||||
|
||||
<string name="activity_dashboard_text">Dashboard Activity</string>
|
||||
<string name="dashboard_text">Dashboard</string>
|
||||
<string name="grades_text">Grades</string>
|
||||
|
Reference in New Issue
Block a user