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