diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2115831..9948868 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,15 +24,6 @@ - - - \ No newline at end of file diff --git a/app/src/main/java/de/sebse/fuplanner/MainActivity.java b/app/src/main/java/de/sebse/fuplanner/MainActivity.java index a4929d3..6108cc6 100644 --- a/app/src/main/java/de/sebse/fuplanner/MainActivity.java +++ b/app/src/main/java/de/sebse/fuplanner/MainActivity.java @@ -33,16 +33,20 @@ import de.sebse.fuplanner.fragments.canteen.DaySwitcherFragment; import de.sebse.fuplanner.fragments.moddetails.ModDetailFragment; import de.sebse.fuplanner.services.Canteen.CanteenBrowser; import de.sebse.fuplanner.services.Canteen.types.Canteen; +import de.sebse.fuplanner.services.GoogleAuth.Credentials; import de.sebse.fuplanner.services.GoogleAuth.GoogleAuth; -import de.sebse.fuplanner.services.KVV.KVV; +import de.sebse.fuplanner.services.NewKVV.KVV; import de.sebse.fuplanner.services.KVV.types.LoginToken; -import de.sebse.fuplanner.services.KVV.types.Modules; +import de.sebse.fuplanner.services.NewKVV.KVVListener; import de.sebse.fuplanner.tools.MainActivityListener; import de.sebse.fuplanner.tools.RequestPermissionsResultListener; import de.sebse.fuplanner.tools.logging.Logger; +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkError; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; public class MainActivity extends AppCompatActivity - implements MainActivityListener, + implements MainActivityListener, KVVListener, NavigationView.OnNavigationItemSelectedListener, LoginFragment.OnLoginFragmentInteractionListener, ModulesFragment.OnModulesFragmentInteractionListener, @@ -63,26 +67,25 @@ public class MainActivity extends AppCompatActivity private FragmentManager mFragmentManager; private GoogleAuth mGoogleAuth; - private KVV mKVV; + private de.sebse.fuplanner.services.KVV.KVV mKVV; + private KVV mNewKVV; private final Logger log = new Logger(this); private NavigationView mNavigationView; private int fragmentPage = FRAGMENT_NONE; - private int currentPage = FRAGMENT_NONE; private String fragmentData = ""; - private String currentData = ""; private CanteenBrowser mCanteenBrowser; - private boolean mOfflineMode = false; private HashMap permissionListeners = new HashMap<>(); + private boolean mOfflineBanner; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - int newFragmentPage = FRAGMENT_NONE; - String newFragmentData = ""; + int desiredPage = getDefaultFragmentAfterLogin(); + String desiredData = ""; if (savedInstanceState != null) { - newFragmentPage = savedInstanceState.getInt(ARG_FRAGMENT_PAGE, fragmentPage); - newFragmentData = savedInstanceState.getString(ARG_FRAGMENT_STATUS, fragmentData); + desiredPage = savedInstanceState.getInt(ARG_FRAGMENT_PAGE, desiredPage); + desiredData = savedInstanceState.getString(ARG_FRAGMENT_STATUS, desiredData); } setContentView(R.layout.activity_main); @@ -99,15 +102,9 @@ public class MainActivity extends AppCompatActivity mNavigationView.setNavigationItemSelectedListener(this); mFragmentManager = getSupportFragmentManager(); - LoginToken loginToken = getKVV().easyLogin(); - if (loginToken == null) { - checkAndDoLogin(); - } else { - if (newFragmentPage != FRAGMENT_LOGIN && newFragmentPage != FRAGMENT_STARTUP && newFragmentPage != FRAGMENT_NONE) - toLoginState(loginToken, newFragmentPage, newFragmentData); - else - toLoginState(loginToken, getDefaultFragmentAfterLogin(), ""); - } + getNewKVV().account().doOfflineLogin(); + updateNavigation(); + changeFragment(desiredPage, desiredData); } @Override @@ -123,7 +120,7 @@ public class MainActivity extends AppCompatActivity @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. - if (currentPage == FRAGMENT_SCHEDULE) { + if (fragmentPage == FRAGMENT_SCHEDULE) { getMenuInflater().inflate(R.menu.options_schedule, menu); return true; } @@ -180,13 +177,13 @@ public class MainActivity extends AppCompatActivity startActivity(sendIntent); break; case R.id.nav_logout: - this.getKVV().logout(); + getNewKVV().account().logout(true); this.getGoogleAuth().getLoginState(credentials -> { if (credentials != null) { this.getGoogleAuth().deleteLoginState(credentials.getUsername(), credentials.getPassword()); } - this.toLogoutState(); }); + this.toLogoutState(); break; } @@ -210,13 +207,14 @@ public class MainActivity extends AppCompatActivity @Override protected void onSaveInstanceState(Bundle savedInstanceState) { - Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(fragmentPage)); - savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage); - savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData); - if (fragment instanceof ModDetailFragment) { - savedInstanceState.putString(ARG_FRAGMENT_STATUS, ((ModDetailFragment) fragment).getData()); - } else { - savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData); + if (fragmentPage != FRAGMENT_STARTUP && fragmentPage != FRAGMENT_NONE && fragmentPage != FRAGMENT_LOGIN) { + Fragment fragment = mFragmentManager.findFragmentByTag(String.valueOf(fragmentPage)); + savedInstanceState.putInt(ARG_FRAGMENT_PAGE, fragmentPage); + if (fragment instanceof ModDetailFragment) { + savedInstanceState.putString(ARG_FRAGMENT_STATUS, ((ModDetailFragment) fragment).getData()); + } else { + savedInstanceState.putString(ARG_FRAGMENT_STATUS, fragmentData); + } } super.onSaveInstanceState(savedInstanceState); } @@ -239,13 +237,21 @@ public class MainActivity extends AppCompatActivity return this.mGoogleAuth; } - public KVV getKVV() { + @Deprecated + public de.sebse.fuplanner.services.KVV.KVV getKVV() { if (this.mKVV == null) { - this.mKVV = new KVV(this); + this.mKVV = new de.sebse.fuplanner.services.KVV.KVV(this); } return this.mKVV; } + public KVV getNewKVV() { + if (this.mNewKVV == null) { + this.mNewKVV = new KVV(this, this); + } + return this.mNewKVV; + } + public CanteenBrowser getCanteenBrowser() { if (this.mCanteenBrowser == null) { this.mCanteenBrowser = new CanteenBrowser(this); @@ -254,23 +260,13 @@ public class MainActivity extends AppCompatActivity } private int getDefaultFragmentAfterLogin() { - return getDefaultFragmentAfterLogin(new String[1]); - } - - private int getDefaultFragmentAfterLogin(String[] id) { - if (fragmentPage == FRAGMENT_NONE){ - id[0] = ""; - return FRAGMENT_MODULES; - } - else { - id[0] = fragmentData; - return fragmentPage; - } + return FRAGMENT_MODULES; } private void toLogoutState() { - setOfflineBanner(true); + setOfflineBanner(false); setRefreshFailedBanner(false); + updateNavigation(); changeFragment(FRAGMENT_LOGIN); } @@ -283,14 +279,17 @@ public class MainActivity extends AppCompatActivity } private void toLoginState(String fullName, String email, int newFragment, String newData, boolean onlineMode) { - setOfflineBanner(onlineMode); - changeFragment(newFragment, newData); + setOfflineBanner(!onlineMode); + updateNavigation(); View header = mNavigationView.getHeaderView(0); ((TextView) header.findViewById(R.id.login_name)).setText(fullName); ((TextView) header.findViewById(R.id.login_mail)).setText(email); + + changeFragment(newFragment, newData); } + @Deprecated private void checkAndDoLogin() { changeFragment(FRAGMENT_STARTUP); getGoogleAuth().getLoginState(credentials -> { @@ -298,10 +297,9 @@ public class MainActivity extends AppCompatActivity toLogoutState(); return; } - String[] id = {""}; - int fragment = getDefaultFragmentAfterLogin(id); - this.getKVV().login(credentials.getUsername(), credentials.getPassword(), success -> toLoginState(success, fragment , id[0]), + int fragment = getDefaultFragmentAfterLogin(); + this.getKVV().login(credentials.getUsername(), credentials.getPassword(), success -> toLoginState(success, fragment, ""), error -> { log.e(error); toLogoutState(); @@ -355,56 +353,38 @@ public class MainActivity extends AppCompatActivity fragmentTransaction.commit(); if (newFragment == FRAGMENT_STARTUP) { - findViewById(R.id.app_bar_layout).setVisibility(View.GONE); + findViewById(R.id.app_bar_include).setVisibility(View.GONE); } else { - findViewById(R.id.app_bar_layout).setVisibility(View.VISIBLE); + findViewById(R.id.app_bar_include).setVisibility(View.VISIBLE); } - boolean isChangeLoginState = - ( - (newFragment == FRAGMENT_STARTUP || newFragment == FRAGMENT_LOGIN) && - (currentPage != FRAGMENT_STARTUP && currentPage != FRAGMENT_LOGIN) - ) || ( - (currentPage == FRAGMENT_STARTUP || currentPage == FRAGMENT_LOGIN || currentPage == FRAGMENT_NONE) && - (newFragment != FRAGMENT_STARTUP && newFragment != FRAGMENT_LOGIN && (getKVV().isLoggedIn() || mOfflineMode)) - ); + this.fragmentPage = newFragment; + this.fragmentData = newData; - if (newFragment != FRAGMENT_STARTUP && newFragment != FRAGMENT_NONE && newFragment != FRAGMENT_LOGIN) { - this.fragmentPage = newFragment; - this.fragmentData = newData; - } - this.currentPage = newFragment; - this.currentData = newData; invalidateOptionsMenu(); - if (isChangeLoginState) - refreshNavigation(); - else - setNavigationSelection(currentPage, currentData); + //TODO navigation selection } - private void setOfflineBanner(boolean onlineMode) { + private void setOfflineBanner(boolean visible) { View offline_header = findViewById(R.id.offline_msg); - if (onlineMode) - offline_header.setVisibility(View.GONE); - else - offline_header.setVisibility(View.VISIBLE); - mOfflineMode = !onlineMode; + offline_header.setVisibility(visible ? View.VISIBLE : View.GONE); + mOfflineBanner = visible; } private void setRefreshFailedBanner(boolean refreshFailed) { View viewNoConnection = findViewById(R.id.no_connection_msg); - if (!mOfflineMode && refreshFailed) + if (!mOfflineBanner && refreshFailed) viewNoConnection.setVisibility(View.VISIBLE); else viewNoConnection.setVisibility(View.GONE); } - private void setNavigationSelection(int fragment, String data) { + private void setNavigationSelection() { MenuItem item; - switch (fragment) { + switch (fragmentPage) { case FRAGMENT_MODULES_DETAILS: - getKVV().getModule(data, success -> { + getKVV().getModule(fragmentData, success -> { int size = mNavigationView.getMenu().size(); //noinspection ConstantConditions String title = success == null ? null : success.title; @@ -426,7 +406,7 @@ public class MainActivity extends AppCompatActivity case FRAGMENT_CANTEENS_DETAILS: getCanteenBrowser().getCanteens(success -> { int size = mNavigationView.getMenu().size(); - Canteen canteen = success.getCanteen(Integer.parseInt(data)); + Canteen canteen = success.getCanteen(Integer.parseInt(fragmentData)); //noinspection ConstantConditions String title = canteen == null ? null : canteen.getName(); for (int k = 0; k < size; k++) { @@ -466,10 +446,10 @@ public class MainActivity extends AppCompatActivity private void afterAnyMenuInflate(boolean isLoggedIn) { if (isLoggedIn) { - getKVV().getModuleList(success -> { + getNewKVV().modules().list().recv(success -> { int i = 0; - for (Iterator it = success.latestSemesterIterator(); it.hasNext(); ) { - Modules.Module module = it.next(); + for (Iterator it = success.latestSemesterIterator(); it.hasNext(); ) { + de.sebse.fuplanner.services.NewKVV.types.Modules.Module module = it.next(); MenuItem menuItem = mNavigationView.getMenu().add(Menu.NONE, Menu.NONE, 101 + i, module.title); menuItem.setOnMenuItemClickListener(item -> { onModulesFragmentInteraction(module.getID()); @@ -492,16 +472,30 @@ public class MainActivity extends AppCompatActivity }, log::e); } + private void updateNavigation() { + boolean isLoggedIn = getNewKVV().account().isLoggedIn(); + setNavigationHeader(isLoggedIn); + mNavigationView.getMenu().clear(); + if (isLoggedIn) + mNavigationView.inflateMenu(R.menu.activity_main_drawer_login); + else + mNavigationView.inflateMenu(R.menu.activity_main_drawer); + afterAnyMenuInflate(isLoggedIn); + setNavigationSelection(); + } + + + + @Override public void onLoginFragmentInteraction(LoginToken loginToken, boolean onlineMode) { - String[] id = {""}; - int fragment = getDefaultFragmentAfterLogin(id); - toLoginState(loginToken.getFullName(), loginToken.getEmail(), fragment, id[0], onlineMode); + int fragment = getDefaultFragmentAfterLogin(); + toLoginState(loginToken.getFullName(), loginToken.getEmail(), fragment, "", onlineMode); } @Override @@ -524,6 +518,7 @@ public class MainActivity extends AppCompatActivity setTitle(titleId); } + @Deprecated @Override public void loginTokenInvalid(boolean doLoginCheck) { if (doLoginCheck) { @@ -564,16 +559,33 @@ public class MainActivity extends AppCompatActivity Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } + + + + + + + + + @Override - public void refreshNavigation() { - boolean isLoggedIn = getKVV().isLoggedIn() || mOfflineMode; - setNavigationHeader(isLoggedIn); - mNavigationView.getMenu().clear(); - if (isLoggedIn) - mNavigationView.inflateMenu(R.menu.activity_main_drawer_login); - else - mNavigationView.inflateMenu(R.menu.activity_main_drawer); - afterAnyMenuInflate(isLoggedIn); - setNavigationSelection(currentPage, currentData); + public void getCredentials(NetworkCallback callback, NetworkErrorCallback error) { + getGoogleAuth().getLoginState(credentials -> { + if (credentials == null || credentials.getUsername() == null || credentials.getPassword() == null) { + error.onError(new NetworkError(200100, 403, "No Google Login available!")); + } else { + callback.onResponse(credentials); + } + }); + } + + @Override + public void handleLogin(de.sebse.fuplanner.services.NewKVV.types.LoginToken token, boolean enteringOnlineMode) { + toLoginState(token.getUsername(), token.getEmail(), getDefaultFragmentAfterLogin(), "", enteringOnlineMode); + } + + @Override + public void handleLogout() { + toLogoutState(); } } diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAnnounceFragment.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAnnounceFragment.java index 66ea74e..52f3713 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAnnounceFragment.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAnnounceFragment.java @@ -117,9 +117,9 @@ public class ModDetailAnnounceFragment extends Fragment implements Download.OnDo super.onAttach(context); if (context instanceof MainActivityListener) { this.context = ((MainActivityListener) context); - this.context.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailResourceFragment"); + this.context.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailAnnounceFragment"); } else - throw new RuntimeException(context.toString() + " must implement MainActivityListener"); + throw new RuntimeException(context.toString() + " must implement ModDetailAnnounceFragment"); } @Override diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAssignmentFragment.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAssignmentFragment.java index 48e6f52..a3225d9 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAssignmentFragment.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailAssignmentFragment.java @@ -117,7 +117,7 @@ public class ModDetailAssignmentFragment extends Fragment implements Download.On super.onAttach(context); if (context instanceof MainActivityListener) { this.context = ((MainActivityListener) context); - this.context.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailResourceFragment"); + this.context.addRequestPermissionsResultListener(getDownload().getRequestPermissionsResultListener(), "ModDetailAssignmentFragment"); } else throw new RuntimeException(context.toString() + " must implement MainActivityListener"); } @@ -125,7 +125,7 @@ public class ModDetailAssignmentFragment extends Fragment implements Download.On @Override public void onDetach() { super.onDetach(); - this.context.removeRequestPermissionsResultListener("ModDetailResourceFragment"); + this.context.removeRequestPermissionsResultListener("ModDetailAssignmentFragment"); } Download getDownload() { diff --git a/app/src/main/java/de/sebse/fuplanner/services/GoogleAuth/GoogleAuth.java b/app/src/main/java/de/sebse/fuplanner/services/GoogleAuth/GoogleAuth.java index ead95a8..98074e8 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/GoogleAuth/GoogleAuth.java +++ b/app/src/main/java/de/sebse/fuplanner/services/GoogleAuth/GoogleAuth.java @@ -29,6 +29,7 @@ public class GoogleAuth { private static final String TAG = "GoogleAuth"; private final FragmentActivity activity; + private static final String FU_PLANNER_PROVIDER = "FUPlanner"; private CredentialsClient mCredentialsClient; private boolean mIsResolving; @Nullable @@ -61,6 +62,7 @@ public class GoogleAuth { connect(); CredentialRequest request = new CredentialRequest.Builder() .setPasswordLoginSupported(true) + .setAccountTypes(FU_PLANNER_PROVIDER) .build(); diff --git a/app/src/main/java/de/sebse/fuplanner/services/KVV/Download.java b/app/src/main/java/de/sebse/fuplanner/services/KVV/Download.java index 4c03d45..8fcfb83 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/KVV/Download.java +++ b/app/src/main/java/de/sebse/fuplanner/services/KVV/Download.java @@ -15,7 +15,6 @@ import java.util.Arrays; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; -import androidx.core.content.FileProvider; import de.sebse.fuplanner.MainActivity; import de.sebse.fuplanner.R; import de.sebse.fuplanner.services.KVV.types.Resource; @@ -24,10 +23,7 @@ import de.sebse.fuplanner.tools.RequestPermissionsResultListener; import de.sebse.fuplanner.tools.UtilsDate; import de.sebse.fuplanner.tools.logging.Logger; -import static android.content.Intent.normalizeMimeType; -import static androidx.core.app.ActivityCompat.startActivityForResult; import static androidx.core.content.ContextCompat.checkSelfPermission; -import static androidx.core.content.ContextCompat.startActivity; public class Download { @@ -35,7 +31,6 @@ public class Download { private final ActivityInterface activityInterface; private RequestedDownload requestedDownload; private Logger log = new Logger(this); - static final int REQUEST_IMAGE_OPEN = 1; public Download(ContextInterface contextInterface, ActivityInterface activityInterface) { @@ -60,7 +55,7 @@ public class Download { if (file.getModifiedDate() != 0) { if (!message.isEmpty()) message += "\n"; - message += resources.getString(R.string.last_modified_on, UtilsDate.getModifiedDateTime(contextInterface.get(), file.getModifiedDate())); + resources.getString(R.string.last_modified_on, UtilsDate.getModifiedDateTime(contextInterface.get(), file.getModifiedDate())); } alertDialogBuilder @@ -186,17 +181,9 @@ public class Download { } private void fileOpen(File url){ + Uri uri = Uri.fromFile(url); - Uri uri = FileProvider.getUriForFile(contextInterface.get(), contextInterface.get().getApplicationContext().getPackageName() + ".my.provider", url); - - Intent intent; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - intent = new Intent(Intent.ACTION_VIEW); - } else { - intent = new Intent(); - } - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + Intent intent = new Intent();//Intent.ACTION_VIEW // Check what kind of file you are trying to open, by comparing the url with extensions. // When the if condition is matched, plugin sets the correct intent (mime) type, // so Android knew what application to use to open the file @@ -235,18 +222,13 @@ public class Download { intent.setDataAndType(uri, "video/*"); } else { //if you want you can also define the intent type for any other file + //additionally use else clause below, to manage other unknown extensions //in this case, Android will show all applications installed on the device //so you can choose which application to use intent.setDataAndType(uri, "*/*"); } - //intent.addCategory(Intent.CATEGORY_OPENABLE); - // Only the system receives the ACTION_OPEN_DOCUMENT, so no need to test. - - - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); contextInterface.get().startActivity(intent); diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVV.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVV.java new file mode 100644 index 0000000..ab63ebe --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVV.java @@ -0,0 +1,47 @@ +package de.sebse.fuplanner.services.NewKVV; + +import android.content.Context; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; + +public class KVV { + private final HashMap addons = new HashMap<>(); + private final KVVListener mListener; + private final Context mContext; + + public KVV(KVVListener listener, Context context) { + this.mListener = listener; + this.mContext = context; + } + + @NotNull + public KVVLogin account() { + return (KVVLogin) addAndGet("account", () -> new KVVLogin(mListener, mContext)); + } + + @NotNull + public KVVModules modules() { + return (KVVModules) addAndGet("module", () -> new KVVModules(account(), mContext)); + } + + + + + + + @NotNull + private Object addAndGet(@NotNull String addon, @NotNull ModuleCreatorInterface creatorInterface) { + Object o = addons.get(addon); + if (o == null) { + o = creatorInterface.create(); + addons.put(addon, o); + } + return o; + } + + private interface ModuleCreatorInterface { + @NotNull Object create(); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVListener.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVListener.java new file mode 100644 index 0000000..2dd7a9b --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVListener.java @@ -0,0 +1,14 @@ +package de.sebse.fuplanner.services.NewKVV; + +import de.sebse.fuplanner.services.GoogleAuth.Credentials; +import de.sebse.fuplanner.services.NewKVV.types.LoginToken; +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; + +public interface KVVListener { + void getCredentials(NetworkCallback callback, NetworkErrorCallback error); + + void handleLogin(LoginToken token, boolean enteringOnlineMode); + + void handleLogout(); +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVLogin.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVLogin.java new file mode 100644 index 0000000..29d4b42 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVLogin.java @@ -0,0 +1,433 @@ +package de.sebse.fuplanner.services.NewKVV; + +import android.content.Context; + + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import androidx.annotation.Nullable; +import de.sebse.fuplanner.services.NewKVV.types.LoginToken; +import de.sebse.fuplanner.tools.network.HTTPService; +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkError; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; + +public class KVVLogin extends HTTPService { + private KVVListener mListener; + @Nullable private LoginToken mToken; + private boolean mLoginPending = false; + private boolean mOnlineMode = false; + + KVVLogin(KVVListener listener, Context context) { + super(context); + this.mListener = listener; + } + + public void doOnlineLogin(String username, String password, NetworkCallback callback, NetworkErrorCallback errorCallback) { + if (mLoginPending) { + errorCallback.onError(new NetworkError(100160, -1, "Login already pending!")); + } + if (mToken != null) { + errorCallback.onError(new NetworkError(100161, -1, "Already logged in!")); + } + mLoginPending = true; + doLogin(username, password, token -> { + testLoginToken(token2 -> { + setToken(token, true); + mLoginPending = false; + callback.onResponse(token); + }, error -> { + mLoginPending = false; + errorCallback.onError(error); + }); + }, error -> { + mLoginPending = false; + errorCallback.onError(error); + }); + } + + public boolean doOfflineLogin() { + if (mLoginPending || mToken != null) + return false; + mLoginPending = true; + boolean result = false; + try { + result = setToken(LoginToken.load(getContext()), false); + } catch (FileNotFoundException ignored) { + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + mLoginPending = false; + return result; + } + + public boolean isOfflineStoredAvailable() { + try { + LoginToken.load(getContext()); + return true; + } catch (FileNotFoundException ignored) { + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return false; + } + + public boolean logout(boolean delete) { + if (mLoginPending) + return false; + if (mToken == null) + return true; + if (delete) + mToken.delete(getContext()); + mToken = null; + return handleCallbacks(); + } + + public boolean isLoginPending() { + return mLoginPending; + } + + public boolean isLoggedIn() { + return mToken != null; + } + + public boolean isInOfflineMode() { + return isLoggedIn() && !mOnlineMode; + } + + public boolean isInOnlineMode() { + return isLoggedIn() && mOnlineMode; + } + + public void testLoginToken(NetworkCallback callback, NetworkErrorCallback errorCallback) { + if (mToken == null) { + errorCallback.onError(new NetworkError(100173, -1, "Not logged in!")); + return; + } + get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", mToken.getUsername()), mToken.getCookies(), response -> { + String body = response.getParsed(); + if (body == null) { + errorCallback.onError(new NetworkError(100172, 403, "Testing login failed!")); + return; + } + try { + JSONObject json = new JSONObject(body); + String displayName = json.getString("displayName"); + String email = json.getString("email"); + mToken.setAdditionals(displayName, email); + callback.onResponse(mToken); + } catch (JSONException e) { + errorCallback.onError(new NetworkError(100171, 403, "Cannot parse profile!")); + } + }, error -> errorCallback.onError(new NetworkError(100170, error.networkResponse.statusCode, "Testing login failed!"))); + } + + @Nullable public LoginToken getLoginToken() { + return mToken; + } + + void refreshLogin(NetworkCallback success, NetworkErrorCallback error) { + mListener.getCredentials(credentials -> { + doOnlineLogin(credentials.getUsername(), credentials.getPassword(), success, error); + }, e -> { + logout(false); + error.onError(e); + }); + } + + + + private boolean handleCallbacks() { + if (mToken != null) { + mListener.handleLogin(mToken, false); + return true; + } else { + mListener.handleLogout(); + return false; + } + } + + private boolean setToken(@Nullable LoginToken token, boolean enteringOnlineMode) { + if (token == null) + return false; + mToken = token; + if (enteringOnlineMode) { + try { + mToken.save(getContext()); + } catch (IOException e) { + e.printStackTrace(); + } + } + mOnlineMode = !enteringOnlineMode; + return handleCallbacks(); + } + + + + + + + + + + + private void doLogin(String username, String password, NetworkCallback callback, NetworkErrorCallback error) { + startKVVSession(success -> { + String kvvJSESSIONID = success.get("JSESSIONID"); + getSAMLRequest(kvvJSESSIONID, success1 -> startIdentSession(success1.get("Location"), success11 -> { + String identJSESSIONID = success11.get("JSESSIONID"); + String ident_idp_authn_lc_key = success11.get("_idp_authn_lc_key"); + String identROUTEID = success11.get("ROUTEID"); + loginIdent(true, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success111 -> loginIdent(false, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success11112 -> { + String ident_idp_session = success11112.get("_idp_session"); + getSAMLResponse(identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, ident_idp_session, success1111 -> loginKVV(success1111.get("RelayState"), success1111.get("SAMLResponse"), kvvJSESSIONID, success111112 -> { + LoginToken token = new LoginToken(username, success111112.get("shibsessionKey"), success111112.get("shibsessionName"), kvvJSESSIONID); + finishKVVlogin(token, success11111 -> callback.onResponse(token), error); + }, error), error); + }, error), error); + }, error), error); + }, error); + } + + /* + GET https://kvv.imp.fu-berlin.de/portal/login + -> JSESSIONID 5c10406f-588c-4c16-96e9-c80d115417de.tomcat1 + */ + private void startKVVSession(final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + get("https://kvv.imp.fu-berlin.de/portal/login", null, response -> { + String cookies = response.getHeaders().get("Set-Cookie"); + if (cookies==null) { + errorCallback.onError(new NetworkError(100101, -1, "Error on starting KVV session!")); + return; + } + HashMap object; + try { + object = getCookie(cookies, new String[]{"JSESSIONID"}); + } catch (NoSuchFieldException e) { + errorCallback.onError(new NetworkError(100102, -1, "Error on starting KVV session!")); + return; + } + callback.onResponse(object); + }, error -> errorCallback.onError(new NetworkError(100100, error.networkResponse.statusCode, "Error on starting KVV session!"))); + } + + /* + GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container + <- JSESSIONID + -> (Location-Header) https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO + ?SAMLRequest=fZLLb.....Q8yre3X1IHwkJKE0Mnpy/V9TH4A + &RelayState=ss:mem:7ea01e29157b8bd906f7002176.....0d1a505f2c8bf + */ + private void getSAMLRequest(String JSESSIONID, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + HashMap cookies = new HashMap<>(); + cookies.put("JSESSIONID", JSESSIONID); + get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", cookies, response -> { + String location = response.getHeaders().get("Location"); + if (location==null) { + errorCallback.onError(new NetworkError(100111, -1, "Error on getting SAML request!")); + return; + } + HashMap object = new HashMap<>(); + object.put("Location", location); + callback.onResponse(object); + }, error -> errorCallback.onError(new NetworkError(100110, error.networkResponse.statusCode, "Error on getting SAML request!"))); + } + + /* + GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO + ?SAMLRequest=fZLLbsIwEEV/JfI+cWJAUIsgpbAoEi2IpF10UznxUKw6dupxaPn7hkdb2LD29bkzRzNGUeuGZ63fmjV8toA++K61QX58SEnrDLcCFXIjakDuK55njwvOopg3znpbWU2CDBGcV9ZMrcG2BpeD26kKnteLlGy9b5BT+rHbRapuok0bluC0MpEEmm9VWVoNfhshWnpgM7pa5gUJZt0wyogD9h+iJBiv/P6aomQTbtqSdhNtlIYzZg1SOag8zfMlCeazlLyNqpHsy1gO2V1fVsNBMuqJoUyAJaxXDUaiiyG2MDfohfEpYXEyDJM4ZKxgCe/FPI5fSbA6L36vjFTm/bal8hRC/lAUq/C02gs4PK7VBchkfHDNj8Xuwv5trPhVTiY3BeOf4DG96DmVNvypA89nK6tVtQ8yre3X1IHwkJKE0Mnpy/V9TH4A + &RelayState=ss:mem:7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf + -> JSESSIONID C4B6A428BA1F50746235D03F5D107A57 + -> _idp_authn_lc_key 57a6ae26067f374cc3d0ccfc47e27b04b47752d2a3d4eb2782af0d3994535395 + -> ROUTEID .1 + */ + private void startIdentSession(String url, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + get(url, null, response -> { + String cookies = response.getHeaders().get("Set-Cookie"); + if (cookies==null) { + errorCallback.onError(new NetworkError(100121, -1, "Error on starting Ident session!")); + return; + } + HashMap object; + try { + object = getCookie(cookies, new String[]{"JSESSIONID", "_idp_authn_lc_key", "ROUTEID"}); + } catch (NoSuchFieldException e) { + errorCallback.onError(new NetworkError(100122, -1, "Error on starting Ident session!")); + return; + } + callback.onResponse(object); + }, error -> errorCallback.onError(new NetworkError(100120, error.networkResponse.statusCode, "Error on starting Ident session!"))); + } + + /* + POST https://identity.fu-berlin.de/idp-fub/Authn/UserPassword + <- j_username seedorf96 + <- j_password neinhieristpatrick + <- (Header-"Content-Type") application/x-www-form-urlencoded + <- JSESSIONID + <- _idp_authn_lc_key + <- ROUTEID + -> _idp_session OTMuMTkzLjg1LjMz|LQ==|OGYxOWI4MjA2NTQ4YWUwYzJkOWM4Mjk4YzcwZDMwZmJiZjBmMTdmMzkyZGU2OWIwY2JkNmZlNjlmNTRmNzBlMQ==|wLlzQal7VqyntmG2vLNn06wt8wQ= + */ + private void loginIdent(final boolean first, String username, String password, String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + HashMap cookies = new HashMap<>(); + cookies.put("JSESSIONID", JSESSIONID); + cookies.put("_idp_authn_lc_key", _idp_authn_lc_key); + cookies.put("ROUTEID", ROUTEID); + HashMap body = new HashMap<>(); + body.put("j_username", username); + body.put("j_password", password); + post("https://identity.fu-berlin.de/idp-fub/Authn/UserPassword", cookies, body, response -> { + if (first) { + callback.onResponse(new HashMap<>()); + return; + } + + String cookies1 = response.getHeaders().get("Set-Cookie"); + if (cookies1 ==null) { + errorCallback.onError(new NetworkError(100131, -1, "Error on logging in to Identity Server!")); + return; + } + HashMap object; + try { + object = getCookie(cookies1, new String[]{"_idp_session"}); + } catch (NoSuchFieldException e) { + errorCallback.onError(new NetworkError(100132, -1, "Error on logging in to Identity Server!")); + return; + } + callback.onResponse(object); + }, error -> errorCallback.onError(new NetworkError(100130, error.networkResponse.statusCode, "Error on logging in to Identity Server!"))); + } + + /* + GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO + <- JSESSIONID + <- _idp_authn_lc_key + <- ROUTEID + <- _idp_session + -> (BODY) RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf + -> (BODY) SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg== + */ + private void getSAMLResponse(String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, String _idp_session, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + HashMap cookies = new HashMap<>(); + cookies.put("JSESSIONID", JSESSIONID); + cookies.put("_idp_authn_lc_key", _idp_authn_lc_key); + cookies.put("ROUTEID", ROUTEID); + cookies.put("_idp_session", _idp_session); + get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> { + String body = response.getParsed(); + if (body == null) { + errorCallback.onError(new NetworkError(100143, -1, "Error on getting SAML response!")); + return; + } + + HashMap object = new HashMap<>(); + + Pattern pattern = Pattern.compile("ss:mem:([0-9a-f]+)"); + Matcher matcher = pattern.matcher(body); + if (!matcher.find()) { + errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!")); + return; + } + object.put("RelayState", "ss:mem:"+matcher.group(1)); + + pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)"); + matcher = pattern.matcher(body); + if (!matcher.find()) { + errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!")); + return; + } + object.put("SAMLResponse", matcher.group(1)); + + callback.onResponse(object); + }, error -> errorCallback.onError(new NetworkError(100140, error.networkResponse.statusCode, "Error on getting SAML response!"))); + } + + + /* + POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST + <- RelayState 7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf + <- SAMLResponse PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg== + <- JSESSIONID + -> _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468 + _b1912c5a03d733a80bd3fee772bf68d4 + */ + private void loginKVV(String RelayState, String SAMLResponse, String JSESSIONID, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + HashMap cookies = new HashMap<>(); + cookies.put("JSESSIONID", JSESSIONID); + HashMap body = new HashMap<>(); + body.put("RelayState", RelayState); + body.put("SAMLResponse", SAMLResponse); + post("https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST", cookies, body, response -> { + String cookies1 = response.getHeaders().get("Set-Cookie"); + if (cookies1 ==null) { + errorCallback.onError(new NetworkError(100151, -1, "Error on starting KVV session!")); + return; + } + HashMap object = new HashMap<>(); + + + Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);"); + Matcher matcher = pattern.matcher(cookies1); + if (!matcher.find()) { + errorCallback.onError(new NetworkError(100152, -1, "Error on starting Ident session!")); + } + object.put("shibsessionKey", matcher.group(1)); + object.put("shibsessionName", matcher.group(2)); + + callback.onResponse(object); + }, error -> errorCallback.onError(new NetworkError(100150, error.networkResponse.statusCode, "Error on starting Ident session!"))); + } + + + /* + GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container + <- JSESSIONID + <- _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468 + _b1912c5a03d733a80bd3fee772bf68d4 + */ + private void finishKVVlogin(LoginToken loginToken, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", loginToken.getCookies(), response -> callback.onResponse(new HashMap<>()), error -> errorCallback.onError(new NetworkError(100160, error.networkResponse.statusCode, "Cannot finish login process!"))); + } + + + + + + + + + + + + + private String getCookie(String cookies, String name) throws NoSuchFieldException { + Pattern pattern = Pattern.compile(name+"=([^;]+);"); + Matcher matcher = pattern.matcher(cookies); + if (!matcher.find()) { + log.e("GETcookie failed", name); + log.e("GETcookie failed", cookies); + throw new NoSuchFieldException(); + } + return matcher.group(1); + } + + private HashMap getCookie(String cookies, String[] names) throws NoSuchFieldException { + HashMap result = new HashMap<>(); + for (String name: names) { + result.put(name,this.getCookie(cookies, name)); + } + return result; + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModules.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModules.java new file mode 100644 index 0000000..546a057 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModules.java @@ -0,0 +1,43 @@ +package de.sebse.fuplanner.services.NewKVV; + +import android.content.Context; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; + +public class KVVModules { + private final HashMap addons = new HashMap<>(); + private final KVVLogin login; + private final Context context; + + public KVVModules(KVVLogin login, Context context) { + this.login = login; + this.context = context; + } + + @NotNull + public KVVModulesAnnouncements announcements() { + return (KVVModulesAnnouncements) addAndGet("announcements", () -> new KVVModulesAnnouncements(login, list(), context)); + } + + @NotNull + public KVVModulesList list() { + return (KVVModulesList) addAndGet("list", () -> new KVVModulesList(login, context)); + } + + + @NotNull + private Object addAndGet(@NotNull String addon, @NotNull ModuleCreatorInterface creatorInterface) { + Object o = addons.get(addon); + if (o == null) { + o = creatorInterface.create(); + addons.put(addon, o); + } + return o; + } + + private interface ModuleCreatorInterface { + @NotNull Object create(); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModulesAnnouncements.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModulesAnnouncements.java new file mode 100644 index 0000000..90e60c2 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModulesAnnouncements.java @@ -0,0 +1,88 @@ +package de.sebse.fuplanner.services.NewKVV; + +import android.content.Context; +import android.os.Build; +import android.text.Html; +import android.text.Spanned; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +import de.sebse.fuplanner.services.NewKVV.types.Announcement; +import de.sebse.fuplanner.services.NewKVV.types.Modules; +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkError; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; + +class KVVModulesAnnouncements extends ModulesPart> { + + KVVModulesAnnouncements(KVVLogin login, KVVModulesList list, Context context) { + super(login, list, context); + } + + @Override + protected ArrayList getPart(Modules.Module module) { + return module.announcements; + } + + @Override + protected boolean setPart(Modules.Module module, ArrayList part) { + boolean changed = false; + if (module.announcements != null && module.announcements.hashCode() != part.hashCode()) + changed = true; + module.announcements = part; + return changed; + } + + @Override + protected void upgrade(final String ID, final NetworkCallback> callback, final NetworkErrorCallback errorCallback) { + if (!login.isInOnlineMode()) { + errorCallback.onError(new NetworkError(101204, 500, "Currently running in offline mode!")); + return; + } + super.get(String.format("https://kvv.imp.fu-berlin.de/direct/announcement/site/%s.json?n=999999&d=999999999", ID), login.getLoginToken().getCookies(), response -> { + String body = response.getParsed(); + if (body == null) { + errorCallback.onError(new NetworkError(101201, 403, "No announcements retrieved!")); + return; + } + ArrayList announcements = new ArrayList<>(); + try { + JSONObject json = new JSONObject(body); + JSONArray sites = json.getJSONArray("announcement_collection"); + + for (int i = 0; i < sites.length(); i++) { + JSONObject site = sites.getJSONObject(i); + String id = site.getString("announcementId"); + String title = site.getString("title"); + String text = site.getString("body"); + text = String.valueOf(fromHtml(text)); + String createdBy = site.getString("createdByDisplayName"); + long createdOn = site.getLong("createdOn"); + + // Extract attachment links + JSONArray attachments = site.getJSONArray("attachments"); + ArrayList urls = new ArrayList<>(); + for (int j =0; j check + if (announcements.size() == 0) + login.testLoginToken(token -> callback.onResponse(announcements), errorCallback); + else + callback.onResponse(announcements); + }, error -> errorCallback.onError(new NetworkError(101203, error.networkResponse.statusCode, "Cannot get announcements!"))); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModulesList.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModulesList.java new file mode 100644 index 0000000..18947e0 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/KVVModulesList.java @@ -0,0 +1,161 @@ +package de.sebse.fuplanner.services.NewKVV; + +import android.content.Context; + +import org.jetbrains.annotations.Nullable; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashSet; +import java.util.regex.MatchResult; + +import de.sebse.fuplanner.services.NewKVV.types.Lecturer; +import de.sebse.fuplanner.services.NewKVV.types.Modules; +import de.sebse.fuplanner.tools.NewAsyncQueue; +import de.sebse.fuplanner.tools.Regex; +import de.sebse.fuplanner.tools.network.HTTPService; +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkError; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; + +import static de.sebse.fuplanner.services.NewKVV.ModulesPart.RETRY_COUNT; + +public class KVVModulesList extends HTTPService { + protected final KVVLogin login; + @Nullable private Modules modules; + private NewAsyncQueue queue = new NewAsyncQueue(); + + KVVModulesList(KVVLogin login, Context context) { + super(context); + this.login = login; + restore(); + } + + public void find(String moduleID, NetworkCallback moduleNetworkCallback, NetworkErrorCallback errorCallback) { + find(moduleID, moduleNetworkCallback, errorCallback, RETRY_COUNT); + } + + private void find(String moduleID, NetworkCallback moduleNetworkCallback, NetworkErrorCallback errorCallback, int retries) { + if (retries < 0) { + errorCallback.onError(new NetworkError(101107, -1, "Too many retries!")); + return; + } + if (this.modules != null) { + Modules.Module module = this.modules.get(moduleID); + if (module != null) { + moduleNetworkCallback.onResponse(module); + return; + } + } + recv(success -> find(moduleID, moduleNetworkCallback, errorCallback, retries - 1), errorCallback, true, RETRY_COUNT); + } + + void store() { + if (this.modules != null) { + try { + this.modules.save(getContext()); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void restore() { + try { + this.modules = Modules.load(getContext()); + } catch (FileNotFoundException ignored) { + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public void recv(final NetworkCallback callback, final NetworkErrorCallback errorCallback) { + recv(callback, errorCallback, false, RETRY_COUNT); + } + + public void recv(final NetworkCallback callback, final NetworkErrorCallback errorCallback, boolean forceRefresh, final int retries) { + queue.add(() -> { + if (this.modules != null && !forceRefresh) { + callback.onResponse(this.modules); + queue.next(); + return; + } + this.upgrade(success -> { + if (this.modules == null) + this.modules = success; + else + this.modules.updateList(success); + callback.onResponse(this.modules); + queue.next(); + }, error -> { + if (retries > 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) { + login.refreshLogin(success -> { + recv(callback, errorCallback, forceRefresh, retries-1); + queue.next(); + }, errorCallback); + return; + } + errorCallback.onError(error); + queue.next(); + }); + }); + } + + private void upgrade(final NetworkCallback callback, final NetworkErrorCallback errorCallback) { + if (!login.isInOnlineMode()) { + errorCallback.onError(new NetworkError(101105, 500, "Currently running in offline mode!")); + return; + } + // https://file.io/71sa2V + get("https://kvv.imp.fu-berlin.de/direct/site.json", login.getLoginToken().getCookies(), response -> { + String body = response.getParsed(); + if (body == null) { + errorCallback.onError(new NetworkError(101101, 403, "No module list retrieved!")); + return; + } + Modules modules = new Modules(login.getLoginToken()); + try { + JSONObject json = new JSONObject(body); + JSONArray sites = json.getJSONArray("site_collection"); + + for (int i = 0; i < sites.length(); i++) { + JSONObject site = sites.getJSONObject(i); + String semester = site.getJSONObject("props").getString("term_eid"); + HashSet lvNumbers = new HashSet<>(); + for (MatchResult matchResult : Regex.allMatches("[0-9]+", site.getJSONObject("props").getString("kvv_lvnumbers"))) { + lvNumbers.add(matchResult.group()); + } + String title = site.getString("entityTitle"); + HashSet lecturers = new HashSet<>(); + for (String lecturer : site.getJSONObject("props").getString("kvv_lecturers").split("#")) { + if (lecturer.length() > 2) + lecturers.add(new Lecturer(lecturer)); + } + String type = site.getJSONObject("props").getString("kvv_coursetype"); + String description = site.getString("description"); + description = String.valueOf(ModulesPart.fromHtml(description)); + String id = site.getString("id"); + modules.addModule(semester, lvNumbers, title, lecturers, type, description, id); + } + } catch (JSONException e) { + e.printStackTrace(); + errorCallback.onError(new NetworkError(101102, 403, "Cannot parse module list!")); + return; + } catch (NoSuchFieldException e) { + e.printStackTrace(); + errorCallback.onError(new NetworkError(101103, 403, "Cannot parse module list!")); + return; + } + // Empty module *may be* because token is invalid -> check + if (modules.size() == 0) + login.testLoginToken(token -> callback.onResponse(modules), errorCallback); + else + callback.onResponse(modules); + }, error -> errorCallback.onError(new NetworkError(101104, error.networkResponse.statusCode, "Cannot get module list!"))); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/ModulesPart.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/ModulesPart.java new file mode 100644 index 0000000..17239ab --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/ModulesPart.java @@ -0,0 +1,75 @@ +package de.sebse.fuplanner.services.NewKVV; + +import android.content.Context; +import android.os.Build; +import android.text.Html; +import android.text.Spanned; + +import de.sebse.fuplanner.services.NewKVV.types.Modules; +import de.sebse.fuplanner.tools.NewAsyncQueue; +import de.sebse.fuplanner.tools.network.HTTPService; +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; + +public abstract class ModulesPart extends HTTPService { + static final int RETRY_COUNT = 1; + protected final KVVLogin login; + protected final KVVModulesList list; + private NewAsyncQueue queue = new NewAsyncQueue(); + + ModulesPart(KVVLogin login, KVVModulesList list, Context context) { + super(context); + this.login = login; + this.list = list; + } + + public void recv(final String moduleID, final NetworkCallback callback, final NetworkErrorCallback errorCallback) { + recv(moduleID, callback, errorCallback, false); + } + + public void recv(final String moduleID, final NetworkCallback callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh) { + list.find(moduleID, success -> recv(success, callback, errorCallback, forceRefresh, RETRY_COUNT), errorCallback); + } + + private void recv(final Modules.Module module, final NetworkCallback callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh, final int retries) { + queue.add(() -> { + if (getPart(module) != null && !forceRefresh) { + callback.onResponse(module); + queue.next(); + return; + } + upgrade(module.getID(), success -> { + if (setPart(module, success)) { + this.list.store(); + } + callback.onResponse(module); + queue.next(); + }, error -> { + if (retries >= 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) { + login.refreshLogin(success -> { + recv(module, callback, errorCallback, forceRefresh, retries-1); + queue.next(); + }, errorCallback); + return; + } + errorCallback.onError(error); + queue.next(); + }); + }); + } + + static Spanned fromHtml(String html){ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); + } else { + //noinspection deprecation + return Html.fromHtml(html); + } + } + + protected abstract T getPart(Modules.Module module); + + protected abstract boolean setPart(Modules.Module module, T part); + + protected abstract void upgrade(final String ID, final NetworkCallback callback, final NetworkErrorCallback errorCallback); +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Announcement.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Announcement.java new file mode 100644 index 0000000..f25a6b7 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Announcement.java @@ -0,0 +1,64 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import com.google.android.gms.common.internal.Objects; + +import java.io.Serializable; +import java.util.ArrayList; + +public class Announcement implements Serializable { + private final String id; + private final String title; + private final String body; + private final String createdBy; + private final long createdOn; + private final ArrayList urls; + + public Announcement(String id, String title, String body, String createdBy, long createdOn, ArrayList urls) { + + this.id = id; + this.title = title; + this.body = body; + this.createdBy = createdBy; + this.createdOn = createdOn; + this.urls = urls; + } + + public ArrayList getUrls() { + return urls; + } + + private String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getBody() { + return body; + } + + public String getCreatedBy() { + return createdBy; + } + + public long getCreatedOn() { + return createdOn; + } + + @Override + public String toString() { + return "ID: "+getId()+ + "\nTitle: "+getTitle()+ + "\nBody length: "+getBody().length()+ + "\nCreated by: "+getCreatedBy()+ + "\nCreated on: "+getCreatedOn()+ + "\nURLs: "+getUrls().toString(); + } + + @Override + public int hashCode() { + return Objects.hashCode(getId(), getBody(), getCreatedBy(), getCreatedOn(), getTitle(), getUrls()); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Assignment.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Assignment.java new file mode 100644 index 0000000..4f366af --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Assignment.java @@ -0,0 +1,53 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import java.io.Serializable; +import java.util.ArrayList; + +public class Assignment implements Serializable { + private final String id; + private final String title; + private final long dueTime; + private final ArrayList urls; + private final String instructions; + + public Assignment(String id, String title, long dueTime, String gradebookItemName, String gradeScale, ArrayList urls, String instructions) {//, String grade + this.id = id; + this.title = title; + this.dueTime = dueTime; + this.urls = urls; + //this.grade = grade; + this.instructions = instructions; + } + + private String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public boolean isOpen() { + return dueTime > System.currentTimeMillis(); + } + + public long getDueDate() { + return dueTime; + } + + public ArrayList getUrls() { + return urls; + } + + public String getInstructions() { + return instructions; + } + + @Override + public String toString() { + return "ID: "+getId()+ + "\nTitle: "+getTitle()+ + "\nDue date: "+getDueDate()+ + "\nInstructions: "+getInstructions().substring(0, Math.min(getInstructions().length(), 100)); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/AssignmentList.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/AssignmentList.java new file mode 100644 index 0000000..969e03f --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/AssignmentList.java @@ -0,0 +1,16 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import de.sebse.fuplanner.tools.DateSortedList; + +public class AssignmentList extends DateSortedList { + + @Override + public long getDateByItem(Assignment item) { + return item.getDueDate(); + } + + @Override + public boolean reversed() { + return true; + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Event.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Event.java new file mode 100644 index 0000000..ee73985 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Event.java @@ -0,0 +1,114 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import de.sebse.fuplanner.tools.ColorRGB; + +public class Event implements Serializable { + private final String siteId; + private final String id; + private final String type; + private final String title; + private final long duration; + private final long firstTime; + private final String location; + + public Event(String id, String type, String title, long duration, long firstTime, String siteId, String location) { + this.siteId = siteId; + this.id = id; + this.type = type; + this.title = title; + this.duration = duration; + this.firstTime = firstTime; + this.location = location + .replace(" Übungsraum", "") + .replace(" Konferenzraum", "") + .replace(" Seminarraum", ""); + } + + public String getId() { + return id; + } + + public String getTitle() { + return this.title; + } + + public String getType() { + return this.type; + } + + public long getStartDate() { + return this.firstTime; + } + + public long getEndDate() { + return this.firstTime+this.duration; + } + + public String getLocation() { + return location; + } + + public String getModuleId() { + return siteId; + } + + public ColorRGB getColor() { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + byte[] encodedHash; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + encodedHash = digest.digest(siteId.getBytes(StandardCharsets.UTF_8)); + } else { + encodedHash = digest.digest(siteId.getBytes()); + } + int h = (0xff & encodedHash[0]) + (0xff & encodedHash[1]) * 255; + h = h * 360 / 0xffff; + //int s = 0xff & encodedHash[2]; + //s = s * 100 / 0xffff; + //int v = 0xff & encodedHash[3]; + //v = v * 100 / 0xffff; + + // range for more beautiful colors + h = h / 30 * 30; + int s = 100; + int v = 80; + + return hsvToRgb(h/360.0, s/100.0, v/100.0); + } + + private static ColorRGB hsvToRgb(double hue, double saturation, double value) { + int h = (int)(hue * 6); + double f = hue * 6 - h; + double p = value * (1 - saturation); + double q = value * (1 - f * saturation); + double t = value * (1 - (1 - f) * saturation); + + switch (h) { + case 0: return new ColorRGB(value, t, p); + case 1: return new ColorRGB(q, value, p); + case 2: return new ColorRGB(p, value, t); + case 3: return new ColorRGB(p, q, value); + case 4: return new ColorRGB(t, p, value); + case 5: return new ColorRGB(value, p, q); + default: throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value); + } + } + + @Override + public String toString() { + return "ID: "+getId()+ + "\nType: "+getType()+ + "\nStart Date: "+getStartDate()+ + "\nEnd date: "+getEndDate(); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/EventList.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/EventList.java new file mode 100644 index 0000000..1c18424 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/EventList.java @@ -0,0 +1,16 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import de.sebse.fuplanner.tools.DateSortedList; + +public class EventList extends DateSortedList { + + @Override + public long getDateByItem(Event item) { + return item.getEndDate(); + } + + @Override + public boolean reversed() { + return false; + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Gradebook.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Gradebook.java new file mode 100644 index 0000000..d97e368 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Gradebook.java @@ -0,0 +1,34 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import java.io.Serializable; + +public class Gradebook implements Serializable { + private final String itemName; + private final double grade; + private final double maxPoints; + + public Gradebook(String itemName, double points, double maxPoints) { + this.itemName = itemName; + this.grade = points; + this.maxPoints = maxPoints; + } + + public double getMaxPoints() { + return maxPoints; + } + + public double getPoints() { + return grade; + } + + public String getItemName() { + return itemName; + } + + @Override + public String toString() { + return "Name: "+getItemName()+ + "\nPoints: "+ getPoints()+ + "\nMax points: "+getMaxPoints(); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/GroupedEvents.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/GroupedEvents.java new file mode 100644 index 0000000..0911641 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/GroupedEvents.java @@ -0,0 +1,128 @@ +package de.sebse.fuplanner.services.NewKVV.types; + + +import android.annotation.SuppressLint; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +import androidx.annotation.NonNull; +import de.sebse.fuplanner.tools.logging.Logger; + +public class GroupedEvents { + private ArrayList arrayList = new ArrayList<>(); + private Logger log = new Logger(this); + + public void add(Event event) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(event.getStartDate()); + long dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + for (Group group : arrayList) { + if (group.add(event)) { + return; + } + } + arrayList.add(new Group(event)); + } + + public List getGroups() { + return Collections.unmodifiableList(arrayList); + } + + public class Group { + private long firstDate; + private long lastDate; + private ArrayList skippedDates; + + private int dayOfWeek; + private long startTime; + private long duration; + + private Group(Event event) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(event.getStartDate()); + dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + startTime = calendar.get(Calendar.HOUR_OF_DAY)*3600000+calendar.get(Calendar.MINUTE)*60000+calendar.get(Calendar.SECOND)*1000+calendar.get(Calendar.MILLISECOND); + duration = event.getEndDate()-event.getStartDate(); + + skippedDates = new ArrayList<>(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + firstDate = calendar.getTimeInMillis(); + lastDate = firstDate; + } + + private boolean add(Event event) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(event.getStartDate()); + int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + int startTime = calendar.get(Calendar.HOUR_OF_DAY)*3600000+calendar.get(Calendar.MINUTE)*60000+calendar.get(Calendar.SECOND)*1000+calendar.get(Calendar.MILLISECOND); + long length = event.getEndDate()-event.getStartDate(); + if (this.dayOfWeek != dayOfWeek || this.startTime != startTime || this.duration != length) + return false; + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long date = calendar.getTimeInMillis(); + if (date < firstDate) { + firstDate = addDays(firstDate, -7); + while (firstDate > date) { + skippedDates.add(firstDate); + firstDate = addDays(firstDate, -7); + } + } else if (date > lastDate) { + lastDate = addDays(lastDate, 7); + while (lastDate < date) { + skippedDates.add(lastDate); + lastDate = addDays(lastDate, 7); + } + } else { + skippedDates.remove(date); + } + return true; + } + + private long addDays(long timeInMillis, int days) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timeInMillis); + calendar.add(Calendar.DAY_OF_YEAR, days); + return calendar.getTimeInMillis(); + } + + public long getFirstDate() { + return firstDate; + } + + public long getLastDate() { + return lastDate; + } + + public List getSkippedDates() { + return Collections.unmodifiableList(skippedDates); + } + + public int getDayOfWeek() { + return dayOfWeek; + } + + public long getStartTime() { + return startTime; + } + + public long getDuration() { + return duration; + } + + @SuppressLint("DefaultLocale") + @NonNull + @Override + public String toString() { + return String.format("%d - %d - %d - %s", dayOfWeek, startTime, duration, skippedDates); + } + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Lecturer.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Lecturer.java new file mode 100644 index 0000000..31a2163 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Lecturer.java @@ -0,0 +1,41 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import java.io.Serializable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Lecturer implements Serializable { + private final String firstName; + private final String surname; + private final String mail; + + public Lecturer(String parsableString) throws NoSuchFieldException { + Pattern pattern = Pattern.compile("([^|]*)\\|([^|]*)\\|([^|]*)\\|\\|", Pattern.DOTALL); + Matcher matcher = pattern.matcher(parsableString); + if (!matcher.find()) { + throw new NoSuchFieldException(); + } + this.firstName = matcher.group(1); + this.surname = matcher.group(2); + this.mail = matcher.group(3); + } + + private String getFirstName() { + return firstName; + } + + private String getSurname() { + return surname; + } + + private String getMail() { + return mail; + } + + @Override + public String toString() { + return "First name: "+ getFirstName()+ + "\nSurname: "+getSurname()+ + "\nMail: "+getMail(); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/LoginToken.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/LoginToken.java new file mode 100644 index 0000000..015c82d --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/LoginToken.java @@ -0,0 +1,114 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import android.content.Context; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; + +import androidx.annotation.Nullable; + +/** + * Created by sebastian on 29.10.17. + */ + +public class LoginToken implements Serializable { + private static final String FILE_NAME = "LoginTokenSaveFile"; + + private final String username; + private final String shibsessionKey; + private final String shibsessionName; + private final String JSESSIONID; + @Nullable private String fullName; + @Nullable private String email; + + public LoginToken(String username, String shibsessionKey, String shibsessionName, String JSESSIONID) { + this.username = username; + this.shibsessionKey = shibsessionKey; + this.shibsessionName = shibsessionName; + this.JSESSIONID = JSESSIONID; + } + + @Nullable + public static LoginToken load(Context context) throws IOException, ClassNotFoundException { + FileInputStream fis; + try { + fis = context.openFileInput(FILE_NAME); + } catch (FileNotFoundException e) { + return null; + } + ObjectInputStream is = new ObjectInputStream(fis); + LoginToken loginToken = (LoginToken) is.readObject(); + is.close(); + fis.close(); + return loginToken; + } + + public void save(Context context) throws IOException { + FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE); + ObjectOutputStream os = new ObjectOutputStream(fos); + os.writeObject(this); + os.close(); + fos.close(); + } + + public void delete(Context context) { + context.deleteFile(FILE_NAME); + } + + public void setAdditionals(String fullName, String email) { + this.fullName = fullName; + this.email = email; + } + + public String getUsername() { + return username; + } + + private String getShibsessionKey() { + return shibsessionKey; + } + + private String getShibsessionName() { + return shibsessionName; + } + + private String getJSESSIONID() { + return JSESSIONID; + } + + public String getFullName() { + return fullName; + } + + public String getEmail() { + return email; + } + + public HashMap getCookies() { + HashMap cookies = new HashMap<>(); + cookies.put("JSESSIONID", getJSESSIONID()); + cookies.put(getShibsessionKey(), getShibsessionName()); + cookies.put("pasystem_timezone_ok", "true"); + return cookies; + } + + public boolean isSameUser(LoginToken token) { + return token != null && this.getUsername().equals(token.getUsername()); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + HashMap cookies = this.getCookies(); + for (String header: cookies.keySet()) { + result.append(header).append("=").append(cookies.get(header)).append(";"); + } + return result.substring(0, result.length()-1); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Modules.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Modules.java new file mode 100644 index 0000000..6c4c21d --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Modules.java @@ -0,0 +1,168 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import android.content.Context; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Created by sebastian on 29.10.17. + */ + +public class Modules implements Iterable, Serializable { + private SortedListModule list; + private final LoginToken token; + //private transient Logger log = new Logger(this); + private static final String FILE_NAME = "ModuleListSaveFile"; + + public Modules(LoginToken loginToken) { + this.token = loginToken; + this.list = new SortedListModule(); + } + + public void addModule(String semester, HashSet lvNumber, String title, HashSet lecturer, String type, String description, String ID) { + Module m = new Module(semester, lvNumber, title, lecturer, type, description, ID); + this.list.add(m); + } + + @NonNull + @Override + public String toString() { + return this.list.toString(); + } + + @NonNull + @Override + public Iterator iterator() { + return this.list.iterator(); + } + + public Iterator latestSemesterIterator() { + return this.list.filteredIterator(this.list.getLatestSemester()); + } + + public int size() { + return this.list.size(); + } + + public Module get(String id) { + return this.list.getById(id); + } + + public Module getByIndex(int index) { + return this.list.get(index); + } + + public static Modules load(Context context) throws IOException, ClassNotFoundException { + FileInputStream fis = context.openFileInput(FILE_NAME); + ObjectInputStream is = new ObjectInputStream(fis); + Modules modules = (Modules) is.readObject(); + is.close(); + fis.close(); + return modules; + } + + public void save(Context context) throws IOException { + FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE); + ObjectOutputStream os = new ObjectOutputStream(fos); + os.writeObject(this); + os.close(); + fos.close(); + } + + public void delete(Context context) { + context.deleteFile(FILE_NAME); + } + + public LoginToken getToken() { + return token; + } + + public void updateList(Modules modules) { + SortedListModule old = this.list; + this.list = modules.list; + for (Module oldModule : old) { + Module newModule = this.list.getById(oldModule.getID()); + if (newModule != null) { + newModule.announcements = oldModule.announcements; + newModule.assignments = oldModule.assignments; + newModule.events = oldModule.events; + newModule.gradebook = oldModule.gradebook; + newModule.resources = oldModule.resources; + } + } + } + + public class Module implements Serializable { + public final String semester; + final HashSet lvNumber; + public final String title; + final HashSet lecturer; + public final String type; + public final String description; + private final String ID; + @Nullable public ArrayList announcements; + @Nullable public AssignmentList assignments; + @Nullable public EventList events; + @Nullable public ArrayList gradebook; + @Nullable public ArrayList resources; + + /*private Module() { + this(null, null, null, null, null); + throw new AssertionError("Do not use this constructor!"); + }*/ + + public float getGradebookPercent(){ + float maxPoint = 0; + float userPoint = 0; + if (gradebook != null) { + for (Gradebook g : gradebook){ + maxPoint += g.getMaxPoints(); + userPoint += g.getPoints(); + } + } + if (maxPoint == 0) + return 0; + return userPoint/maxPoint; + } + + private Module(String semester, HashSet lvNumber, String title, HashSet lecturer, String type, String description, String ID) { + semester = semester.replace("SS", "S"); + semester = semester.replaceAll("[0-9]{2}([0-9]{2})", "$1"); + title = title.replaceAll("(.*?) (S[0-9]{2}|W[0-9/]{5})", "$1"); + + this.semester = semester; + this.lvNumber = lvNumber; + this.title = title; + this.lecturer = lecturer; + this.type = type; + this.description = description; + this.ID = ID; + } + + public String getID() { + return ID; + } + + @NonNull + @Override + public String toString() { + return "Semester: "+semester+ + "\nlvNumber: "+lvNumber.toString()+ + "\ntitle: "+title+ + "\nlecturer: "+lecturer.toString()+ + "\ntype: "+type+ + "\nID: "+ID; + } + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Resource.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Resource.java new file mode 100644 index 0000000..598cf69 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/Resource.java @@ -0,0 +1,134 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import java.io.Serializable; +import java.util.ArrayList; + +import androidx.annotation.LayoutRes; +import de.sebse.fuplanner.R; +import de.sebse.fuplanner.tools.ui.treeview.LayoutItemType; +import de.sebse.fuplanner.tools.ui.treeview.TreeNode; + + +public abstract class Resource implements Serializable { + + final String author; + final long modifiedDate; + final String title; + final String url; + private final boolean visible; + private final String container; + + + Resource(String author, String title, long modifiedDate, String url, boolean visible, String container) { + this.author = author; + this.title = title; + this.modifiedDate = modifiedDate; + this.url = url; + this.visible = visible; + this.container = container; + } + + public String getAuthor() { + return author; + } + + public long getModifiedDate() { + return modifiedDate; + } + + public String getTitle() { + return title; + } + + public String getUrl() { + return url; + } + + public String getContainer() { + return container; + } + + public boolean isVisible() { + return visible; + } + + public abstract TreeNode getTreeNode(); + + public static class File extends Resource implements LayoutItemType { + private final String type; + + public File(String author, String title, long modifiedDate, String url, boolean visible, String container, String type) { + super(author, title, modifiedDate, url, visible, container); + this.type = type; + } + + @Override + public String toString() { + return "Resource{" + + "author='" + author + '\'' + + ", modifiedDate=" + modifiedDate + + ", title='" + title + '\'' + + ", url='" + url + '\'' + + ", type='" + type + '\'' + + '}'; + } + + @Override + public TreeNode getTreeNode() { + return new TreeNode<>(this); + } + + @Override + public @LayoutRes int getLayoutId() { + return R.layout.item_file; + } + } + + public static class Folder extends Resource implements LayoutItemType { + + private final ArrayList children; + public Folder(String author, String title, long modifiedDate, String url, boolean visible, String container) { + super(author, title, modifiedDate, url, visible, container); + children = new ArrayList<>(); + } + + public void add(Resource res){ + children.add(res); + } + + public Resource get(int id){ + return children.get(id); + } + + public int size(){ + return children.size(); + } + + @Override + public String toString() { + return "Resource{" + + "author='" + author + '\'' + + ", modifiedDate=" + modifiedDate + + ", title='" + title + '\'' + + ", url='" + url + '\'' + + ", children='" + children + '\'' + + '}'; + } + + @Override + public TreeNode getTreeNode() { + TreeNode dir = new TreeNode<>(this); + for (Resource res: children) { + dir.addChild(res.getTreeNode()); + } + return dir; + } + + @Override + public @LayoutRes int getLayoutId() { + return R.layout.item_dir; + } + } +} + + diff --git a/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/SortedListModule.java b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/SortedListModule.java new file mode 100644 index 0000000..13b1a83 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/services/NewKVV/types/SortedListModule.java @@ -0,0 +1,68 @@ +package de.sebse.fuplanner.services.NewKVV.types; + +import de.sebse.fuplanner.tools.Regex; +import de.sebse.fuplanner.tools.SortedList; + +public class SortedListModule extends SortedList { + private static final int LARGER = 1; + private static final int EQUAL = 0; + private static final int SMALLER = -1; + + @Override + public int compare(Modules.Module o1, Modules.Module o2) { + int semester; + try { + semester = -compareSemester(o1.semester, o2.semester); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + semester = EQUAL; + } + if (semester != EQUAL) + return semester; + return o1.title.compareToIgnoreCase(o2.title); + } + + @Override + public void add(Modules.Module e) { + super.add(e); + } + + public String getLatestSemester() { + if (size() > 0) + //noinspection ConstantConditions + return this.get(0).semester; + else + return null; + } + + private static int compareSemester(String a, String b) throws NoSuchFieldException { + if (a == null && b == null) + return EQUAL; + if (a == null) + return SMALLER; + if (b == null) + return LARGER; + + String s1type = Regex.regex("^(S|WS) ", a); + int s1year = Integer.parseInt(Regex.regex("^(S|WS) ([0-9]{2})", a, 2)); + String s2type = Regex.regex("^(S|WS) ", b); + int s2year = Integer.parseInt(Regex.regex("^(S|WS) ([0-9]{2})", b, 2)); + + if (s1year == s2year) { + if (s1type.equals(s2type)) + return EQUAL; + return s1type.equals("S") ? SMALLER : LARGER; + } + return s1year < s2year ? SMALLER : LARGER; + } + + @Override + public boolean hasIdentifier(Modules.Module o1, String id) { + return o1.getID().equals(id); + } + + @Override + public boolean hasFilter(Modules.Module o1, String filter) { + return o1.semester.equals(filter); + } +} diff --git a/app/src/main/java/de/sebse/fuplanner/tools/MainActivityListener.java b/app/src/main/java/de/sebse/fuplanner/tools/MainActivityListener.java index 63f5306..c972745 100644 --- a/app/src/main/java/de/sebse/fuplanner/tools/MainActivityListener.java +++ b/app/src/main/java/de/sebse/fuplanner/tools/MainActivityListener.java @@ -28,6 +28,4 @@ public interface MainActivityListener { void addRequestPermissionsResultListener(RequestPermissionsResultListener listener, String id); void removeRequestPermissionsResultListener(String id); - - void refreshNavigation(); } diff --git a/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java b/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java new file mode 100644 index 0000000..8a39c91 --- /dev/null +++ b/app/src/main/java/de/sebse/fuplanner/tools/NewAsyncQueue.java @@ -0,0 +1,58 @@ +package de.sebse.fuplanner.tools; + +import java.util.LinkedList; + +import de.sebse.fuplanner.tools.network.NetworkCallback; +import de.sebse.fuplanner.tools.network.NetworkErrorCallback; + +public class NewAsyncQueue { + private final LinkedList mQueue = new LinkedList<>(); + private boolean mIsRunning = false; + + public void add(AsyncQueueCallback callback) { + if (isRunning()) + getQueue().addLast(callback); + else { + setRunning(true); + callback.run(); + } + } + + public void next() { + AsyncQueueCallback callback = getQueue().pollFirst(); + if (callback == null) + setRunning(false); + else + callback.run(); + } + + public interface AsyncQueueCallback { + void run(); + } + + public NetworkErrorCallback check(NetworkErrorCallback value) { + return error -> { + value.onError(error); + next(); + }; + } + + public NetworkCallback check(NetworkCallback value) { + return success -> { + value.onResponse(success); + next(); + }; + } + + private boolean isRunning() { + return mIsRunning; + } + + private void setRunning(boolean value) { + mIsRunning = value; + } + + private LinkedList getQueue() { + return mQueue; + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6b82597..e962ffb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -10,6 +10,7 @@ diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml deleted file mode 100644 index 4495c28..0000000 --- a/app/src/main/res/xml/provider_paths.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file