diff --git a/app/app.iml b/app/app.iml index 673e0e1..7a740ab 100644 --- a/app/app.iml +++ b/app/app.iml @@ -94,12 +94,12 @@ - + - + diff --git a/app/build.gradle b/app/build.gradle index b563794..e2a3f51 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,14 +7,13 @@ android { applicationId "de.sebse.fuplanner" minSdkVersion 21 targetSdkVersion 29 - versionCode 41 - versionName "1.6.2" + versionCode 42 + versionName "1.6.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { - minifyEnabled true - shrinkResources true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } @@ -31,7 +30,7 @@ dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'androidx.preference:preference:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3' implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.android.volley:volley:1.1.1' implementation 'com.github.Cutta:TagView:1.3' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04fe56a..46af935 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + + + + - diff --git a/app/src/main/java/de/sebse/fuplanner/MainActivity.java b/app/src/main/java/de/sebse/fuplanner/MainActivity.java index 4f39ea8..c13f910 100644 --- a/app/src/main/java/de/sebse/fuplanner/MainActivity.java +++ b/app/src/main/java/de/sebse/fuplanner/MainActivity.java @@ -189,7 +189,7 @@ public class MainActivity extends AppCompatActivity if (!mAccountManager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) { desiredPage = getDefaultFragmentAfterLogout(); desiredData = ""; - mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, null, null); + goToLoginScreen(); updateNavigation(); changeFragment(desiredPage, desiredData); } else { @@ -210,16 +210,10 @@ public class MainActivity extends AppCompatActivity if (!mAlreadyCreated) { Intent serviceIntent = new Intent(this, KVV.class); - bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); + getApplicationContext().bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); } mAlreadyCreated = true; - if (!Preferences.getBoolean(this, R.string.pref_set_auto_sync_on_startup)) { - registerSync(true); - Preferences.setBoolean(this, R.string.pref_set_auto_sync_on_startup, true); - } else { - registerSync(false); - } CustomNotificationManager.createNotificationChannel(this); /*getKVV(kvv -> { kvv.modules().list().recv(list -> { @@ -229,12 +223,17 @@ public class MainActivity extends AppCompatActivity });*/ } + private void goToLoginScreen() { + Preferences.setBoolean(this, R.string.pref_set_auto_sync_on_startup, false); + mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, null, null); + } + @Override protected void onStart() { super.onStart(); if (!mAlreadyCreated) { Intent serviceIntent = new Intent(this, KVV.class); - bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); + getApplicationContext().bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); } mAlreadyCreated = true; } @@ -245,7 +244,7 @@ public class MainActivity extends AppCompatActivity if (mKVV != null) mKVV.removeListener("mainactivity"); if (mBound) - unbindService(mConnection); + getApplicationContext().unbindService(mConnection); mKVV = null; mBound = false; mAlreadyCreated = false; @@ -279,7 +278,7 @@ public class MainActivity extends AppCompatActivity updateNavigation(); if (restoreResult == Login.RESTORE_STATUS_SUCCESS && !isLoggedInBeforePause) { changeFragment(getDefaultFragmentAfterLogin()); - registerSync(true); + registerSync(); } else if (restoreResult == Login.RESTORE_STATUS_INVALID_PASSWORD && isLoggedInBeforePause) { kvv.account().logout(false); changeFragment(getDefaultFragmentAfterLogout()); @@ -289,6 +288,13 @@ public class MainActivity extends AppCompatActivity }); } isPaused = false; + + if (!Preferences.getBoolean(this, R.string.pref_set_auto_sync_on_startup) && mAccountManager.hasAccounts(AccountGeneral.ACCOUNT_TYPE)) { + registerSync(true); + Preferences.setBoolean(this, R.string.pref_set_auto_sync_on_startup, true); + } else { + registerSync(); + } } @Override @@ -523,16 +529,23 @@ public class MainActivity extends AppCompatActivity changeFragment(newFragment); } - private void registerSync(boolean onLogin) { + private void registerSync() { + registerSync(false); + } + + private void registerSync(boolean isLogin) { Account accountByType = mAccountManager.getAccountByType(AccountGeneral.ACCOUNT_TYPE); if (accountByType != null) { - if (onLogin) + if (isLogin || (ContentResolver.getMasterSyncAutomatically() && ContentResolver.getSyncAutomatically(accountByType, KVVContentProvider.PROVIDER_NAME))) { ContentResolver.setSyncAutomatically(accountByType, KVVContentProvider.PROVIDER_NAME, true); + } ContentResolver.addPeriodicSync( accountByType, KVVContentProvider.PROVIDER_NAME, Bundle.EMPTY, - Long.parseLong(Preferences.getStringArray(this, R.array.pref_sync_frequency))*60*60); + Long.parseLong(Preferences.getStringArray(this, R.array.pref_sync_frequency)) * 60 * 60 + ); + ContentResolver.requestSync(accountByType, KVVContentProvider.PROVIDER_NAME, Bundle.EMPTY); } } @@ -686,7 +699,7 @@ public class MainActivity extends AppCompatActivity if (drawer.isDrawerOpen(GravityCompat.START) && !isDrawerFixed) { drawer.closeDrawer(GravityCompat.START); } - mAccountManager.getTokenByType(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, null, null); + goToLoginScreen(); }); } diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/ModulesAdapter.java b/app/src/main/java/de/sebse/fuplanner/fragments/ModulesAdapter.java index 2a058df..1d857df 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/ModulesAdapter.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/ModulesAdapter.java @@ -98,6 +98,8 @@ class ModulesAdapter extends RecyclerView.Adapter { lecturers.append(lecturer.getNameShort()); } iHolder.mSubLeft.setText(lecturers); + // {'G', 'V-Ue', 'P', 'S', 'V', 'Ue', 'L'} + // {'Vorlesung', 'Übung', 'Praktikum', 'Zentralübung', 'Seminaristische Übung', 'Integrierte Veranstaltung', 'Seminar am PC', 'Praxisseminar', 'Seminar', 'Projektseminar', 'Projekt'} iHolder.mSubRight.setText(module.type); iHolder.mView.setOnClickListener(v -> { diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/PrefsFragment.java b/app/src/main/java/de/sebse/fuplanner/fragments/PrefsFragment.java index 0bf3be1..3ad38ca 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/PrefsFragment.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/PrefsFragment.java @@ -1,23 +1,38 @@ package de.sebse.fuplanner.fragments; +import android.Manifest; import android.accounts.Account; import android.content.ContentResolver; +import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; + +import java.util.Arrays; + import de.sebse.fuplanner.MainActivity; import de.sebse.fuplanner.R; import de.sebse.fuplanner.services.fulogin.AccountGeneral; import de.sebse.fuplanner.services.kvv.sync.KVVContentProvider; +import de.sebse.fuplanner.services.kvv.ui.Download; import de.sebse.fuplanner.tools.CustomAccountManager; +import de.sebse.fuplanner.tools.MainActivityListener; import de.sebse.fuplanner.tools.Preferences; +import de.sebse.fuplanner.tools.RequestPermissionsResultListener; +import de.sebse.fuplanner.tools.logging.Logger; public class PrefsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { + private MainActivityListener mMainActivityListener; + public static PrefsFragment newInstance() { PrefsFragment fragment = new PrefsFragment(); Bundle args = new Bundle(); @@ -52,26 +67,75 @@ public class PrefsFragment extends PreferenceFragmentCompat implements SharedPre if (preference instanceof ListPreference) preference.setSummary(((ListPreference) preference).getEntry()); - if (getActivity() != null && getActivity() instanceof MainActivity) { - CustomAccountManager accountManager = ((MainActivity) getActivity()).getAccountManager(); - if (accountManager != null) { - Account accountByType = accountManager.getAccountByType(AccountGeneral.ACCOUNT_TYPE); - if (accountByType != null) { - ContentResolver.setSyncAutomatically(accountByType, KVVContentProvider.PROVIDER_NAME, true); - ContentResolver.addPeriodicSync( - accountByType, - KVVContentProvider.PROVIDER_NAME, - Bundle.EMPTY, - Long.parseLong(Preferences.getStringArray(getActivity(), R.array.pref_sync_frequency))); + if (s.equals(requireContext().getString(R.string.pref_sync_frequency))) { + if (getActivity() != null && getActivity() instanceof MainActivity) { + CustomAccountManager accountManager = ((MainActivity) getActivity()).getAccountManager(); + if (accountManager != null) { + Account accountByType = accountManager.getAccountByType(AccountGeneral.ACCOUNT_TYPE); + if (accountByType != null) { + ContentResolver.setSyncAutomatically(accountByType, KVVContentProvider.PROVIDER_NAME, true); + ContentResolver.addPeriodicSync( + accountByType, + KVVContentProvider.PROVIDER_NAME, + Bundle.EMPTY, + Long.parseLong(Preferences.getStringArray(getActivity(), R.array.pref_sync_frequency))); + } } } } - String nightMode = Preferences.getStringArray(requireContext(), R.array.pref_night_mode); - switch (nightMode) { - case "night": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); break; - case "day": AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); break; - default: AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); break; + if (s.equals(requireContext().getString(R.string.pref_night_mode))) { + String nightMode = Preferences.getStringArray(requireContext(), R.array.pref_night_mode); + switch (nightMode) { + case "night": + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + break; + case "day": + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + break; + default: + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + break; + } + } + + if (s.equals(requireContext().getString(R.string.pref_add_calendar)) + && getActivity() != null + && Preferences.getBoolean(getActivity(), R.string.pref_add_calendar)) { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR}, 1); + } } } + + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof MainActivityListener) { + mMainActivityListener = (MainActivityListener) context; + mMainActivityListener.onTitleTextChange(R.string.settings); + mMainActivityListener.addRequestPermissionsResultListener(getRequestPermissionsResultListener(), "PrefFragment"); + } else + throw new RuntimeException(context.toString() + " must implement MainActivityListener"); + } + + @Override + public void onDetach() { + super.onDetach(); + mMainActivityListener.removeRequestPermissionsResultListener("PrefFragment"); + mMainActivityListener = null; + } + + private RequestPermissionsResultListener getRequestPermissionsResultListener() { + return (requestCode, permissions, grantResults) -> { + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED + || ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { + Preferences.setBoolean(requireContext(), R.string.pref_add_calendar, false); + setPreferenceScreen(null); + setPreferencesFromResource(R.xml.preferences, null); + } + }; + } } \ No newline at end of file diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/ScheduleFragment.java b/app/src/main/java/de/sebse/fuplanner/fragments/ScheduleFragment.java index 08b2bbd..3cc5d73 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/ScheduleFragment.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/ScheduleFragment.java @@ -140,7 +140,7 @@ public class ScheduleFragment extends Fragment implements start.setTimeInMillis(e.getStartDate()); WeekViewEvent weekViewEvent = new WeekViewEvent(e.getModuleId()+"/"+e.getId(), e.getTitle(), e.getLocation(), start, end); - weekViewEvent.setColor(e.getColor()); + weekViewEvent.setColor(e.getColor(getContext())); events.add(weekViewEvent); } } 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 ce9d727..08ec89a 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 @@ -2,6 +2,7 @@ package de.sebse.fuplanner.fragments.moddetails; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -102,8 +103,20 @@ public class ModDetailAnnounceFragment extends Fragment implements Download.OnDo return; mListener.getKVV(kvv -> { kvv.modules().list().find(mItemPos, (Modules.Module module) -> { - String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-"); - folderName += "/Announcement"; + String folderName; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (module.semester != null) { + folderName = module.semester.toString(); + } else { + folderName = "PROJ"; + } + folderName += "-" + module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + folderName += "/Announcements"; + } else { + folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + folderName += "/Announcement"; + } + getDownload().openDownloadDialog(title, url, folderName); }, log::e); }); 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 c4a8dcc..df9e79c 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 @@ -2,6 +2,7 @@ package de.sebse.fuplanner.fragments.moddetails; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -102,8 +103,20 @@ public class ModDetailAssignmentFragment extends Fragment implements Download.On return; mListener.getKVV(kvv -> { kvv.modules().list().find(mItemPos, (Modules.Module module) -> { - String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-"); - folderName += "/Announcement"; + String folderName; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (module.semester != null) { + folderName = module.semester.toString(); + } else { + folderName = "PROJ"; + } + folderName += "-" + module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + folderName += "/Assignments"; + } else { + folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + folderName += "/Assignment"; + } + getDownload().openDownloadDialog(title, url, folderName); }, log::e); }); diff --git a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailGradebookAdapter.java b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailGradebookAdapter.java index 19bcb96..96167b9 100644 --- a/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailGradebookAdapter.java +++ b/app/src/main/java/de/sebse/fuplanner/fragments/moddetails/ModDetailGradebookAdapter.java @@ -127,10 +127,10 @@ class ModDetailGradebookAdapter extends RecyclerView.Adapter { kvv.modules().resources().recv(mItemPos, (Modules.Module module) -> { - String folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + String folderName; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (module.semester != null) { + folderName = module.semester.toString(); + } else { + folderName = "PROJ"; + } + folderName += "-" + module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + folderName += "/Resources"; + } else { + folderName = "FU-"+module.title.replaceAll("[:*<>|/\"\\\\]", "-"); + } Resource.File file = (Resource.File) node.getContent(); getDownload().openDownloadDialog(file, folderName); }, log::e); diff --git a/app/src/main/java/de/sebse/fuplanner/services/canteen/CanteenBrowser.java b/app/src/main/java/de/sebse/fuplanner/services/canteen/CanteenBrowser.java index 7060a5c..392e14d 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/canteen/CanteenBrowser.java +++ b/app/src/main/java/de/sebse/fuplanner/services/canteen/CanteenBrowser.java @@ -49,12 +49,11 @@ public class CanteenBrowser extends HTTPService { } public void getCanteens(final NetworkCallback callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) { + if (this.canteens != null && !forceRefresh) { + callback.onResponse(this.canteens); + return; + } queue.add("list", () -> { - if (this.canteens != null && !forceRefresh) { - callback.onResponse(this.canteens); - queue.next("list"); - return; - } this.upgradeCanteens(success -> { if (this.canteens == null) this.canteens = success; @@ -111,12 +110,11 @@ public class CanteenBrowser extends HTTPService { } public void getAvailableCanteens(final NetworkCallback callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) { + if (this.availableCanteens != null && !forceRefresh) { + callback.onResponse(this.availableCanteens); + return; + } queue.add("available", () -> { - if (this.availableCanteens != null && !forceRefresh) { - callback.onResponse(this.availableCanteens); - queue.next("available"); - return; - } this.upgradeAvailableCanteens(success -> { if (this.availableCanteens == null) this.availableCanteens = success; @@ -136,9 +134,7 @@ public class CanteenBrowser extends HTTPService { // "https://openmensa.org/api/v2/canteens?near[lat]=52.449743&near[lng]=13.282245&near[dist]=50" for (double[] root : canteenRoots) { - log.d("invoke", root[0], root[1]); get(String.format("https://openmensa.org/api/v2/canteens?near[lat]=%s&near[lng]=%s&near[dist]=50", root[0], root[1]), null, response -> { - log.d("invoke response", root[0], root[1]); String body = response.getParsed(); if (body == null) { errorCallback.onError(new NetworkError(201401, 403, "No canteen list retrieved!")); @@ -152,7 +148,6 @@ public class CanteenBrowser extends HTTPService { return; } finishedCount.getAndIncrement(); - log.d("invoke increment", root[0], root[1], finishedCount.get(), this.canteenRoots.length); if (finishedCount.get() == this.canteenRoots.length) { callback.onResponse(canteens); @@ -189,12 +184,11 @@ public class CanteenBrowser extends HTTPService { public void getCanteen(Canteen canteen, final NetworkCallback callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) { String hash = "canteen" + canteen.getId(); + if (canteen.size() > 0 && !forceRefresh) { + callback.onResponse(canteen); + return; + } queue.add(hash, () -> { - if (canteen.size() > 0 && !forceRefresh) { - callback.onResponse(canteen); - queue.next(hash); - return; - } this.upgradeCanteen(canteen, success -> { canteen.update(success); this.save(); @@ -236,12 +230,11 @@ public class CanteenBrowser extends HTTPService { public void getDay(Day day, final NetworkCallback callback, final NetworkErrorCallback errorCallback, boolean forceRefresh) { String hash = "day" + day.getCanteenId() + "@@@" + Canteen.calendarToKey(day.getCalendar()); + if (day.size() > 0 && !forceRefresh) { + callback.onResponse(day); + return; + } queue.add(hash, () -> { - if (day.size() > 0 && !forceRefresh) { - callback.onResponse(day); - queue.next(hash); - return; - } this.upgradeDay(day, success -> { day.update(success); this.save(); diff --git a/app/src/main/java/de/sebse/fuplanner/services/canteen/types/Canteens.java b/app/src/main/java/de/sebse/fuplanner/services/canteen/types/Canteens.java index cded142..8fc58c6 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/canteen/types/Canteens.java +++ b/app/src/main/java/de/sebse/fuplanner/services/canteen/types/Canteens.java @@ -4,6 +4,7 @@ import android.content.Context; import androidx.annotation.NonNull; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; @@ -74,7 +75,12 @@ public class Canteens implements Serializable, Iterable { } public static Canteens load(Context context) throws IOException, ClassNotFoundException { - FileInputStream fis = context.openFileInput(FILE_NAME); + FileInputStream fis; + try { + fis = context.openFileInput(FILE_NAME); + } catch (FileNotFoundException e) { + return null; + } ObjectInputStream is = new ObjectInputStream(fis); Canteens modules = (Canteens) is.readObject(); is.close(); diff --git a/app/src/main/java/de/sebse/fuplanner/services/fulogin/FUAuthenticatorActivity.java b/app/src/main/java/de/sebse/fuplanner/services/fulogin/FUAuthenticatorActivity.java index 8cb2681..f9b4708 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/fulogin/FUAuthenticatorActivity.java +++ b/app/src/main/java/de/sebse/fuplanner/services/fulogin/FUAuthenticatorActivity.java @@ -171,7 +171,7 @@ public class FUAuthenticatorActivity extends AccountAuthenticatorActivity { final Account account = new Account(accountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE)); final AccountManager mAccountManager = AccountManager.get(this); - if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) { + if (mIsAddingNewAccount) { String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); String authtokenType = mAuthTokenType; diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/Login.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/Login.java index 8711582..9985b07 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/Login.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/Login.java @@ -16,12 +16,11 @@ import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV; import de.sebse.fuplanner.tools.CustomAccountManager; import de.sebse.fuplanner.tools.NetworkCallbackCollector; import de.sebse.fuplanner.tools.Preferences; -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 Login extends HTTPService { +public class Login { public static final int RESTORE_STATUS_SUCCESS = 1; public static final int RESTORE_STATUS_ERROR = 2; public static final int RESTORE_STATUS_INVALID_PASSWORD = 3; @@ -31,6 +30,7 @@ public class Login extends HTTPService { public static final int RELOGIN = 0; private final KVVListener mListener; + private Context context; @Nullable private LoginTokenKVV mTokenKVV; @Nullable private LoginTokenBB mTokenBB; private boolean mLoginPending = false; @@ -38,8 +38,13 @@ public class Login extends HTTPService { private final NetworkCallbackCollector mRestoreCallbacks = new NetworkCallbackCollector<>(); Login(KVVListener listener, Context context) { - super(context); + super(); this.mListener = listener; + this.context = context; + } + + private Context getContext() { + return this.context; } public void restoreOnlineLogin(IntegerInterface callback) { diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java index 132aa6d..8483c2e 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesEvents.java @@ -123,7 +123,6 @@ public class ModulesEvents extends PartModules { //String[] tests = {"462854", "465661", "462126", "463782", "437050", "433843", "471614", "464205"}; //String[] tests = {"461459", "424564", "459494", "429737", "463765", "476477", "464082", "459577", "459743", "464318", "449358", "454327", "461784", "468081", "485919"}; //vvNumber = tests[new Random().nextInt(tests.length)]; - //log.d("LAAAAAAST", vvNumber); //vvNumber = "462126"; super.get(String.format("https://www.fu-berlin.de/vv/de/lv/%s", vvNumber), null, response1 -> { String body = response1.getParsed(); diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java index 7a6b267..87ad5ea 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesList.java @@ -27,7 +27,7 @@ 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.kvv.PartModules.RETRY_COUNT; +import static de.sebse.fuplanner.services.kvv.Part.RETRY_COUNT; public class ModulesList extends HTTPService { private final Login mLogin; @@ -104,9 +104,10 @@ public class ModulesList extends HTTPService { } catch (FileNotFoundException ignored) { } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); + delete(); } if (this.mModules == null) { - recv(success -> {}, log::e); + recv(success -> {}, log::e, true); } } @@ -131,6 +132,10 @@ public class ModulesList extends HTTPService { mLogin.getLoginTokenBB() != null && mLogin.getLoginTokenBB().isOtherUser(mModules.getUsername()) ) delete(); + if (this.mModules != null && !forceRefresh) { + callback.onResponse(this.mModules); + return; + } mQueue.add(() -> { if (mLogin.isLoginPending()) { mLogin.restoreOnlineLogin(resCode -> { @@ -141,11 +146,6 @@ public class ModulesList extends HTTPService { } }); mQueue.add(() -> { - if (this.mModules != null && !forceRefresh) { - callback.onResponse(this.mModules); - mQueue.next(); - return; - } Function errorFunc = ((Integer errorCode) -> (error -> { if (retries > 0 && (error.getHttpStatus() == 401 || error.getHttpStatus() == 403)) { mLogin.refreshLogin(success -> { diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java index 63451f4..e93ac86 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ModulesResources.java @@ -1,8 +1,11 @@ package de.sebse.fuplanner.services.kvv; import android.content.Context; +import android.os.Build; import android.os.Environment; +import androidx.core.content.ContextCompat; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -367,8 +370,15 @@ public class ModulesResources extends PartModules> { } private String saveFileInDownloads(String filename, byte[] data, String moduleName) { // Saves file in folder: DOWNLOADS/moduleName - File folder = new File(Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS), moduleName); + File folder; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + folder = new File(getContext().getExternalFilesDir( + Environment.DIRECTORY_DOWNLOADS), moduleName); + } else { + folder = new File(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS), moduleName); + } if (!folder.mkdirs()) { log.w( "Directory not created"); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/PartModules.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/PartModules.java index 71b1f8c..485fe80 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/PartModules.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/PartModules.java @@ -19,6 +19,10 @@ abstract class PartModules extends Part { @Override protected void recv(final Modules.Module module, final NetworkCallback callback, final NetworkErrorCallback errorCallback, final boolean forceRefresh, final int retries) { + if (getPart(module) != null && !forceRefresh) { + callback.onResponse(module); + return; + } mQueue.add(() -> { if (mLogin.isLoginPending()) { mLogin.restoreOnlineLogin(resCode -> { @@ -29,11 +33,6 @@ abstract class PartModules extends Part { } }); mQueue.add(() -> { - if (getPart(module) != null && !forceRefresh) { - callback.onResponse(module); - mQueue.next(); - return; - } upgrade(module.getModuleType(), module.getID(), success -> { if (setPart(module, success)) { this.mList.store(); diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java index 2ee94cb..edab6bc 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/sync/KVVSyncAdapter.java @@ -1,26 +1,40 @@ package de.sebse.fuplanner.services.kvv.sync; +import android.Manifest; import android.accounts.Account; +import android.accounts.AccountManager; import android.content.AbstractThreadedSyncAdapter; import android.content.ComponentName; import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SyncResult; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.provider.CalendarContract.Calendars; +import android.provider.CalendarContract.Events; +import android.util.Pair; import java.util.ArrayList; +import java.util.Calendar; import java.util.Iterator; import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; + import de.sebse.fuplanner.R; import de.sebse.fuplanner.fragments.moddetails.ModulePart; import de.sebse.fuplanner.services.kvv.KVV; import de.sebse.fuplanner.services.kvv.types.Announcement; import de.sebse.fuplanner.services.kvv.types.Assignment; import de.sebse.fuplanner.services.kvv.types.AssignmentList; +import de.sebse.fuplanner.services.kvv.types.Event; import de.sebse.fuplanner.services.kvv.types.EventList; import de.sebse.fuplanner.services.kvv.types.Grade; import de.sebse.fuplanner.services.kvv.types.Gradebook; @@ -28,9 +42,11 @@ import de.sebse.fuplanner.services.kvv.types.Modules; import de.sebse.fuplanner.services.kvv.types.Resource; import de.sebse.fuplanner.tools.CustomNotificationManager; import de.sebse.fuplanner.tools.NewAsyncQueue; +import de.sebse.fuplanner.tools.Preferences; import de.sebse.fuplanner.tools.UtilsDate; import de.sebse.fuplanner.tools.logging.Logger; +import static android.provider.CalendarContract.CALLER_IS_SYNCADAPTER; import static de.sebse.fuplanner.MainActivity.FRAGMENT_MODULES_DETAILS; public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { @@ -98,8 +114,9 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { String authority, ContentProviderClient provider, SyncResult syncResult) { - if (!mBound) { + if (!mBound && !mWaitForBound) { Intent intent = new Intent(getContext(), KVV.class); + getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); mWaitForBound = true; mQueue.add(() -> {}); } @@ -137,9 +154,13 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { sendNotifications(assignments, module.assignments, module.title, Assignment::getTitle, Assignment::getId, module.getID(), ModulePart.ASSIGNMENT, R.string.assignment_updated, R.string.assignment_added, R.string.assignment_removed); + //ArrayList differencesAdd = new ArrayList<>(); + //ArrayList> differencesUpd = new ArrayList<>(); + //ArrayList differencesDel = new ArrayList<>(); sendNotifications(events, module.events, module.title, evt -> evt.getTitle()+" - "+UtilsDate.getModifiedDate(evt.getStartDate()), event -> event.getStartDate() +event.getType()+event.getTitle(), module.getID(), ModulePart.EVENT, - R.string.event_updated, R.string.event_added, R.string.event_removed); + R.string.event_updated, R.string.event_added, R.string.event_removed/*, + differencesAdd, differencesUpd, differencesDel*/); sendNotifications(gradebook, module.gradebook, module.title, Grade::getItemName, Grade::getItemName, module.getID(), ModulePart.GRADEBOOK, R.string.gradebook_updated, R.string.gradebook_added, R.string.gradebook_removed); @@ -153,20 +174,159 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { if (--latch[0] == 0) mQueue.next(); }, true); } + + // Add events to calendar + if (createCalendar(account)) { + iterator = success.latestSemesterIterator(); + while (iterator.hasNext()) { + Modules.Module module = iterator.next(); + addToCalendar(module.events, account); + } + forceSync(); + } }, msg -> { log.e(msg); mQueue.next(); }, true); }); mQueue.add(() -> { + if (mBound) { + getContext().unbindService(mConnection); + } mBound = false; mKVV = null; - getContext().unbindService(mConnection); + mQueue.next(); }); } + static Uri asSyncAdapter(Uri uri, String account, String accountType) { + return uri.buildUpon() + .appendQueryParameter(CALLER_IS_SYNCADAPTER, "true") + .appendQueryParameter(Calendars.ACCOUNT_NAME, account) + .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); + } + + private static Uri createCalendarWithName(Context ctx, Account account, String calendarName) { + String accountName = account.name; + String accountType = account.type; + Uri target = asSyncAdapter(Calendars.CONTENT_URI, accountName, accountType); + + ContentValues values = new ContentValues(); + values.put(Calendars._ID, calendarName.hashCode()); + values.put(Calendars.ACCOUNT_NAME, accountName); + values.put(Calendars.ACCOUNT_TYPE, accountType); + values.put(Calendars.NAME, calendarName); + values.put(Calendars.CALENDAR_DISPLAY_NAME, calendarName); + values.put(Calendars.CALENDAR_COLOR, 0x00FF00); + values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ); + values.put(Calendars.OWNER_ACCOUNT, accountName); + values.put(Calendars.VISIBLE, 1); + values.put(Calendars.SYNC_EVENTS, 1); + values.put(Calendars.CALENDAR_TIME_ZONE, "Europe/Berlin"); + values.put(Calendars.CAN_PARTIALLY_UPDATE, 1); + values.put(Calendars.CAL_SYNC1, System.currentTimeMillis()); + + return ctx.getContentResolver().insert(target, values); + } + + private int createEvents(Context ctx, Account account, String calendarName, EventList events) { + String accountName = account.name; + String accountType = account.type; + Uri target = asSyncAdapter(Events.CONTENT_URI, accountName, accountType); + + ContentValues[] contentValues = new ContentValues[events.size()]; + for (int i = 0; i < contentValues.length; i++) { + Event event = events.get(i); + ContentValues values = new ContentValues(); + //values.put(Events._ID, event.hashCode()); + values.put(Events.TITLE, event.getTitle()); + values.put(Events.DTSTART, event.getStartDate()); + values.put(Events.DTEND, event.getEndDate()); + values.put(Events.CALENDAR_ID, calendarName.hashCode()); + contentValues[i] = values; + //ctx.getContentResolver().insert(target, values); + } + log.d(contentValues.length); + return ctx.getContentResolver().bulkInsert(target, contentValues); + } + + private static Cursor getCalendars(Context ctx, Account account) { + Uri target = asSyncAdapter(Calendars.CONTENT_URI, account.name, account.type); + + return ctx.getContentResolver().query(target, new String[]{Calendars.CALENDAR_DISPLAY_NAME, Calendars.ACCOUNT_NAME}, null, null, null); + } + + private static Cursor getEvents(Context ctx, Account account) { + Uri target = asSyncAdapter(Events.CONTENT_URI, account.name, account.type); + + return ctx.getContentResolver().query(target, new String[]{Events.TITLE, Events.DTSTART}, null, null, null); + } + + private static int deleteCalendars(Context ctx, Account account) { + String accountName = account.name; + String accountType = account.type; + Uri target = asSyncAdapter(Calendars.CONTENT_URI, account.name, account.type); + + ContentValues values = new ContentValues(); + values.put(Calendars.ACCOUNT_NAME, accountName); + values.put(Calendars.ACCOUNT_TYPE, accountType); + + return ctx.getContentResolver().delete(target, null, null); + } + + private void addToCalendar(EventList events, Account account) { + if (events == null) { + return; + } + if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { + boolean integrationEnabled = Preferences.getBoolean(getContext(), R.string.pref_add_calendar); + if (integrationEnabled) { + createEvents(getContext(), account, getContext().getString(R.string.app_name), events); + } + } + } + + private boolean createCalendar(Account account) { + if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) { + boolean integrationEnabled = Preferences.getBoolean(getContext(), R.string.pref_add_calendar); + if (integrationEnabled) { + //log.w("No calendar found! Add calendar..."); + deleteCalendars(getContext(), account); + createCalendarWithName(getContext(), account, getContext().getString(R.string.app_name)); + return true; + } else { + log.w("Calendar found and integration disabled! Delete calendar..."); + deleteCalendars(getContext(), account); + return false; + } + } else { + log.w("Permission calendar not granted!"); + return false; + } + } + + private void forceSync() { + // Force a sync + Bundle extras = new Bundle(); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + AccountManager am = AccountManager.get(getContext()); + Account[] acc = am.getAccountsByType("com.google"); + Account account = null; + if (acc.length>0) { + account = acc[0]; + ContentResolver.requestSync(account, "com.android.calendar", extras); + } + } + private void sendNotifications(Iterable oldList, Iterable newList, String title, StringInterface titleInterface, StringInterface idInterface, String moduleId, int modulePart, @StringRes int updateRes, @StringRes int addRes, @StringRes int removeRes) { - if (oldList == null || newList == null) { + sendNotifications(oldList, newList, title, titleInterface, idInterface, moduleId, modulePart, updateRes, addRes, removeRes, null, null, null); + } + + private void sendNotifications(Iterable oldList, Iterable newList, String title, StringInterface titleInterface, StringInterface idInterface, String moduleId, int modulePart, @StringRes int updateRes, @StringRes int addRes, @StringRes int removeRes, ArrayList changesAdd, ArrayList> changesUpd, ArrayList changesDel) { + if (oldList == null || newList == null) { return; } ArrayList obsoletes = new ArrayList<>(); @@ -181,6 +341,9 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { found = true; if (newEntry.hashCode() != oldEntry.hashCode()) { CustomNotificationManager.sendNotification(getContext(), getContext().getString(updateRes, title), titleInterface.get(newEntry), FRAGMENT_MODULES_DETAILS, targetData); + if (changesUpd != null) { + changesUpd.add(new Pair<>(oldEntry, newEntry)); + } } obsoletes.remove(oldEntry); break; @@ -188,10 +351,16 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter { } if (!found) { CustomNotificationManager.sendNotification(getContext(), getContext().getString(addRes, title), titleInterface.get(newEntry), FRAGMENT_MODULES_DETAILS, targetData); + if (changesAdd != null) { + changesAdd.add(newEntry); + } } } for (T oldEntry: obsoletes) { CustomNotificationManager.sendNotification(getContext(), getContext().getString(removeRes, title), titleInterface.get(oldEntry), FRAGMENT_MODULES_DETAILS, targetData); + if (changesDel != null) { + changesDel.add(oldEntry); + } } } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheBBCourse.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheBBCourse.java index 4a3eeaf..8f2ad08 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheBBCourse.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheBBCourse.java @@ -3,6 +3,7 @@ package de.sebse.fuplanner.services.kvv.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; @@ -23,7 +24,12 @@ public class CacheBBCourse implements Serializable { private HashMap mBBCourseListRefresh = new HashMap<>(); public static CacheBBCourse load(Context context) throws IOException, ClassNotFoundException { - FileInputStream fis = context.openFileInput(FILE_NAME); + FileInputStream fis; + try { + fis = context.openFileInput(FILE_NAME); + } catch (FileNotFoundException e) { + return null; + } ObjectInputStream is = new ObjectInputStream(fis); Object readObject = is.readObject(); if (!(readObject instanceof CacheBBCourse)) @@ -76,8 +82,8 @@ public class CacheBBCourse implements Serializable { return mKVVCourseList.contains(courseID); } - public void setBBCourse(String courseID, Modules.Module lecturer) { - mBBCourseList.put(courseID, lecturer); + public void setBBCourse(String courseID, Modules.Module module) { + mBBCourseList.put(courseID, module); mBBCourseListRefresh.put(courseID, System.currentTimeMillis()); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java index c8b029d..914d83b 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheKVVCourse.java @@ -3,6 +3,7 @@ package de.sebse.fuplanner.services.kvv.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; @@ -20,7 +21,12 @@ public class CacheKVVCourse implements Serializable { private HashMap mKVVCourseListRefresh = new HashMap<>(); public static CacheKVVCourse load(Context context) throws IOException, ClassNotFoundException { - FileInputStream fis = context.openFileInput(FILE_NAME); + FileInputStream fis; + try { + fis = context.openFileInput(FILE_NAME); + } catch (FileNotFoundException e) { + return null; + } ObjectInputStream is = new ObjectInputStream(fis); Object readObject = is.readObject(); if (!(readObject instanceof CacheKVVCourse)) @@ -62,8 +68,8 @@ public class CacheKVVCourse implements Serializable { fos.close(); } - public void setKVVCourse(String courseID, Modules.Module lecturer) { - mKVVCourseList.put(courseID, lecturer); + public void setKVVCourse(String courseID, Modules.Module module) { + mKVVCourseList.put(courseID, module); mKVVCourseListRefresh.put(courseID, System.currentTimeMillis()); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheLecturer.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheLecturer.java index c079574..09b2fb5 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheLecturer.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/CacheLecturer.java @@ -3,6 +3,7 @@ package de.sebse.fuplanner.services.kvv.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; @@ -23,7 +24,12 @@ public class CacheLecturer implements Serializable { private HashMap mLecturersRefresh = new HashMap<>(); public static CacheLecturer load(Context context) throws IOException, ClassNotFoundException { - FileInputStream fis = context.openFileInput(FILE_NAME); + FileInputStream fis; + try { + fis = context.openFileInput(FILE_NAME); + } catch (FileNotFoundException e) { + return null; + } ObjectInputStream is = new ObjectInputStream(fis); Object readObject = is.readObject(); if (!(readObject instanceof CacheLecturer)) diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Event.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Event.java index 6dee558..952fd60 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Event.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Event.java @@ -1,5 +1,8 @@ package de.sebse.fuplanner.services.kvv.types; +import android.content.Context; +import android.content.res.Configuration; + import com.google.android.gms.common.internal.Objects; import java.io.Serializable; @@ -8,6 +11,9 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatDelegate; + import de.sebse.fuplanner.tools.ColorRGB; public class Event implements Serializable { @@ -60,7 +66,7 @@ public class Event implements Serializable { return siteId; } - public ColorRGB getColor() { + public ColorRGB getColor(@Nullable Context context) { MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-256"); @@ -79,8 +85,21 @@ public class Event implements Serializable { // range for more beautiful colors h = h / 30 * 30; + + boolean nightMode = false; + if (context != null) { + int currentNightMode = context.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK; + if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) { + nightMode = true; + } + } int s = 100; int v = 80; + if (nightMode) { + s = 60; + v = 100; + } return hsvToRgb(h/360.0, s/100.0, v/100.0); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenBB.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenBB.java index a3d8ebf..200fc05 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenBB.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenBB.java @@ -44,6 +44,10 @@ public class LoginTokenBB { }, errorCallback); } + public static boolean hasAccounts(CustomAccountManager manager) { + return manager.hasAccounts(AccountGeneral.ACCOUNT_TYPE); + } + public void delete(CustomAccountManager manager) { manager.deleteAccount(AccountGeneral.ACCOUNT_TYPE); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenKVV.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenKVV.java index e0acf83..190ed3b 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenKVV.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/LoginTokenKVV.java @@ -42,6 +42,10 @@ public class LoginTokenKVV { }, errorCallback); } + public static boolean hasAccounts(CustomAccountManager manager) { + return manager.hasAccounts(AccountGeneral.ACCOUNT_TYPE); + } + public void delete(CustomAccountManager manager) { manager.deleteAccount(AccountGeneral.ACCOUNT_TYPE); } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Semester.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Semester.java index 80823cb..c613e54 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Semester.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/types/Semester.java @@ -2,6 +2,7 @@ package de.sebse.fuplanner.services.kvv.types; import java.io.Serializable; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import de.sebse.fuplanner.tools.Regex; @@ -52,4 +53,10 @@ public class Semester implements Serializable { } return false; } + + @NonNull + @Override + public String toString() { + return (type == SEM_SS ? "SS" : "WS") + year; + } } diff --git a/app/src/main/java/de/sebse/fuplanner/services/kvv/ui/Download.java b/app/src/main/java/de/sebse/fuplanner/services/kvv/ui/Download.java index 8f39440..ae70fd9 100644 --- a/app/src/main/java/de/sebse/fuplanner/services/kvv/ui/Download.java +++ b/app/src/main/java/de/sebse/fuplanner/services/kvv/ui/Download.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; +import android.os.Build; import android.os.Environment; import java.io.File; @@ -47,8 +48,14 @@ public class Download { if (context == null) return; AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); - File f = new File(Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS)+"/"+folderName+"/"+file.getTitle()); + File f; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + f = new File(context.getExternalFilesDir( + Environment.DIRECTORY_DOWNLOADS) + "/" + folderName + "/" + file.getTitle()); + } else { + f = new File(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS) + "/" + folderName + "/" + file.getTitle()); + } Resources resources = context.getResources(); String message = ""; if (file.getAuthor() != null && !file.getAuthor().isEmpty()) diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml index 5e3f758..71bed9f 100644 --- a/app/src/main/res/layout/app_bar_main.xml +++ b/app/src/main/res/layout/app_bar_main.xml @@ -16,7 +16,8 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/fuToolbarBackground" - app:popupTheme="@style/FUTheme.PopupOverlay" /> + app:popupTheme="@style/FUTheme.PopupOverlay" + android:theme="@style/FUTheme.Toolbar" /> Angestellter Andere - + Jede Stunde Alle 2 Stunden diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4143a91..3d3378a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -52,6 +52,8 @@ Synchronisationshäufigkeit Stellt Häufigkeit der automatischen Synchronisation ein Sync-Frequenz + Zum Kalender hinzufügen + Wenn ausgewählt, wird Hauptgerichte Spezial Gerichte Beilagen diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index a327fb9..ccc01d8 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -8,6 +8,7 @@ @color/colorFUGrayDark2 @color/colorFUBlack @color/colorFUGreenDark + @color/colorFUWhite @color/colorFURedDark @color/colorFURedDark2 @color/colorFUBlack diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml deleted file mode 100644 index cc38383..0000000 --- a/app/src/main/res/values/arrays.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - All - Student - Employee - Other - - - all - student - employee - other - - - - Every hour - Every 2 hours - Every 4 hours - Every 6 hours - Every 12 hours - Every 24 hours - - - 1 - 2 - 4 - 6 - 12 - 24 - - - - Show every meal - Show only vegetarian meals - Show only vegan meals - - - all - vegetarian - vegan - - - - Follow system default - Always day mode - Always night mode - - - auto - day - night - - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b4416a0..3e426d1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -22,9 +22,10 @@ @color/colorFUBlue @color/colorFUGray - @color/colorFUGrayDark2 + @color/colorFUGrayDark @color/colorFUWhite @color/colorFUGreen + @color/colorFUGreenDark @color/colorFURedLight2 @color/colorFURedLight @color/colorFUGray diff --git a/app/src/main/res/values/preferences.xml b/app/src/main/res/values/preferences.xml index 39cac80..0f2a829 100644 --- a/app/src/main/res/values/preferences.xml +++ b/app/src/main/res/values/preferences.xml @@ -1,6 +1,18 @@ + + All + Student + Employee + Other + + + all + student + employee + other + @string/pref_price_group @string/pref_price_group_default @@ -8,6 +20,22 @@ pref_price_group all + + Every hour + Every 2 hours + Every 4 hours + Every 6 hours + Every 12 hours + Every 24 hours + + + 1 + 2 + 4 + 6 + 12 + 24 + @string/pref_sync_frequency @string/pref_sync_frequency_default @@ -15,6 +43,16 @@ pref_sync_frequency 6 + + Show every meal + Show only vegetarian meals + Show only vegan meals + + + all + vegetarian + vegan + @string/pref_food_level @string/pref_food_level_default @@ -22,6 +60,16 @@ pref_food_level all + + Follow system default + Always day mode + Always night mode + + + auto + day + night + @string/pref_night_mode @string/pref_night_mode_default @@ -29,6 +77,9 @@ pref_night_mode auto + pref_add_calendar + false + pref_last_visited_news diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4a2920..a198dcd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,7 @@ Schedule Courses Canteen Plan - Settings + Preferences Options Log out Share @@ -59,6 +59,8 @@ Sync frequency Set automatic background sync frequency Frequency Selection + Add to Calendar + If checked schedule will be added to your calendar app Meals Special meals Side Dishes diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d4c05f4..53f98cc 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -3,10 +3,16 @@ +