[Login/Librus] Implement Librus JST login form.

This commit is contained in:
Kuba Szczodrzyński 2019-12-14 17:21:29 +01:00
parent 13279a915d
commit ad5afac174
13 changed files with 408 additions and 12 deletions

View File

@ -148,16 +148,16 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
*/
private var mApiCode: String? = null
var apiCode: String?
get() { mApiCode = mApiCode ?: profile?.getStudentData("accountCode", null); return mApiCode }
set(value) { profile?.putStudentData("accountCode", value) ?: return; mApiCode = value }
get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode }
set(value) { loginStore.putLoginData("accountCode", value) ?: return; mApiCode = value }
/**
* A JST login PIN.
* Used only during first login in JST mode.
*/
private var mApiPin: String? = null
var apiPin: String?
get() { mApiPin = mApiPin ?: profile?.getStudentData("accountPin", null); return mApiPin }
set(value) { profile?.putStudentData("accountPin", value) ?: return; mApiPin = value }
get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin }
set(value) { loginStore.putLoginData("accountPin", value) ?: return; mApiPin = value }
/**
* A Synergia API access token.
@ -168,7 +168,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiAccessToken: String? = null
var apiAccessToken: String?
get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken }
set(value) { profile?.putStudentData("accountToken", value) ?: return; mApiAccessToken = value }
set(value) { mApiAccessToken = value; profile?.putStudentData("accountToken", value) ?: return; }
/**
* A Synergia API refresh token.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
@ -176,7 +176,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiRefreshToken: String? = null
var apiRefreshToken: String?
get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken }
set(value) { profile?.putStudentData("accountRefreshToken", value) ?: return; mApiRefreshToken = value }
set(value) { mApiRefreshToken = value; profile?.putStudentData("accountRefreshToken", value) ?: return; }
/**
* The expiry time for [apiAccessToken], as a UNIX timestamp.
* Used when refreshing the [apiAccessToken] in JST, Synergia modes.
@ -185,7 +185,7 @@ class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app
private var mApiTokenExpiryTime: Long? = null
var apiTokenExpiryTime: Long
get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L }
set(value) { profile?.putStudentData("accountTokenTime", value) ?: return; mApiTokenExpiryTime = value }
set(value) { mApiTokenExpiryTime = value; profile?.putStudentData("accountTokenTime", value) ?: return; }
/* _____ _
/ ____| (_)

View File

@ -85,6 +85,36 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
LibrusLoginApi(data) {
api.apiGet(TAG, "Me") { json ->
val profile = Profile()
val me = json.getJsonObject("Me")
val account = me?.getJsonObject("Account")
val user = me?.getJsonObject("User")
profile.putStudentData("isPremium", account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true)
val isParent = account?.getInt("GroupId") == 5
profile.accountNameLong =
if (isParent)
buildFullName(account?.getString("FirstName"), account?.getString("LastName"))
else null
profile.studentNameLong =
buildFullName(user?.getString("FirstName"), user?.getString("LastName"))
profile.studentNameShort = profile.studentNameLong?.getShortName()
profile.name = profile.studentNameLong
profile.subname = account.getString("Login")
profile.empty = true
profile.putStudentData("accountId", account.getInt("Id") ?: 0)
profile.putStudentData("accountLogin", profile.subname)
profile.putStudentData("accountToken", data.apiAccessToken)
profile.putStudentData("accountRefreshToken", data.apiRefreshToken)
profile.putStudentData("accountTokenTime", data.apiTokenExpiryTime)
profileList.add(profile)
EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore))
onSuccess()
}
}
}

View File

@ -108,7 +108,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
AttendanceType.class,
pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
ConfigEntry.class,
Metadata.class}, version = 68)
Metadata.class}, version = 69)
@TypeConverters({
ConverterTime.class,
ConverterDate.class,
@ -823,6 +823,12 @@ public abstract class AppDb extends RoomDatabase {
database.execSQL("DELETE FROM metadata WHERE thingType=7");
}
};
private static final Migration MIGRATION_68_69 = new Migration(68, 69) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE loginStores ADD COLUMN loginStoreMode INTEGER NOT NULL DEFAULT 0");
}
};
public static AppDb getDatabase(final Context context) {
@ -888,7 +894,8 @@ public abstract class AppDb extends RoomDatabase {
MIGRATION_64_65,
MIGRATION_65_66,
MIGRATION_66_67,
MIGRATION_67_68
MIGRATION_67_68,
MIGRATION_68_69
)
.allowMainThreadQueries()
//.fallbackToDestructiveMigration()

View File

@ -5,7 +5,6 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
@ -28,7 +27,7 @@ public class LoginStore {
@ColumnInfo(name = "loginStoreData")
public JsonObject data;
@Ignore
@ColumnInfo(name = "loginStoreMode")
public int mode = 0;
public static final int LOGIN_MODE_LIBRUS_EMAIL = 0;
public static final int LOGIN_MODE_LIBRUS_SYNERGIA = 1;

View File

@ -51,6 +51,7 @@ public class LoginChooserFragment extends Fragment {
b.loginMobidziennikLogo.setOnClickListener((v) -> nav.navigate(R.id.loginMobidziennikFragment, null, LoginActivity.navOptions));
b.loginLibrusLogo.setOnClickListener((v) -> nav.navigate(R.id.loginLibrusFragment, null, LoginActivity.navOptions));
b.loginLibrusJstLogo.setOnClickListener((v) -> nav.navigate(R.id.loginLibrusJstFragment, null, LoginActivity.navOptions));
b.loginVulcanLogo.setOnClickListener((v) -> nav.navigate(R.id.loginVulcanFragment, null, LoginActivity.navOptions));
b.loginIuczniowieLogo.setOnClickListener((v) -> nav.navigate(R.id.loginIuczniowieFragment, null, LoginActivity.navOptions));

View File

@ -0,0 +1,121 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-13.
*/
package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusJstBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_MODE_LIBRUS_JST;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
public class LoginLibrusJstFragment extends Fragment {
private App app;
private NavController nav;
private FragmentLoginLibrusJstBinding b;
private static final String TAG = "LoginLibrus";
private ErrorSnackbar errorSnackbar;
public LoginLibrusJstFragment() { }
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
if (getActivity() != null) {
app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
}
else {
return null;
}
b = DataBindingUtil.inflate(inflater, R.layout.fragment_login_librus_jst, container, false);
return b.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
assert getContext() != null;
assert getActivity() != null;
view.postDelayed(() -> {
ApiError error = LoginActivity.error;
if (error != null) {
switch (error.getErrorCode()) {
case ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN:
b.loginCodeLayout.setError(getString(R.string.login_error_incorrect_code_or_pin));
break;
}
errorSnackbar.addError(error).show();
LoginActivity.error = null;
}
}, 100);
b.helpButton.setOnClickListener((v) -> nav.navigate(R.id.loginLibrusHelpFragment, null, LoginActivity.navOptions));
b.backButton.setOnClickListener((v) -> nav.navigateUp());
b.loginButton.setOnClickListener((v) -> {
boolean errors = false;
b.loginCodeLayout.setError(null);
b.loginPinLayout.setError(null);
Editable codeEditable = b.loginCode.getText();
Editable pinEditable = b.loginPin.getText();
if (codeEditable == null || codeEditable.length() == 0) {
b.loginCodeLayout.setError(getString(R.string.login_error_no_code));
errors = true;
}
if (pinEditable == null || pinEditable.length() == 0) {
b.loginPinLayout.setError(getString(R.string.login_error_no_pin));
errors = true;
}
if (errors)
return;
errors = false;
String code = codeEditable.toString().toUpperCase();
String pin = pinEditable.toString();
b.loginCode.setText(code);
if (!code.matches("[A-Z0-9_]+")) {
b.loginCodeLayout.setError(getString(R.string.login_error_incorrect_code));
errors = true;
}
if (!pin.matches("[a-z0-9_]+")) {
b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin));
errors = true;
}
if (errors)
return;
errors = false;
Bundle args = new Bundle();
args.putInt("loginType", LOGIN_TYPE_LIBRUS);
args.putInt("loginMode", LOGIN_MODE_LIBRUS_JST);
args.putString("accountCode", code);
args.putString("accountPin", pin);
nav.navigate(R.id.loginProgressFragment, args, LoginActivity.navOptions);
});
}
}

View File

@ -85,8 +85,10 @@ public class LoginProgressFragment extends Fragment {
}
int loginType = args.getInt("loginType", -1);
int loginMode = args.getInt("loginMode", 0);
LoginStore loginStore = new LoginStore(-1, loginType, new JsonObject());
loginStore.mode = loginMode;
loginStore.copyFrom(args);
if (App.devMode && LoginChooserFragment.fakeLogin) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -152,6 +152,54 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:baselineAligned="false"
android:orientation="horizontal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_weight="1">
<ImageView
android:id="@+id/loginLibrusJstLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_oswiata" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:visibility="invisible">
<!--<ImageView
android:id="@+id/loginIuczniowieLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:background="@drawable/bg_rounded_ripple"
android:padding="16dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/login_logo_iuczniowie" />-->
</FrameLayout>
</LinearLayout>
<Switch
android:id="@+id/fakeLogin"
android:layout_width="match_parent"

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.mikepenz.iconics.view.IconicsImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="48dp"
app:iiv_color="@color/colorPrimary"
app:iiv_icon="cmd-account-circle"
app:iiv_size="32dp"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="16dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_jst_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginTop="2dp"
android:layout_marginRight="24dp"
android:text="@string/login_librus_jst_subtitle" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/login_code_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_token"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/login_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPinLayout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/login_hint_pin"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/loginPin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/helpButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="@string/login_help_button"
android:textAllCaps="false"
android:visibility="gone"/>
<Space
android:layout_width="0.0dip"
android:layout_height="0.0dip"
android:layout_weight="1.0" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/login_button"
android:textAllCaps="false" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/backButton"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="@string/back"
android:textAllCaps="false"
android:layout_marginStart="8dp" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -15,6 +15,9 @@
<action
android:id="@+id/action_loginTypeFragment_to_loginLibrusFragment"
app:destination="@id/loginLibrusFragment" />
<action
android:id="@+id/action_loginTypeFragment_to_loginLibrusJstFragment"
app:destination="@id/loginLibrusJstFragment" />
<action
android:id="@+id/action_loginTypeFragment_to_loginVulcanFragment"
app:destination="@id/loginVulcanFragment" />
@ -46,6 +49,15 @@
android:id="@+id/action_loginLibrusFragment_to_loginProgressFragment"
app:destination="@id/loginProgressFragment" />
</fragment>
<fragment
android:id="@+id/loginLibrusJstFragment"
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginLibrusJstFragment"
android:label="fragment_login_librus_jst"
tools:layout="@layout/fragment_login_librus_jst" >
<action
android:id="@+id/action_loginLibrusJstFragment_to_loginProgressFragment"
app:destination="@id/loginProgressFragment" />
</fragment>
<fragment
android:id="@+id/loginProgressFragment"
android:name="pl.szczodrzynski.edziennik.ui.modules.login.LoginProgressFragment"

View File

@ -349,7 +349,7 @@
<string name="login_librus_help_subtitle">In order to use the app you need a Librus account. You can create it on portal.librus.pl. Use the data you normally enter in the fields marked on the image.</string>
<string name="login_librus_help_title">Help - Librus</string>
<string name="login_librus_subtitle">Log in using your Librus account data (created before on your e-mail address). You can\'t login using the data provided by your school. In case of any problems, use the button below the form.</string>
<string name="login_librus_title">Log in - Librus</string>
<string name="login_librus_jst_title">Log in - Librus JST</string>
<string name="login_migration_error_format">A migration error has occurred. It\'s already reported, which means I\'ll try to fix it.\n\nYou can continue using the app. In case of any problems try to remove the profile and create it again.</string>
<string name="login_migration_subtitle">The app got a huge update. If you encounter any problems, feel free to contact me using the Feedback option in the menu.</string>
<string name="login_migration_title">Szkolny.eu has been updated</string>

View File

@ -373,6 +373,7 @@
<string name="login_librus_help_title">Pomoc - Librus</string>
<string name="login_librus_subtitle">Zaloguj się danymi swojego konta Librus (założonego wcześniej na swój adres e-mail). Nie należy logować się danymi otrzymanymi ze swojej szkoły. W razie problemów skorzystaj z przycisku pod formularzem.</string>
<string name="login_librus_title">Zaloguj się - Librus</string>
<string name="login_librus_jst_title">Zaloguj się - Librus JST</string>
<string name="login_migration_error_format">Wystąpił błąd w migracji danych. Został on już zgłoszony, co oznacza, że postaram się go naprawić.\n\nMożesz zacząć korzystać z aplikacji, jednak w przypadku jakichś błędów spróbuj usunąć profil i zalogować się ponownie.</string>
<string name="login_migration_subtitle">Aplikacja otrzymała dużą aktualizację. Jeżeli będą występować jakieś problemy w działaniu, możesz się ze mną skontaktować używając pozycji Pomoc i opinie w menu.</string>
<string name="login_migration_title">Szkolny.eu został zaktualizowany</string>
@ -1061,4 +1062,9 @@
<string name="edziennik_progress_endpoint_behaviour_grades">Pobieranie ocen z zachowania...</string>
<string name="edziennik_szkolny_api_sync_title">Synchronizowanie udostępnionych wydarzeń...</string>
<string name="settings_theme_open_drawer_on_back_pressed_text">Otwieraj menu przyciskiem wstecz</string>
<string name="login_librus_jst_subtitle">Zaloguj się tokenem i kodem PIN, który można wygenerować po zalogowanu się do Synergii w zakładce Aplikacje mobilne.</string>
<string name="login_error_incorrect_token_or_pin">Nieprawidłowy token lub PIN</string>
<string name="login_error_incorrect_code_or_pin">Nieprawidłowy kod lub PIN</string>
<string name="login_error_incorrect_code">Nieprawidłowy kod</string>
<string name="login_error_no_code">Podaj kod</string>
</resources>