Compare commits

...

39 Commits

Author SHA1 Message Date
1acf1547d5 [3.9.13-dev] 2019-12-02 22:24:55 +01:00
5d3de35c10 [UI/Timetable] Fix non-null cast exception. 2019-12-02 22:20:53 +01:00
8f8d613f6e [UI] Update libraries & NavLib to fix missing badges on most profiles. 2019-12-02 22:10:36 +01:00
6a161b3c97 [UI/Timetable] Add showing event indicators on lessons. 2019-12-02 21:25:18 +01:00
3e97572100 [Dialog/Events] Use new event dialog in homework fragment and event list dialog. 2019-12-02 19:36:19 +01:00
fc3b6fd1e0 [UI/Event] Fix stuff because i'm dumb 2019-12-02 19:07:30 +01:00
9bc7f9ac11 [UI/Event] Make use of default values in event manual dialog. 2019-12-02 19:04:30 +01:00
0a2f252405 [UI/Home] Implement home card swapping and saving. 2019-12-02 18:12:52 +01:00
09bc658f97 [UI] Fix blank screen when loading activity. 2019-12-02 18:11:12 +01:00
7b04202a00 [Config] Add the rest of config. Migrate from AppConfig and remove most values from AppConfig. 2019-12-01 22:35:42 +01:00
acf364166b [Notifications/LessonChange] Fix lesson change notifications text. 2019-12-01 21:54:59 +01:00
4e88efae94 [Home/Grades] Fix padding. 2019-12-01 20:25:50 +01:00
8df24dc1c4 [Dialog/Events] Create adapter outside of the observer. 2019-12-01 20:25:28 +01:00
8482c27689 [Timetable] Add marking lessons as seen in timetable. 2019-12-01 20:23:48 +01:00
d1265dc1f2 [Dialog/Events] Make new event list dialog. 2019-11-30 23:33:20 +01:00
47d395de71 [Home] Add home grades card. 2019-11-29 23:16:50 +01:00
5b443e02a3 [Mobidziennik] Fix getting grades with no category. Add support for comments. 2019-11-29 18:59:47 +01:00
f8a7d52b1d [Mobidziennik] Fix extracting when attachment has no size. 2019-11-29 17:51:16 +01:00
a133a96819 [UI/Messages] Make message list scroll to last opened message. 2019-11-29 17:29:23 +01:00
c71b8f994c [Messages] Implement Mobidziennik attachments. Fix multiplying attachments in UI. 2019-11-28 23:32:10 +01:00
9b02c97926 [UI/Messages] Convert adapter to Kotlin. 2019-11-28 23:12:21 +01:00
ab06efc934 [Librus/Attachment] A huge structure reformat. 2019-11-28 23:00:25 +01:00
928b73f139 [Config] Implement per-profile config. Update timetable card. 2019-11-28 21:45:27 +01:00
a36fb09bc3 [Dialog/Event] Add event adding in the new event manual dialog. 2019-11-28 15:11:23 +01:00
eaed4b76aa [Timetable] Fix showing question marks in lesson changes. 2019-11-26 23:11:14 +01:00
6d8960f089 [Home/Timetable] Add click listener to timetable card. 2019-11-26 22:56:43 +01:00
ca3b6d0705 [APIv2/Idziennik] Fix getting subject and rewrite getting proposed grades. 2019-11-26 22:28:52 +01:00
c2e7931ea6 [Config] Implement (basic) new app config storage. 2019-11-26 21:55:04 +01:00
d1a5d8cba9 [APIv2/Vulcan] Fix start time in events. 2019-11-26 21:44:35 +01:00
c2f91e6867 [APIv2] Move AppError to API directory. 2019-11-26 21:10:31 +01:00
55e32b8d88 [APIv2/Librus] Temporarily fix subject in attendance. 2019-11-26 21:02:33 +01:00
462b1df767 [3.9.12-dev] 2019-11-25 22:55:09 +01:00
d17d2c8417 [APIv2/Librus] Fix looking for the lesson in getting homework. 2019-11-25 22:53:55 +01:00
6892832fff [APIv2/Idziennik] Add getting homework and rewrite getting exams. 2019-11-25 22:53:55 +01:00
66d54c7c45 [APIv2/Librus] Fix messages login. 2019-11-25 22:40:14 +01:00
d432685aa8 [Update] Fix update downloading from notification. 2019-11-25 22:23:55 +01:00
37f3d76fb8 [UI] Implement home timetable card. 2019-11-25 22:17:08 +01:00
7961a74995 [APIv2/Events] Fix fetching events and homework. Add DataRemoveModel for events. 2019-11-25 21:13:55 +01:00
9d590508ad [APIv2/Librus] Fix messages session ID extraction. 2019-11-25 15:00:51 +01:00
128 changed files with 3407 additions and 1214 deletions

2
.idea/misc.xml generated
View File

@ -50,7 +50,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -24,6 +24,7 @@
-keep class pl.szczodrzynski.edziennik.utils.models.** { *; }
-keep class pl.szczodrzynski.edziennik.data.db.modules.events.Event { *; }
-keep class pl.szczodrzynski.edziennik.data.db.modules.events.EventFull { *; }
-keep class pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel { *; }
-keepclassmembers class pl.szczodrzynski.edziennik.widgets.WidgetConfig { public *; }
-keepnames class pl.szczodrzynski.edziennik.WidgetTimetable
-keepnames class pl.szczodrzynski.edziennik.notifications.WidgetNotifications

View File

@ -0,0 +1,13 @@
<!--
~ Copyright (c) Kuba Szczodrzyński 2019-11-25.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,8A4,4 0,0 1,16 12A4,4 0,0 1,12 16A4,4 0,0 1,8 12A4,4 0,0 1,12 8M12,10A2,2 0,0 0,10 12A2,2 0,0 0,12 14A2,2 0,0 0,14 12A2,2 0,0 0,12 10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z"/>
</vector>

View File

@ -18,7 +18,6 @@ import android.os.Handler;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
@ -68,9 +67,9 @@ import me.leolin.shortcutbadger.ShortcutBadger;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import pl.szczodrzynski.edziennik.config.Config;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.network.NetworkUtils;
@ -83,9 +82,7 @@ import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.AppConfig;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_VULCAN;
public class App extends androidx.multidex.MultiDexApplication implements Configuration.Provider {
private static final String TAG = "App";
@ -145,6 +142,11 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
//public Register register; // REGISTER for current profile, read from registerStore
public ProfileFull profile;
public Config config;
private static Config mConfig;
public static Config getConfig() {
return mConfig;
}
// other stuff
public Gson gson;
@ -194,6 +196,10 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
gson = new Gson();
networkUtils = new NetworkUtils(this);
config = new Config(db);
config.migrate(this);
mConfig = config;
Iconics.init(getApplicationContext());
Iconics.registerFont(SzkolnyFont.INSTANCE);
@ -208,7 +214,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
loadConfig();
Themes.INSTANCE.setThemeInt(appConfig.appTheme);
Themes.INSTANCE.setThemeInt(config.getUi().getTheme());
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
@ -227,7 +233,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
if ("f054761fbdb6a238".equals(deviceId) || BuildConfig.DEBUG) {
devMode = true;
}
else if (appConfig.devModePassword != null) {
else if (config.getDevModePassword() != null) {
checkDevModePassword();
}
@ -298,7 +304,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
//profileLoadById(appSharedPrefs.getInt("current_profile_id", 1));
if (appConfig.registerSyncEnabled) {
if (config.getSync().getEnabled()) {
SyncWorker.Companion.scheduleNext(this, false);
}
else {
@ -362,11 +368,10 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
shortcutManager.setDynamicShortcuts(Arrays.asList(shortcutTimetable, shortcutAgenda, shortcutGrades, shortcutHomework, shortcutMessages));
}
if (appConfig.appInstalledTime == 0) {
if (config.getAppInstalledTime() == 0) {
try {
appConfig.appInstalledTime = getPackageManager().getPackageInfo(getPackageName(), 0).firstInstallTime;
appConfig.appRateSnackbarTime = appConfig.appInstalledTime + 7 * 24 * 60 * 60 * 1000;
saveConfig("appInstalledTime", "appRateSnackbarTime");
config.setAppInstalledTime(getPackageManager().getPackageInfo(getPackageName(), 0).firstInstallTime);
config.setAppRateSnackbarTime(config.getAppInstalledTime() + 7 * 24 * 60 * 60 * 1000);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
@ -434,9 +439,9 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
final long startTime = System.currentTimeMillis();
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(instanceIdResult -> {
Log.d(TAG, "Token for App is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()+". Time is "+(System.currentTimeMillis() - startTime));
appConfig.fcmToken = instanceIdResult.getToken();
config.getSync().setTokenApp(instanceIdResult.getToken());
});
FirebaseInstanceId.getInstance(pushMobidziennikApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
/*FirebaseInstanceId.getInstance(pushMobidziennikApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
Log.d(TAG, "Token for Mobidziennik is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId());
appConfig.fcmTokens.put(LOGIN_TYPE_MOBIDZIENNIK, new Pair<>(instanceIdResult.getToken(), new ArrayList<>()));
});
@ -450,7 +455,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
if (pair == null || pair.first == null || !pair.first.equals(instanceIdResult.getToken())) {
appConfig.fcmTokens.put(LOGIN_TYPE_VULCAN, new Pair<>(instanceIdResult.getToken(), new ArrayList<>()));
}
});
});*/
FirebaseMessaging.getInstance().subscribeToTopic(getPackageName());
@ -513,7 +518,8 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
appSharedPrefs.edit().remove("app.appConfig."+fieldName).apply();
Log.w(TAG, "Should remove app.appConfig."+fieldName);
//appSharedPrefs.edit().remove("app.appConfig."+fieldName).apply(); TODO migration
}
}
}
@ -585,7 +591,11 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
//appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply();
}
public void profileSave() {
AsyncTask.execute(() -> {
db.profileDao().add(profile);
});
}
public void profileSaveAsync() {
AsyncTask.execute(() -> {
@ -606,14 +616,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
db.profileDao().add(profileFull);
db.loginStoreDao().add(profileFull);
}
public void profileSaveFull(Profile profile, LoginStore loginStore) {
db.profileDao().add(profile);
db.loginStoreDao().add(loginStore);
}
public ProfileFull profileGetOrNull(int id) {
return db.profileDao().getFullByIdNow(id);
}
public void profileLoadById(int id) {
profileLoadById(id, false);
@ -636,6 +638,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1);
profileId = profile.getId();
appSharedPrefs.edit().putInt("current_profile_id", profile.getId()).apply();
config.setProfile(profileId);
}
else if (!loadedLast) {
profileLoadById(profileLastId(), true);
@ -706,7 +709,7 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
public void checkDevModePassword() {
try {
devMode = Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", appConfig.devModePassword).equals("ok here you go it's enabled now")
devMode = Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", config.getDevModePassword()).equals("ok here you go it's enabled now")
|| BuildConfig.DEBUG;
} catch (Exception e) {
e.printStackTrace();

View File

@ -0,0 +1,319 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik
import android.util.Log
import androidx.work.Configuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext
class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScope {
companion object {
var devMode = false
}
//lateinit var db: AppDb
//val config by lazy { Config(db); // TODO migrate }
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun getWorkManagerConfiguration() = Configuration.Builder()
.setMinimumLoggingLevel(Log.VERBOSE)
.build()
/*val preferences by lazy { getSharedPreferences(getString(R.string.preference_file), Context.MODE_PRIVATE) }
val notifier by lazy { Notifier(this) }
val permissionChecker by lazy { PermissionChecker(this) }
lateinit var profile: ProfileFull
/* _ _ _______ _______ _____
| | | |__ __|__ __| __ \
| |__| | | | | | | |__) |
| __ | | | | | | ___/
| | | | | | | | | |
|_| |_| |_| |_| |*/
val http: OkHttpClient by lazy {
val builder = OkHttpClient.Builder()
.cache(null)
.followRedirects(true)
.followSslRedirects(true)
.retryOnConnectionFailure(true)
.cookieJar(cookieJar)
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
builder.installHttpsSupport()
if (devMode || BuildConfig.DEBUG) {
HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this))
val chuckerCollector = ChuckerCollector(this, true, Period.ONE_HOUR)
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
}
builder.build()
}
val httpLazy: OkHttpClient by lazy {
http.newBuilder()
.followRedirects(false)
.followSslRedirects(false)
.build()
}
val cookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(this)) }
/* _____ _ _
/ ____(_) | |
| (___ _ __ _ _ __ __ _| |_ _ _ _ __ ___
\___ \| |/ _` | '_ \ / _` | __| | | | '__/ _ \
____) | | (_| | | | | (_| | |_| |_| | | | __/
|_____/|_|\__, |_| |_|\__,_|\__|\__,_|_| \___|
__/ |
|__*/
private val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" }
private val signature: String by lazy {
var str = ""
try {
val packageInfo: PackageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
for (signature in packageInfo.signatures) {
val signatureBytes = signature.toByteArray()
val md = MessageDigest.getInstance("SHA")
md.update(signatureBytes)
str = Base64.encodeToString(md.digest(), Base64.DEFAULT)
}
} catch (e: Exception) {
e.printStackTrace()
}
str
}
private var unreadBadgesAvailable = true
/* _____ _
/ ____| | |
___ _ __ | | _ __ ___ __ _| |_ ___
/ _ \| '_ \| | | '__/ _ \/ _` | __/ _ \
| (_) | | | | |____| | | __/ (_| | || __/
\___/|_| |_|\_____|_| \___|\__,_|\__\__*/
override fun onCreate() {
super.onCreate()
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
CaocConfig.Builder.create()
.backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM)
.enabled(true)
.showErrorDetails(true)
.showRestartButton(true)
.logErrorOnRestart(true)
.trackActivities(true)
.minTimeBetweenCrashesMs(60*1000)
.errorDrawable(R.drawable.ic_rip)
.restartActivity(MainActivity::class.java)
.errorActivity(CrashActivity::class.java)
.apply()
Iconics.init(applicationContext)
Iconics.registerFont(SzkolnyFont)
db = AppDb.getDatabase(this)
Themes.themeInt = config.ui.theme
MHttp.instance().customOkHttpClient(http)
devMode = "f054761fbdb6a238" == deviceId || BuildConfig.DEBUG
if (config.devModePassword != null)
checkDevModePassword()
launch { async(Dispatchers.Default) {
if (config.sync.enabled) {
scheduleNext(this@App, false)
} else {
cancelNext(this@App)
}
db.metadataDao().countUnseen().observeForever { count: Int ->
if (unreadBadgesAvailable)
unreadBadgesAvailable = ShortcutBadger.applyCount(this@App, count)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val shortcutManager = getSystemService(ShortcutManager::class.java)
val shortcutTimetable = ShortcutInfo.Builder(this@App, "item_timetable")
.setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE))
.build()
val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda")
.setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA))
.build()
val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades")
.setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES))
.build()
val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks")
.setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK))
.build()
val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages")
.setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages))
.setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages))
.setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES))
.build()
shortcutManager.dynamicShortcuts = listOf(
shortcutTimetable,
shortcutAgenda,
shortcutGrades,
shortcutHomework,
shortcutMessages
)
} // shortcuts - end
if (config.appInstalledTime == 0L)
try {
config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
config.appRateSnackbarTime = config.appInstalledTime + 7*DAY*MS
} catch (e: NameNotFoundException) {
e.printStackTrace()
}
val pushMobidziennikApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE")
.setApplicationId("1:747285019373:android:f6341bf7b158621d")
.build(),
"Mobidziennik2"
)
val pushLibrusApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o")
.setApplicationId("1:513056078587:android:1e29083b760af544")
.build(),
"Librus"
)
val pushVulcanApp = FirebaseApp.initializeApp(
this@App,
FirebaseOptions.Builder()
.setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA")
.setApplicationId("1:987828170337:android:ac97431a0a4578c3")
.build(),
"Vulcan"
)
try {
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
config.sync.tokenApp = token
}
FirebaseInstanceId.getInstance(pushMobidziennikApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenMobidziennik) {
config.sync.tokenMobidziennik = token
config.sync.tokenMobidziennikList = listOf()
}
}
FirebaseInstanceId.getInstance(pushLibrusApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenLibrus) {
config.sync.tokenLibrus = token
config.sync.tokenLibrusList = listOf()
}
}
FirebaseInstanceId.getInstance(pushVulcanApp).instanceId.addOnSuccessListener { instanceIdResult ->
val token = instanceIdResult.token
if (token != config.sync.tokenVulcan) {
config.sync.tokenVulcan = token
config.sync.tokenVulcanList = listOf()
}
}
FirebaseMessaging.getInstance().subscribeToTopic(packageName)
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}}
}
private fun profileLoad(profileId: Int) {
db.profileDao().getFullByIdNow(profileId)?.also {
profile = it
} ?: run {
if (!::profile.isInitialized) {
profile = ProfileFull(-1, "", "", -1)
}
}
}
fun profileLoad(profileId: Int, onSuccess: (profile: ProfileFull) -> Unit) {
launch {
val deferred = async(Dispatchers.Default) {
profileLoad(profileId)
}
deferred.await()
onSuccess(profile)
}
}
private fun OkHttpClient.Builder.installHttpsSupport() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
try {
try {
ProviderInstaller.installIfNeeded(this@App)
} catch (e: Exception) {
Log.e("OkHttpTLSCompat", "Play Services not found or outdated")
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val x509TrustManager = trustManagerFactory.trustManagers.singleOrNull { it is X509TrustManager } as X509TrustManager?
?: return
val sc = SSLContext.getInstance("TLSv1.2")
sc.init(null, null, null)
sslSocketFactory(TLSSocketFactory(sc.socketFactory), x509TrustManager)
val cs: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_0)
.tlsVersions(TlsVersion.TLS_1_1)
.tlsVersions(TlsVersion.TLS_1_2)
.build()
val specs: MutableList<ConnectionSpec> = ArrayList()
specs.add(cs)
specs.add(ConnectionSpec.COMPATIBLE_TLS)
specs.add(ConnectionSpec.CLEARTEXT)
connectionSpecs(specs)
}
} catch (exc: Exception) {
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc)
}
}
}
fun checkDevModePassword() {
devMode = try {
Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", config.devModePassword) == "ok here you go it's enabled now" || BuildConfig.DEBUG
} catch (e: Exception) {
e.printStackTrace()
false
}
}*/
}

View File

@ -5,6 +5,8 @@ import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
@ -36,7 +38,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.navlib.R
import pl.szczodrzynski.navlib.getColorFromAttr
import pl.szczodrzynski.navlib.getColorFromRes
import java.text.SimpleDateFormat
import java.util.*
@ -201,6 +203,7 @@ const val DAY = 24L*HOUR
const val WEEK = 7L*DAY
const val MONTH = 30L*DAY
const val YEAR = 365L*DAY
const val MS = 1000L
fun <T> LongSparseArray<T>.values(): List<T> {
val result = mutableListOf<T>()
@ -379,13 +382,13 @@ fun CharSequence?.asItalicSpannable(): Spannable {
*/
fun <T : CharSequence> listOfNotEmpty(vararg elements: T): List<T> = elements.filterNot { it.isEmpty() }
fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
fun List<CharSequence?>.concat(delimiter: String? = null): CharSequence {
if (this.isEmpty()) {
return ""
}
if (this.size == 1) {
return this[0]
return this[0] ?: ""
}
var spanned = false
@ -400,6 +403,8 @@ fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
if (spanned) {
val ssb = SpannableStringBuilder()
for (piece in this) {
if (piece == null)
continue
if (!first && delimiter != null)
ssb.append(delimiter)
first = false
@ -409,6 +414,8 @@ fun List<CharSequence>.concat(delimiter: String? = null): CharSequence {
} else {
val sb = StringBuilder()
for (piece in this) {
if (piece == null)
continue
if (!first && delimiter != null)
sb.append(delimiter)
first = false
@ -533,3 +540,69 @@ operator fun Time?.compareTo(other: Time?): Int {
operator fun StringBuilder.plusAssign(str: String?) {
this.append(str)
}
fun Context.timeTill(time: Int, delimiter: String = " "): String {
val parts = mutableListOf<Pair<Int, Int>>()
val hours = time / 3600
val minutes = (time - hours*3600) / 60
val seconds = time - minutes*60 - hours*3600
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to hours; prefixAdded = true
parts += R.plurals.time_till_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_till_text to minutes; prefixAdded = true
parts += R.plurals.time_till_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_till_text to seconds; prefixAdded = true
parts += R.plurals.time_till_seconds to seconds
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}
fun Context.timeLeft(time: Int, delimiter: String = " "): String {
val parts = mutableListOf<Pair<Int, Int>>()
val hours = time / 3600
val minutes = (time - hours*3600) / 60
val seconds = time - minutes*60 - hours*3600
var prefixAdded = false
if (hours > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to hours
prefixAdded = true
parts += R.plurals.time_left_hours to hours
}
if (minutes > 0) {
if (!prefixAdded) parts += R.plurals.time_left_text to minutes
prefixAdded = true
parts += R.plurals.time_left_minutes to minutes
}
if (hours == 0 && minutes < 10) {
if (!prefixAdded) parts += R.plurals.time_left_text to seconds
prefixAdded = true
parts += R.plurals.time_left_seconds to seconds
}
return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) }
}
inline fun <reified T> Any?.instanceOfOrNull(): T? {
return when (this) {
is T -> this
else -> null
}
}
fun Drawable.setTintColor(color: Int): Drawable {
colorFilter = PorterDuffColorFilter(
color,
PorterDuff.Mode.SRC_ATOP
)
return this
}

View File

@ -243,7 +243,7 @@ class MainActivity : AppCompatActivity() {
setTheme(Themes.appTheme)
app.appConfig.language?.let {
app.config.ui.language?.let {
setLanguage(it)
}
@ -306,10 +306,10 @@ class MainActivity : AppCompatActivity() {
}
drawer.apply {
setAccountHeaderBackground(app.appConfig.headerBackground)
setAccountHeaderBackground(app.config.ui.headerBackground)
drawerProfileListEmptyListener = {
app.appConfig.loginFinished = false
app.config.loginFinished = false
app.saveConfig("loginFinished")
profileListEmptyListener()
}
@ -334,7 +334,7 @@ class MainActivity : AppCompatActivity() {
drawerProfileSettingClickListener = this@MainActivity.profileSettingClickListener
miniDrawerVisibleLandscape = null
miniDrawerVisiblePortrait = app.appConfig.miniDrawerVisible
miniDrawerVisiblePortrait = app.config.ui.miniMenuVisible
}
}
@ -387,21 +387,23 @@ class MainActivity : AppCompatActivity() {
SyncWorker.scheduleNext(app)
// APP BACKGROUND
if (app.appConfig.appBackground != null) {
if (app.config.ui.appBackground != null) {
try {
var bg = app.appConfig.appBackground
val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg")
if (bgDir.exists()) {
val files = bgDir.listFiles()
val r = Random()
val i = r.nextInt(files.size)
bg = files[i].toString()
}
val linearLayout = b.root
if (bg.endsWith(".gif")) {
linearLayout.background = GifDrawable(bg)
} else {
linearLayout.background = BitmapDrawable.createFromPath(bg)
app.config.ui.appBackground?.let {
var bg = it
val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg")
if (bgDir.exists()) {
val files = bgDir.listFiles()
val r = Random()
val i = r.nextInt(files.size)
bg = files[i].toString()
}
val linearLayout = b.root
if (bg.endsWith(".gif")) {
linearLayout.background = GifDrawable(bg)
} else {
linearLayout.background = BitmapDrawable.createFromPath(bg)
}
}
} catch (e: IOException) {
e.printStackTrace()
@ -409,7 +411,7 @@ class MainActivity : AppCompatActivity() {
}
// WHAT'S NEW DIALOG
if (app.appConfig.lastAppVersion != BuildConfig.VERSION_CODE) {
if (app.config.appVersion < BuildConfig.VERSION_CODE) {
ServerRequest(app, app.requestScheme + APP_URL + "main.php?just_updated", "MainActivity/JU")
.run { e, result ->
Handler(Looper.getMainLooper()).post {
@ -420,17 +422,16 @@ class MainActivity : AppCompatActivity() {
}
}
}
if (app.appConfig.lastAppVersion < 170) {
if (app.config.appVersion < 170) {
//Intent intent = new Intent(this, ChangelogIntroActivity.class);
//startActivity(intent);
} else {
app.appConfig.lastAppVersion = BuildConfig.VERSION_CODE
app.saveConfig("lastAppVersion")
app.config.appVersion = BuildConfig.VERSION_CODE
}
}
// RATE SNACKBAR
if (app.appConfig.appRateSnackbarTime != 0L && app.appConfig.appRateSnackbarTime <= System.currentTimeMillis()) {
if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) {
navView.coordinator.postDelayed({
CafeBar.builder(this)
.content(R.string.rate_snackbar_text)
@ -444,20 +445,17 @@ class MainActivity : AppCompatActivity() {
.onPositive { cafeBar ->
Utils.openGooglePlay(this)
cafeBar.dismiss()
app.appConfig.appRateSnackbarTime = 0
app.saveConfig("appRateSnackbarTime")
app.config.appRateSnackbarTime = 0
}
.onNegative { cafeBar ->
Toast.makeText(this, "Szkoda, opinie innych pomagają mi rozwijać aplikację.", Toast.LENGTH_LONG).show()
cafeBar.dismiss()
app.appConfig.appRateSnackbarTime = 0
app.saveConfig("appRateSnackbarTime")
app.config.appRateSnackbarTime = 0
}
.onNeutral { cafeBar ->
Toast.makeText(this, "OK", Toast.LENGTH_LONG).show()
cafeBar.dismiss()
app.appConfig.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000
app.saveConfig("appRateSnackbarTime")
app.config.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000
}
.autoDismiss(false)
.swipeToDismiss(true)
@ -689,14 +687,13 @@ class MainActivity : AppCompatActivity() {
}*/
if (navLoading) {
navLoading = false
b.fragment.removeAllViews()
if (intentTargetId == -1)
intentTargetId = HOME_ID
}
when {
app.profile == null -> {
app.profile == null || app.profile.id == -1 -> {
if (intentProfileId == -1)
intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1)
loadProfile(intentProfileId, intentTargetId, extras)
@ -709,13 +706,14 @@ class MainActivity : AppCompatActivity() {
}
intentTargetId != -1 -> {
drawer.currentProfile = app.profile.id
if (navTargetId != intentTargetId)
if (navTargetId != intentTargetId || navLoading)
loadTarget(intentTargetId, extras)
}
else -> {
drawer.currentProfile = app.profile.id
}
}
navLoading = false
}
override fun recreate() {
@ -763,7 +761,7 @@ class MainActivity : AppCompatActivity() {
finish()
}
else {
if (!app.appConfig.loginFinished)
if (!app.config.loginFinished)
finish()
else {
handleIntent(data?.extras)
@ -800,13 +798,16 @@ class MainActivity : AppCompatActivity() {
this.runOnUiThread {
if (app.profile == null) {
LoginActivity.firstCompleted = false
if (app.appConfig.loginFinished) {
if (app.config.loginFinished) {
// this shouldn't run
profileListEmptyListener()
}
} else {
setDrawerItems()
drawer.currentProfile = app.profile.id
// the drawer profile is updated automatically when the drawer item is clicked
// update it manually when switching profiles from other source
//if (drawer.currentProfile != app.profile.id)
drawer.currentProfile = app.profile.id
loadTarget(drawerSelection, arguments)
}
}
@ -973,7 +974,7 @@ class MainActivity : AppCompatActivity() {
val item = DrawerPrimaryItem()
.withIdentifier(target.id.toLong())
.withName(target.name)
.withHiddenInMiniDrawer(!app.appConfig.miniDrawerButtonIds.contains(target.id))
.withHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id))
.also { if (target.description != null) it.withDescription(target.description!!) }
.also { if (target.icon != null) it.withIcon(target.icon!!) }
.also { if (target.title != null) it.withAppTitle(getString(target.title!!)) }

View File

@ -93,8 +93,8 @@ public class Notifier {
public boolean shouldBeQuiet() {
long now = Time.getNow().getInMillis();
long start = app.appConfig.quietHoursStart;
long end = app.appConfig.quietHoursEnd;
long start = app.config.getSync().getQuietHoursStart();
long end = app.config.getSync().getQuietHoursEnd();
if (start > end) {
end += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Night passing");
@ -104,7 +104,7 @@ public class Notifier {
//Log.d(TAG, "Now is smaller");
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return app.appConfig.quietHoursStart > 0 && now >= start && now <= end;
return start > 0 && now >= start && now <= end;
}
public int getNotificationDefaults() {
@ -312,7 +312,7 @@ public class Notifier {
| |
|*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
if (!app.appConfig.notifyAboutUpdates)
if (!app.config.getSync().getNotifyAboutUpdates())
return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion)
@ -340,7 +340,7 @@ public class Notifier {
}
public void notificationUpdatesHide() {
if (!app.appConfig.notifyAboutUpdates)
if (!app.config.getSync().getNotifyAboutUpdates())
return;
notificationManager.cancel(ID_UPDATES);
}

View File

@ -52,9 +52,9 @@ class WidgetTimetable : AppWidgetProvider() {
val app = context.applicationContext as App
var bellSyncDiffMillis: Long = 0
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
app.config.timetable.bellSyncDiff?.let {
bellSyncDiffMillis = (it.hour * 60 * 60 * 1000 + it.minute * 60 * 1000 + it.second * 1000).toLong()
bellSyncDiffMillis *= app.config.timetable.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}

View File

@ -64,6 +64,7 @@ const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzP
const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia"
const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia"
const val IDZIENNIK_WEB_EXAMS = "mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe"
const val IDZIENNIK_WEB_HOMEWORK = "mod_panelRodzica/pracaDomowa/WS_pracaDomowa.asmx/pobierzPraceDomowe"
const val IDZIENNIK_WEB_NOTICES = "mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia"
const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia"
const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia"

View File

@ -168,6 +168,8 @@ const val EXCEPTION_LIBRUS_API_REQUEST = 904
const val EXCEPTION_LIBRUS_SYNERGIA_REQUEST = 905
const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 906
const val EXCEPTION_VULCAN_API_REQUEST = 907
const val EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST = 908
const val EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST = 909
const val EXCEPTION_NOTIFY_AND_SYNC = 910
const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912

View File

@ -24,7 +24,7 @@ object Regexes {
"""Liczona do średniej:.*?<strong>nie<br/?></strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_GRADES_DETAILS by lazy {
"""<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?<small>\((.+?)\)</small>.*?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>""".toRegex(RegexOption.DOT_MATCHES_ALL)
"""<strong.*?>(.+?)</strong>.*?<sup>.+?</sup>.*?(?:<small>\((.+?)\)</small>.*?)?<span>.*?Wartość oceny:.*?<strong>([0-9.]+)</strong>.*?Wpisał\(a\):.*?<strong>(.+?)</strong>.*?(?:Komentarz:.*?<strong>(.+?)</strong>)?</span>""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_EVENT_TYPE by lazy {

View File

@ -5,8 +5,8 @@ import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.AppError.CODE_APP_SERVER_ERROR
import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_APP_SERVER_ERROR
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
@ -176,4 +176,4 @@ class ServerSync(val data: Data, val onSuccess: () -> Unit) {
onSuccess()
}}
}
}

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.api.v2.mobidziennik.Mobidziennik
import pl.szczodrzynski.edziennik.api.v2.template.Template
import pl.szczodrzynski.edziennik.api.v2.vulcan.Vulcan
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
@ -24,7 +25,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
fun syncProfileList(profileList: List<Int>) = EdziennikTask(-1, SyncProfileListRequest(profileList))
fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message))
fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest())
fun attachmentGet(profileId: Int, messageId: Long, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(messageId, attachmentId, attachmentName))
fun attachmentGet(profileId: Int, message: Message, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(message, attachmentId, attachmentName))
}
private lateinit var loginStore: LoginStore
@ -74,7 +75,7 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
is MessageGetRequest -> edziennikInterface?.getMessage(request.message)
is FirstLoginRequest -> edziennikInterface?.firstLogin()
is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead()
is AttachmentGetRequest -> edziennikInterface?.getAttachment(request.messageId, request.attachmentId, request.attachmentName)
is AttachmentGetRequest -> edziennikInterface?.getAttachment(request.message, request.attachmentId, request.attachmentName)
}
}
@ -92,5 +93,5 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
data class SyncProfileListRequest(val profileList: List<Int>)
data class MessageGetRequest(val message: MessageFull)
class AnnouncementsReadRequest
data class AttachmentGetRequest(val messageId: Long, val attachmentId: Long, val attachmentName: String)
data class AttachmentGetRequest(val message: Message, val attachmentId: Long, val attachmentName: String)
}

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
@ -70,7 +71,7 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
}
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {
}

View File

@ -11,6 +11,7 @@ const val ENDPOINT_IDZIENNIK_WEB_TIMETABLE = 1030
const val ENDPOINT_IDZIENNIK_WEB_GRADES = 1040
const val ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES = 1050
const val ENDPOINT_IDZIENNIK_WEB_EXAMS = 1060
const val ENDPOINT_IDZIENNIK_WEB_HOMEWORK = 1061
const val ENDPOINT_IDZIENNIK_WEB_NOTICES = 1070
const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080
const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090
@ -34,6 +35,10 @@ val IdziennikFeatures = listOf(
ENDPOINT_IDZIENNIK_WEB_EXAMS to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_HOMEWORK, listOf(
ENDPOINT_IDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),
Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_BEHAVIOUR, listOf(
ENDPOINT_IDZIENNIK_WEB_NOTICES to LOGIN_METHOD_IDZIENNIK_WEB
), listOf(LOGIN_METHOD_IDZIENNIK_WEB)),

View File

@ -55,6 +55,10 @@ class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_exams)
IdziennikWebExams(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_HOMEWORK -> {
data.startProgress(R.string.edziennik_progress_endpoint_homework)
IdziennikWebHomework(data, onSuccess)
}
ENDPOINT_IDZIENNIK_WEB_NOTICES -> {
data.startProgress(R.string.edziennik_progress_endpoint_notices)
IdziennikWebNotices(data, onSuccess)

View File

@ -94,6 +94,7 @@ open class IdziennikWeb(open val data: DataIdziennik) {
is Long -> json.addProperty(name, value)
is Float -> json.addProperty(name, value)
is Char -> json.addProperty(name, value)
is Boolean -> json.addProperty(name, value)
}
}
setJsonBody(json)

View File

@ -5,21 +5,21 @@
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.lessons.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebExams(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebExams"
}
@ -34,14 +34,15 @@ class IdziennikWebExams(override val data: DataIdziennik,
}
private fun getExams() {
val param = JsonObject()
param.addProperty("strona", 1)
param.addProperty("iloscNaStrone", "99")
param.addProperty("iloscRekordow", -1)
param.addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu")
param.addProperty("kierunekSort", 0)
param.addProperty("maxIloscZaznaczonych", 0)
param.addProperty("panelFiltrow", 0)
val param = JsonObject().apply {
addProperty("strona", 1)
addProperty("iloscNaStrone", "99")
addProperty("iloscRekordow", -1)
addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu")
addProperty("kierunekSort", 0)
addProperty("maxIloscZaznaczonych", 0)
addProperty("panelFiltrow", 0)
}
webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf(
"idP" to data.registerId,
@ -55,28 +56,34 @@ class IdziennikWebExams(override val data: DataIdziennik,
return@webApiGet
}
for (jExamEl in json.getAsJsonArray("ListK")) {
val jExam = jExamEl.asJsonObject
// jExam
val eventId = jExam.get("_recordId").asLong
val rSubject = data.getSubject(jExam.get("przedmiot").asString, -1, "")
val rTeacher = data.getTeacherByLastFirst(jExam.get("wpisal").asString)
val examDate = Date.fromY_m_d(jExam.get("data").asString)
val lessonObject = Lesson.getByWeekDayAndSubject(data.lessonList, examDate.weekDay, rSubject.id)
val examTime = lessonObject?.startTime
json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { exam ->
val id = exam.getLong("_recordId") ?: return@forEach
val examDate = Date.fromY_m_d(exam.getString("data") ?: return@forEach)
val subjectName = exam.getString("przedmiot") ?: return@forEach
val subjectId = data.getSubject(subjectName, null, subjectName).id
val teacherName = exam.getString("wpisal") ?: return@forEach
val teacherId = data.getTeacherByLastFirst(teacherName).id
val topic = exam.getString("zakres") ?: ""
val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
val eventType = when (exam.getString("rodzaj")) {
"sprawdzian/praca klasowa" -> Event.TYPE_EXAM
else -> Event.TYPE_SHORT_QUIZ
}
val eventType = if (jExam.get("rodzaj").asString == "sprawdzian/praca klasowa") Event.TYPE_EXAM else Event.TYPE_SHORT_QUIZ
val eventObject = Event(
profileId,
eventId,
id,
examDate,
examTime,
jExam.get("zakres").asString,
startTime,
topic,
-1,
eventType,
false,
rTeacher.id,
rSubject.id,
teacherId,
subjectId,
data.teamClass?.id ?: -1
)
@ -106,9 +113,11 @@ class IdziennikWebExams(override val data: DataIdziennik,
examsNextMonthChecked = true
getExams()
} else {
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS)
onSuccess()
}
}
}
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-25
*/
package pl.szczodrzynski.edziennik.api.v2.idziennik.data.web
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA
import pl.szczodrzynski.edziennik.api.v2.IDZIENNIK_WEB_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
class IdziennikWebHomework(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebHomework"
}
init {
val param = JsonObject().apply {
addProperty("strona", 1)
addProperty("iloscNaStrone", 997)
addProperty("iloscRekordow", -1)
addProperty("kolumnaSort", "DataZadania")
addProperty("kierunekSort", 0)
addProperty("maxIloscZaznaczonych", 0)
addProperty("panelFiltrow", 0)
}
webApiGet(TAG, IDZIENNIK_WEB_HOMEWORK, mapOf(
"idP" to data.registerId,
"data" to Date.getToday().stringY_m_d,
"wszystkie" to true,
"param" to param
)) { result ->
val json = result.getJsonObject("d") ?: run {
data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA)
.withApiResponse(result))
return@webApiGet
}
json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { homework ->
val id = homework.getLong("_recordId") ?: return@forEach
val eventDate = Date.fromY_m_d(homework.getString("dataO") ?: return@forEach)
val subjectName = homework.getString("przed") ?: return@forEach
val subjectId = data.getSubject(subjectName, null, subjectName).id
val teacherName = homework.getString("usr") ?: return@forEach
val teacherId = data.getTeacherByLastFirst(teacherName).id
val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime
val topic = homework.getString("tytul") ?: ""
val seen = when (profile?.empty) {
true -> true
else -> eventDate < Date.getToday()
}
val eventObject = Event(
profileId,
id,
eventDate,
startTime,
topic,
-1,
Event.TYPE_HOMEWORK,
false,
teacherId,
subjectId,
data.teamClass?.id ?: -1
)
data.eventList.add(eventObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_HOMEWORK,
eventObject.id,
seen,
seen,
System.currentTimeMillis()
))
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS)
onSuccess()
}
}
}

View File

@ -10,21 +10,24 @@ import pl.szczodrzynski.edziennik.api.v2.idziennik.DataIdziennik
import pl.szczodrzynski.edziennik.api.v2.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES
import pl.szczodrzynski.edziennik.api.v2.idziennik.data.IdziennikWeb
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_SEMESTER1_PROPOSED
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.TYPE_YEAR_PROPOSED
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue
class IdziennikWebProposedGrades(override val data: DataIdziennik,
val onSuccess: () -> Unit) : IdziennikWeb(data) {
val onSuccess: () -> Unit) : IdziennikWeb(data) {
companion object {
private const val TAG = "IdziennikWebProposedGrades"
}
init {
init { data.profile?.also { profile ->
webApiGet(TAG, IDZIENNIK_WEB_MISSING_GRADES, mapOf(
"idPozDziennika" to data.registerId
)) { result ->
@ -34,17 +37,17 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
return@webApiGet
}
val jSubjects = json.getAsJsonArray("Przedmioty")
for (jSubjectEl in jSubjects) {
val jSubject = jSubjectEl.getAsJsonObject()
// jSubject
val rSubject = data.getSubject(jSubject.get("Przedmiot").getAsString(), -1, jSubject.get("Przedmiot").getAsString())
val semester1Proposed = jSubject.get("OcenaSem1").getAsString()
val semester2Proposed = jSubject.get("OcenaSem2").getAsString()
json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { subject ->
val subjectName = subject.getString("Przedmiot") ?: return@forEach
val subjectObject = data.getSubject(subjectName, null, subjectName)
val semester1Proposed = subject.getString("OcenaSem1") ?: ""
val semester1Value = getWordGradeValue(semester1Proposed)
val semester1Id = subjectObject.id * (-100) - 1
val semester2Proposed = subject.getString("OcenaSem2") ?: ""
val semester2Value = getWordGradeValue(semester2Proposed)
val semester1Id = rSubject.id * -100 - 1
val semester2Id = rSubject.id * -100 - 2
val semester2Id = subjectObject.id * (-100) - 2
if (semester1Proposed != "") {
val gradeObject = Grade(
@ -58,17 +61,18 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
0f,
1,
-1,
rSubject.id)
gradeObject.type = TYPE_SEMESTER1_PROPOSED
subjectObject.id
).apply {
type = TYPE_SEMESTER1_PROPOSED
}
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
@ -85,17 +89,18 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
0f,
2,
-1,
rSubject.id)
gradeObject.type = TYPE_YEAR_PROPOSED
subjectObject.id
).apply {
type = TYPE_YEAR_PROPOSED
}
data.gradeList.add(gradeObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_GRADE,
gradeObject.id,
profile?.empty ?: false,
profile?.empty ?: false,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
@ -104,5 +109,5 @@ class IdziennikWebProposedGrades(override val data: DataIdziennik,
data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}}
}

View File

@ -5,13 +5,14 @@
package pl.szczodrzynski.edziennik.api.v2.interfaces
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
interface EdziennikInterface {
fun sync(featureIds: List<Int>, viewId: Int? = null, arguments: JsonObject? = null)
fun getMessage(message: MessageFull)
fun markAllAnnouncementsAsRead()
fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String)
fun getAttachment(message: Message, attachmentId: Long, attachmentName: String)
fun firstLogin()
fun cancel()
}

View File

@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.LibrusFirstLogin
import pl.szczodrzynski.edziennik.api.v2.librus.login.*
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
@ -105,12 +106,12 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
}
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {
LibrusLoginPortal(data) {
LibrusLoginApi(data) {
LibrusLoginSynergia(data) {
LibrusLoginMessages(data) {
LibrusMessagesGetAttachment(data, messageId, attachmentId, attachmentName) {
LibrusMessagesGetAttachment(data, message, attachmentId, attachmentName) {
completed()
}
}

View File

@ -60,7 +60,7 @@ val LibrusFeatures = listOf(
Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API
), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data ->
data.app.appConfig.fcmTokens[LOGIN_TYPE_LIBRUS]?.second?.contains(data.profileId) == false
!data.app.config.sync.tokenLibrusList.contains(data.profileId)
},

View File

@ -196,7 +196,7 @@ open class LibrusMessages(open val data: DataLibrus) {
try {
onSuccess(file)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST)
.withResponse(response)
.withThrowable(e))
}
@ -206,7 +206,7 @@ open class LibrusMessages(open val data: DataLibrus) {
try {
onProgress(bytesWritten, bytesTotal)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST)
data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST)
.withThrowable(e))
}
}

View File

@ -36,14 +36,14 @@ class LibrusApiAttendances(override val data: DataLibrus,
val lessonNo = attendance.getInt("LessonNo") ?: return@forEach
val startTime = data.lessonRanges.get(lessonNo).startTime
val lessonDate = Date.fromY_m_d(attendance.getString("Date"))
val subjectId = data.lessonList.singleOrNull {
it.weekDay == lessonDate.weekDay && it.startTime.value == startTime.value
}?.subjectId ?: -1
val semester = attendance.getInt("Semester") ?: return@forEach
val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach
val typeObject = data.attendanceTypes.get(type)
val topic = typeObject?.name ?: ""
val lessonList = data.db.timetableDao().getForDateNow(profileId, lessonDate)
val subjectId = lessonList.firstOrNull { it.startTime == startTime }?.subjectId ?: -1
val attendanceObject = Attendance(
profileId,
id,

View File

@ -9,6 +9,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_EVENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
@ -69,6 +70,8 @@ class LibrusApiEvents(override val data: DataLibrus,
))
}
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_LIBRUS_API_EVENTS, SYNC_ALWAYS)
onSuccess()
}

View File

@ -8,6 +8,7 @@ import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
@ -55,6 +56,8 @@ class LibrusApiHomework(override val data: DataLibrus,
))
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_LIBRUS_API_HOMEWORK, SYNC_ALWAYS)
onSuccess()
}

View File

@ -15,15 +15,16 @@ import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent.Companion.TYP
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
import java.io.File
import kotlin.coroutines.CoroutineContext
class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId: Long, val attachmentId: Long,
val attachmentName: String, val onSuccess: () -> Unit) : LibrusMessages(data), CoroutineScope {
class LibrusMessagesGetAttachment(
override val data: DataLibrus, val message: Message, val attachmentId: Long,
val attachmentName: String, val onSuccess: () -> Unit) : LibrusMessages(data), CoroutineScope {
companion object {
const val TAG = "LibrusMessagesGetAttachment"
}
@ -38,7 +39,7 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId:
init {
messagesGet(TAG, "GetFileDownloadLink", parameters = mapOf(
"fileId" to attachmentId,
"msgId" to messageId,
"msgId" to message.id,
"archive" to 0
)) { doc ->
val downloadLink = doc.select("response GetFileDownloadLink downloadLink").text()
@ -55,8 +56,6 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId:
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
.withApiResponse(doc.toString()))
}
onSuccess()
}
}
@ -92,12 +91,11 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId:
private fun downloadAttachment(attachmentKey: String) {
val targetFile = File(Utils.getStorageDir(), attachmentName)
sandboxGetFile(TAG, "CSDownload&singleUseKey=$attachmentKey",
targetFile, { file ->
sandboxGetFile(TAG, "CSDownload&singleUseKey=$attachmentKey", targetFile, { file ->
val event = AttachmentGetEvent(
profileId,
messageId,
message.id,
attachmentId,
TYPE_FINISHED,
file.absolutePath
@ -108,10 +106,12 @@ class LibrusMessagesGetAttachment(override val data: DataLibrus, val messageId:
EventBus.getDefault().post(event)
onSuccess()
}) { written, _ ->
val event = AttachmentGetEvent(
profileId,
messageId,
message.id,
attachmentId,
TYPE_PROGRESS,
bytesWritten = written

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.api.v2.POST
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusSynergia
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
@ -55,19 +56,18 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () ->
val id = "/podglad/([0-9]+)'".toRegex().find(
elements[9].select("input").attr("onclick")
)?.get(1)?.toLong() ?: return@forEachIndexed
val startTime = data.lessonList.singleOrNull {
it.weekDay == eventDate.weekDay && it.subjectId == subjectId
}?.startTime
val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate)
val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime
val moreInfo = graphElements[2 * i + 1].select("td[title]")
.attr("title").trim()
val description = "Treść: (.*)".toRegex(RegexOption.DOT_MATCHES_ALL).find(moreInfo)
?.get(1)?.replace("<br.*/>".toRegex(), "\n")?.trim()
val notified = when (profile?.empty) {
val seen = when (profile?.empty) {
true -> true
false -> Date.getToday() < eventDate
else -> false
else -> eventDate < Date.getToday()
}
val eventObject = Event(
@ -89,13 +89,15 @@ class LibrusSynergiaHomework(override val data: DataLibrus, val onSuccess: () ->
profileId,
Metadata.TYPE_HOMEWORK,
id,
notified,
notified,
seen,
seen,
addedDate
))
}
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
// because this requires a synergia login (2 more requests) sync this every two hours or if explicit :D
data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 2 * HOUR, DRAWER_ITEM_HOMEWORK)
onSuccess()

View File

@ -13,8 +13,8 @@ import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusPortal
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginApi
import pl.szczodrzynski.edziennik.api.v2.librus.login.LibrusLoginPortal
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_LIBRUS_DISCONNECTED
import pl.szczodrzynski.edziennik.data.api.AppError.CODE_SYNERGIA_NOT_ACTIVATED
import pl.szczodrzynski.edziennik.api.v2.models.AppError.CODE_LIBRUS_DISCONNECTED
import pl.szczodrzynski.edziennik.api.v2.models.AppError.CODE_SYNERGIA_NOT_ACTIVATED
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
@ -89,4 +89,4 @@ class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) {
}
}
}
}
}

View File

@ -101,7 +101,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
val loginElement = doc.createElement("login")
loginElement.appendChild(doc.createTextNode(data.apiLogin))
dataElement.appendChild(loginElement)
val passwordElement = doc.createElement("login")
val passwordElement = doc.createElement("password")
passwordElement.appendChild(doc.createTextNode(data.apiPassword))
dataElement.appendChild(passwordElement)
val keyStrokeElement = doc.createElement("KeyStroke")
@ -150,6 +150,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
sessionId = sessionId?.replace("MAINT", "") // dunno what's this
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)

View File

@ -10,6 +10,7 @@ import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGetAttachment
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGetMessage
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin
@ -18,6 +19,7 @@ import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
@ -76,8 +78,12 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
}
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {
MobidziennikLoginWeb(data) {
MobidziennikWebGetAttachment(data, message, attachmentId, attachmentName) {
completed()
}
}
}
override fun firstLogin() {

View File

@ -6,12 +6,14 @@ package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.FileCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie
import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.File
open class MobidziennikWeb(open val data: DataMobidziennik) {
companion object {
@ -93,4 +95,77 @@ open class MobidziennikWeb(open val data: DataMobidziennik) {
.build()
.enqueue()
}
fun webGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit,
onProgress: (written: Long, total: Long) -> Unit) {
val url = "https://${data.loginServerName}.mobidziennik.pl$action"
d(tag, "Request: Mobidziennik/Web - $url")
if (data.webSessionKey == null) {
data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY)
return
}
if (data.webSessionValue == null) {
data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE)
return
}
if (data.webServerId == null) {
data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID)
return
}
val callback = object : FileCallbackHandler(targetFile) {
override fun onSuccess(file: File?, response: Response?) {
if (file == null) {
data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD)
.withResponse(response))
return
}
try {
onSuccess(file)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST)
.withResponse(response)
.withThrowable(e))
}
}
override fun onProgress(bytesWritten: Long, bytesTotal: Long) {
try {
onProgress(bytesWritten, bytesTotal)
} catch (e: Exception) {
data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST)
.withThrowable(e))
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
data.app.cookieJar.saveFromResponse(null, listOf(
Cookie.Builder()
.name(data.webSessionKey!!)
.value(data.webSessionValue!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build(),
Cookie.Builder()
.name("SERVERID")
.value(data.webServerId!!)
.domain("${data.loginServerName}.mobidziennik.pl")
.secure().httpOnly().build()
))
Request.builder()
.url(url)
.userAgent(MOBIDZIENNIK_USER_AGENT)
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import androidx.core.util.contains
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
@ -74,5 +75,7 @@ class MobidziennikApiEvents(val data: DataMobidziennik, rows: List<String>) {
))
}
}
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
}
}
}

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import androidx.core.util.contains
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.models.Date
@ -53,5 +54,7 @@ class MobidziennikApiHomework(val data: DataMobidziennik, rows: List<String>) {
))
}
}
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-28.
*/
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.api.v2.events.AttachmentGetEvent
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.utils.Utils
import java.io.File
class MobidziennikWebGetAttachment(
override val data: DataMobidziennik, val message: Message, val attachmentId: Long,
val attachmentName: String, val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebGetAttachment"
}
init {
val targetFile = File(Utils.getStorageDir(), attachmentName)
val typeUrl = if (message.type == Message.TYPE_SENT)
"wiadwyslana"
else
"wiadodebrana"
webGetFile(TAG, "/dziennik/$typeUrl/?id=${message.id}&zalacznik=$attachmentId", targetFile, { file ->
val event = AttachmentGetEvent(
profileId,
message.id,
attachmentId,
AttachmentGetEvent.TYPE_FINISHED,
file.absolutePath
)
val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.messageId}_${event.attachmentId}")
Utils.writeStringToFile(attachmentDataFile, event.fileName)
EventBus.getDefault().post(event)
onSuccess()
}) { written, _ ->
// TODO make use of bytesTotal
val event = AttachmentGetEvent(
profileId,
message.id,
attachmentId,
AttachmentGetEvent.TYPE_PROGRESS,
bytesWritten = written
)
EventBus.getDefault().post(event)
}
}
}

View File

@ -124,7 +124,7 @@ class MobidziennikWebGetMessage(
val attachmentName = it.ownText()
Regexes.MOBIDZIENNIK_MESSAGE_ATTACHMENT.find(it.outerHtml())?.let { match ->
val attachmentId = match[1].toLong()
var size = match[2].toFloatOrNull() ?: 0f
var size = match[2].toFloatOrNull() ?: -1f
when (match[3]) {
"K" -> size *= 1024f
"M" -> size *= 1024f * 1024f

View File

@ -96,13 +96,17 @@ class MobidziennikWebGrades(override val data: DataMobidziennik,
if (Regexes.MOBIDZIENNIK_GRADES_COUNT_TO_AVG.containsMatchIn(html)) {
Regexes.MOBIDZIENNIK_GRADES_DETAILS.find(html)?.let { match ->
val gradeName = match[1]
val gradeDescription = match[2]
var gradeDescription = match[2]
val gradeValue = match[3].toFloatOrNull() ?: 0.0f
val teacherName = match[4].fixWhiteSpaces()
val teacherId = data.teacherList.singleOrNull { it.fullNameLastFirst == teacherName }?.id ?: -1
val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id ?: -1
if (match[5].isNotEmpty()) {
gradeDescription += "\n"+match[5].replace("<br>", "\n")
}
val gradeObject = Grade(
profileId,
gradeId,

View File

@ -91,7 +91,7 @@ class MobidziennikLoginWeb(val data: DataMobidziennik, val onSuccess: () -> Unit
.addParameter("ip", data.app.deviceId)
.addParameter("login", data.loginUsername)
.addParameter("haslo", data.loginPassword)
.addParameter("token", data.app.appConfig.fcmTokens[LOGIN_TYPE_MOBIDZIENNIK]?.first)
.addParameter("token", data.app.config.sync.tokenMobidziennik)
.post()
.callback(callback)
.build()

View File

@ -8,7 +8,6 @@ import android.content.Context
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import pl.szczodrzynski.edziennik.data.api.AppError
class ApiError(val tag: String, val errorCode: Int) {
var profileId: Int? = null
@ -67,4 +66,4 @@ class ApiError(val tag: String, val errorCode: Int) {
}
}
}

View File

@ -1,4 +1,8 @@
package pl.szczodrzynski.edziennik.data.api;
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-26
*/
package pl.szczodrzynski.edziennik.api.v2.models;
import android.content.Context;
import android.os.AsyncTask;

View File

@ -10,7 +10,7 @@ import pl.szczodrzynski.edziennik.api.v2.DataNotifications
import pl.szczodrzynski.edziennik.api.v2.EXCEPTION_NOTIFY_AND_SYNC
import pl.szczodrzynski.edziennik.api.v2.ServerSync
import pl.szczodrzynski.edziennik.api.v2.interfaces.EndpointCallback
import pl.szczodrzynski.edziennik.data.api.AppError.*
import pl.szczodrzynski.edziennik.api.v2.models.AppError.*
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement
import pl.szczodrzynski.edziennik.data.db.modules.api.EndpointTimer
@ -283,6 +283,7 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
when (model) {
is DataRemoveModel.Timetable -> model.commit(profileId, db.timetableDao())
is DataRemoveModel.Grades -> model.commit(profileId, db.gradeDao())
is DataRemoveModel.Events -> model.commit(profileId, db.eventDao())
}
}
@ -305,7 +306,6 @@ open class Data(val app: App, val profile: Profile?, val loginStore: LoginStore)
db.gradeDao().addAll(gradeList)
}
if (eventList.isNotEmpty()) {
db.eventDao().removeFuture(profile.id, Date.getToday())
db.eventDao().addAll(eventList)
}
if (noticeList.isNotEmpty()) {

View File

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.models
import pl.szczodrzynski.edziennik.data.db.modules.events.EventDao
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeDao
import pl.szczodrzynski.edziennik.data.db.modules.timetable.TimetableDao
import pl.szczodrzynski.edziennik.utils.models.Date
@ -15,21 +16,23 @@ open class DataRemoveModel {
fun to(dateTo: Date) = Timetable(null, dateTo)
fun between(dateFrom: Date, dateTo: Date) = Timetable(dateFrom, dateTo)
}
fun commit(profileId: Int, dao: TimetableDao) {
if (dateFrom != null && dateTo != null) {
dao.clearBetweenDates(profileId, dateFrom, dateTo)
}
else {
} else {
dateFrom?.let { dateFrom -> dao.clearFromDate(profileId, dateFrom) }
dateTo?.let { dateTo -> dao.clearToDate(profileId, dateTo) }
}
}
}
class Grades(val all: Boolean, val semester: Int?) : DataRemoveModel() {
class Grades(private val all: Boolean, private val semester: Int?) : DataRemoveModel() {
companion object {
fun all() = Grades(true, null)
fun semester(semester: Int) = Grades(false, semester)
}
fun commit(profileId: Int, dao: GradeDao) {
if (all) {
dao.clear(profileId)
@ -37,4 +40,16 @@ open class DataRemoveModel {
semester?.let { dao.clearForSemester(profileId, it) }
}
}
}
class Events(private val type: Int?, private val exceptType: Int?) : DataRemoveModel() {
companion object {
fun futureExceptType(exceptType: Int) = Events(null, exceptType)
fun futureWithType(type: Int) = Events(type, null)
}
fun commit(profileId: Int, dao: EventDao) {
type?.let { dao.removeFutureWithType(profileId, Date.getToday(), it) }
exceptType?.let { dao.removeFutureExceptType(profileId, Date.getToday(), it) }
}
}
}

View File

@ -16,6 +16,7 @@ import pl.szczodrzynski.edziennik.api.v2.template.firstlogin.TemplateFirstLogin
import pl.szczodrzynski.edziennik.api.v2.template.login.TemplateLogin
import pl.szczodrzynski.edziennik.api.v2.templateLoginMethods
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
@ -70,7 +71,7 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
}
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {
}

View File

@ -18,6 +18,7 @@ import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLogin
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi
import pl.szczodrzynski.edziennik.api.v2.vulcanLoginMethods
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
@ -76,7 +77,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
}
override fun getAttachment(messageId: Long, attachmentId: Long, attachmentName: String) {
override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {
}

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_EVENTS
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_HOMEWORK
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_EVENTS
import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_HOMEWORK
@ -52,10 +53,11 @@ class VulcanApiEvents(override val data: DataVulcan, private val isHomework: Boo
val eventDate = Date.fromY_m_d(event.getString("DataTekst") ?: return@forEach)
val subjectId = event.getLong("IdPrzedmiot") ?: -1
val teacherId = event.getLong("IdPracownik") ?: -1
val startTime = data.lessonList.singleOrNull {
it.weekDay == eventDate.weekDay && it.subjectId == subjectId
}?.startTime
val topic = event.getString("Opis") ?: ""
val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate)
val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime
val type = when (isHomework) {
true -> Event.TYPE_HOMEWORK
else -> when (event.getBoolean("Rodzaj")) {
@ -91,8 +93,14 @@ class VulcanApiEvents(override val data: DataVulcan, private val isHomework: Boo
}
when (isHomework) {
true -> data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS)
false -> data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS)
true -> {
data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_API_HOMEWORK, SYNC_ALWAYS)
}
false -> {
data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK))
data.setSyncNext(ENDPOINT_VULCAN_API_EVENTS, SYNC_ALWAYS)
}
}
onSuccess()
}

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config
interface AbstractConfig {
fun set(key: String, value: String?)
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.config.utils.ConfigMigration
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.config.utils.toHashMap
import pl.szczodrzynski.edziennik.data.db.AppDb
import kotlin.coroutines.CoroutineContext
class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
companion object {
const val DATA_VERSION = 2
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
val values: HashMap<String, String?> = hashMapOf()
val ui by lazy { ConfigUI(this) }
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
val grades by lazy { ConfigGrades(this) }
private var mDataVersion: Int? = null
var dataVersion: Int
get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 }
set(value) { set("dataVersion", value); mDataVersion = value }
private var mAppVersion: Int? = null
var appVersion: Int
get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE }
set(value) { set("appVersion", value); mAppVersion = value }
private var mLoginFinished: Boolean? = null
var loginFinished: Boolean
get() { mLoginFinished = mLoginFinished ?: values.get("loginFinished", false); return mLoginFinished ?: false }
set(value) { set("loginFinished", value); mLoginFinished = value }
private var mDevModePassword: String? = null
var devModePassword: String?
get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword }
set(value) { set("devModePassword", value); mDevModePassword = value }
private var mAppInstalledTime: Long? = null
var appInstalledTime: Long
get() { mAppInstalledTime = mAppInstalledTime ?: values.get("appInstalledTime", 0L); return mAppInstalledTime ?: 0L }
set(value) { set("appInstalledTime", value); mAppInstalledTime = value }
private var mAppRateSnackbarTime: Long? = null
var appRateSnackbarTime: Long
get() { mAppRateSnackbarTime = mAppRateSnackbarTime ?: values.get("appRateSnackbarTime", 0L); return mAppRateSnackbarTime ?: 0L }
set(value) { set("appRateSnackbarTime", value); mAppRateSnackbarTime = value }
private var rawEntries: List<ConfigEntry> = db.configDao().getAllNow()
private val profileConfigs: HashMap<Int, ProfileConfig> = hashMapOf()
init {
rawEntries.toHashMap(-1, values)
}
fun migrate(app: App) {
if (dataVersion < DATA_VERSION)
ConfigMigration(app, this)
}
fun getFor(profileId: Int): ProfileConfig {
return profileConfigs[profileId] ?: ProfileConfig(db, profileId, rawEntries)
}
fun setProfile(profileId: Int) {
}
override fun set(key: String, value: String?) {
values[key] = value
launch {
db.configDao().add(ConfigEntry(-1, key, value))
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
class ConfigGrades(private val config: Config) {
companion object {
const val ORDER_BY_DATE_DESC = 0
const val ORDER_BY_SUBJECT_ASC = 1
const val ORDER_BY_DATE_ASC = 2
const val ORDER_BY_SUBJECT_DESC = 3
}
private var mOrderBy: Int? = null
var orderBy: Int
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: 0 }
set(value) { config.set("gradesOrderBy", value); mOrderBy = value }
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set
class ConfigSync(private val config: Config) {
private var mSyncEnabled: Boolean? = null
var enabled: Boolean
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }
set(value) { config.set("syncEnabled", value); mSyncEnabled = value }
private var mSyncOnlyWifi: Boolean? = null
var onlyWifi: Boolean
get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates }
set(value) { config.set("syncOnlyWifi", value); mSyncOnlyWifi = value }
private var mSyncInterval: Int? = null
var interval: Int
get() { mSyncInterval = mSyncInterval ?: config.values.get("syncInterval", 60*60); return mSyncInterval ?: 60*60 }
set(value) { config.set("syncInterval", value); mSyncInterval = value }
private var mNotifyAboutUpdates: Boolean? = null
var notifyAboutUpdates: Boolean
get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true }
set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value }
/* ____ _ _ _
/ __ \ (_) | | | |
| | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___
| | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __|
| |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \
\___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/
private var mQuietHoursStart: Long? = null
var quietHoursStart: Long
get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", 0L); return mQuietHoursStart ?: 0L }
set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value }
private var mQuietHoursEnd: Long? = null
var quietHoursEnd: Long
get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", 0L); return mQuietHoursEnd ?: 0L }
set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value }
private var mQuietDuringLessons: Boolean? = null
var quietDuringLessons: Boolean
get() { mQuietDuringLessons = mQuietDuringLessons ?: config.values.get("quietDuringLessons", false); return mQuietDuringLessons ?: false }
set(value) { config.set("quietDuringLessons", value); mQuietDuringLessons = value }
/* ______ _____ __ __ _______ _
| ____/ ____| \/ | |__ __| | |
| |__ | | | \ / | | | ___ | | _____ _ __ ___
| __|| | | |\/| | | |/ _ \| |/ / _ \ '_ \/ __|
| | | |____| | | | | | (_) | < __/ | | \__ \
|_| \_____|_| |_| |_|\___/|_|\_\___|_| |_|__*/
private var mTokenApp: String? = null
var tokenApp: String?
get() { mTokenApp = mTokenApp ?: config.values.get("tokenApp", null as String?); return mTokenApp }
set(value) { config.set("tokenApp", value); mTokenApp = value }
private var mTokenMobidziennik: String? = null
var tokenMobidziennik: String?
get() { mTokenMobidziennik = mTokenMobidziennik ?: config.values.get("tokenMobidziennik", null as String?); return mTokenMobidziennik }
set(value) { config.set("tokenMobidziennik", value); mTokenMobidziennik = value }
private var mTokenLibrus: String? = null
var tokenLibrus: String?
get() { mTokenLibrus = mTokenLibrus ?: config.values.get("tokenLibrus", null as String?); return mTokenLibrus }
set(value) { config.set("tokenLibrus", value); mTokenLibrus = value }
private var mTokenVulcan: String? = null
var tokenVulcan: String?
get() { mTokenVulcan = mTokenVulcan ?: config.values.get("tokenVulcan", null as String?); return mTokenVulcan }
set(value) { config.set("tokenVulcan", value); mTokenVulcan = value }
private var mTokenMobidziennikList: List<Int>? = null
var tokenMobidziennikList: List<Int>
get() { mTokenMobidziennikList = mTokenMobidziennikList ?: config.values.getIntList("tokenMobidziennikList", listOf()); return mTokenMobidziennikList ?: listOf() }
set(value) { config.set("tokenMobidziennikList", value); mTokenMobidziennikList = value }
private var mTokenLibrusList: List<Int>? = null
var tokenLibrusList: List<Int>
get() { mTokenLibrusList = mTokenLibrusList ?: config.values.getIntList("tokenLibrusList", listOf()); return mTokenLibrusList ?: listOf() }
set(value) { config.set("tokenLibrusList", value); mTokenLibrusList = value }
private var mTokenVulcanList: List<Int>? = null
var tokenVulcanList: List<Int>
get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() }
set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value }
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.utils.models.Time
class ConfigTimetable(private val config: Config) {
private var mBellSyncMultiplier: Int? = null
var bellSyncMultiplier: Int
get() { mBellSyncMultiplier = mBellSyncMultiplier ?: config.values.get("bellSyncMultiplier", 0); return mBellSyncMultiplier ?: 0 }
set(value) { config.set("bellSyncMultiplier", value); mBellSyncMultiplier = value }
private var mBellSyncDiff: Time? = null
var bellSyncDiff: Time?
get() { mBellSyncDiff = mBellSyncDiff ?: config.values.get("bellSyncDiff", null as Time?); return mBellSyncDiff }
set(value) { config.set("bellSyncDiff", value); mBellSyncDiff = value }
private var mCountInSeconds: Boolean? = null
var countInSeconds: Boolean
get() { mCountInSeconds = mCountInSeconds ?: config.values.get("countInSeconds", false); return mCountInSeconds ?: false }
set(value) { config.set("countInSeconds", value); mCountInSeconds = value }
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel
class ConfigUI(private val config: Config) {
private var mTheme: Int? = null
var theme: Int
get() { mTheme = mTheme ?: config.values.get("theme", 1); return mTheme ?: 1 }
set(value) { config.set("theme", value); mTheme = value }
private var mLanguage: String? = null
var language: String?
get() { mLanguage = mLanguage ?: config.values.get("language", null as String?); return mLanguage }
set(value) { config.set("language", value); mLanguage = value }
private var mHeaderBackground: String? = null
var headerBackground: String?
get() { mHeaderBackground = mHeaderBackground ?: config.values.get("headerBackground", null as String?); return mHeaderBackground }
set(value) { config.set("headerBg", value); mHeaderBackground = value }
private var mAppBackground: String? = null
var appBackground: String?
get() { mAppBackground = mAppBackground ?: config.values.get("appBackground", null as String?); return mAppBackground }
set(value) { config.set("appBg", value); mAppBackground = value }
private var mMiniMenuVisible: Boolean? = null
var miniMenuVisible: Boolean
get() { mMiniMenuVisible = mMiniMenuVisible ?: config.values.get("miniMenuVisible", false); return mMiniMenuVisible ?: false }
set(value) { config.set("miniMenuVisible", value); mMiniMenuVisible = value }
private var mMiniMenuButtons: List<Int>? = null
var miniMenuButtons: List<Int>
get() { mMiniMenuButtons = mMiniMenuButtons ?: config.values.getIntList("miniMenuButtons", listOf()); return mMiniMenuButtons ?: listOf() }
set(value) { config.set("miniMenuButtons", value); mMiniMenuButtons = value }
private var mAgendaViewType: Int? = null
var agendaViewType: Int
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: 0 }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
private var mHomeCards: List<HomeCardModel>? = null
var homeCards: List<HomeCardModel>
get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() }
set(value) { config.set("homeCards", value); mHomeCards = value }
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.config.utils.toHashMap
import pl.szczodrzynski.edziennik.data.db.AppDb
import kotlin.coroutines.CoroutineContext
class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEntry>) : CoroutineScope, AbstractConfig {
companion object {
const val DATA_VERSION = 1
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Default
val values: HashMap<String, String?> = hashMapOf()
val grades by lazy { ProfileConfigGrades(this) }
/*
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
val grades by lazy { ConfigGrades(this) }*/
private var mDataVersion: Int? = null
var dataVersion: Int
get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 }
set(value) { set("dataVersion", value); mDataVersion = value }
init {
rawEntries.toHashMap(profileId, values)
/*if (dataVersion < DATA_VERSION)
ProfileConfigMigration(this)*/
}
override fun set(key: String, value: String?) {
values[key] = value
launch {
db.configDao().add(ConfigEntry(profileId, key, value))
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.Companion.YEAR_ALL_GRADES
class ProfileConfigGrades(private val config: ProfileConfig) {
private var mColorMode: Int? = null
var colorMode: Int
get() { mColorMode = mColorMode ?: config.values.get("gradesColorMode", COLOR_MODE_WEIGHTED); return mColorMode ?: COLOR_MODE_WEIGHTED }
set(value) { config.set("gradesColorMode", value); mColorMode = value }
private var mYearAverageMode: Int? = null
var yearAverageMode: Int
get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES }
set(value) { config.set("yearAverageMode", value); mYearAverageMode = value }
private var mCountZeroToAvg: Boolean? = null
var countZeroToAvg: Boolean
get() { mCountZeroToAvg = mCountZeroToAvg ?: config.values.get("countZeroToAvg", true); return mCountZeroToAvg ?: true }
set(value) { config.set("countZeroToAvg", value); mCountZeroToAvg = value }
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config.db
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface ConfigDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(entry: ConfigEntry)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addAll(list: List<ConfigEntry>)
@Query("SELECT * FROM config WHERE profileId = -1")
fun getAllNow(): List<ConfigEntry>
@Query("SELECT * FROM config WHERE profileId = :profileId")
fun getAllNow(profileId: Int): List<ConfigEntry>
}

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-26.
*/
package pl.szczodrzynski.edziennik.config.db
import androidx.room.Entity
@Entity(tableName = "config", primaryKeys = ["profileId", "key"])
data class ConfigEntry(
val profileId: Int = -1,
val key: String,
val value: String?
)

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config.utils
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.config.AbstractConfig
import pl.szczodrzynski.edziennik.config.db.ConfigEntry
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
private val gson = Gson()
fun AbstractConfig.set(key: String, value: Int) {
set(key, value.toString())
}
fun AbstractConfig.set(key: String, value: Boolean) {
set(key, value.toString())
}
fun AbstractConfig.set(key: String, value: Long) {
set(key, value.toString())
}
fun AbstractConfig.set(key: String, value: Float) {
set(key, value.toString())
}
fun AbstractConfig.set(key: String, value: Date?) {
set(key, value?.stringY_m_d)
}
fun AbstractConfig.set(key: String, value: Time?) {
set(key, value?.stringValue)
}
fun AbstractConfig.set(key: String, value: JsonElement?) {
set(key, value?.toString())
}
fun AbstractConfig.set(key: String, value: List<Any>?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.setStringList(key: String, value: List<String>?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.setIntList(key: String, value: List<Int>?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.setLongList(key: String, value: List<Long>?) {
set(key, value?.let { gson.toJson(it) })
}
fun HashMap<String, String?>.get(key: String, default: String?): String? {
return this[key] ?: default
}
fun HashMap<String, String?>.get(key: String, default: Boolean): Boolean {
return this[key]?.toBoolean() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Int): Int {
return this[key]?.toIntOrNull() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Long): Long {
return this[key]?.toLongOrNull() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Float): Float {
return this[key]?.toFloatOrNull() ?: default
}
fun HashMap<String, String?>.get(key: String, default: Date?): Date? {
return this[key]?.let { Date.fromY_m_d(it) } ?: default
}
fun HashMap<String, String?>.get(key: String, default: Time?): Time? {
return this[key]?.let { Time.fromHms(it) } ?: default
}
fun HashMap<String, String?>.get(key: String, default: JsonObject?): JsonObject? {
return this[key]?.let { JsonParser().parse(it)?.asJsonObject } ?: default
}
fun HashMap<String, String?>.get(key: String, default: JsonArray?): JsonArray? {
return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default
}
/* !!! cannot use mutable list here - modifying it will not update the DB */
fun <T> HashMap<String, String?>.get(key: String, default: List<T>?, classOfT: Class<T>): List<T>? {
return this[key]?.let { ConfigGsonUtils().deserializeList<T>(gson, it, classOfT) } ?: default
}
fun HashMap<String, String?>.getStringList(key: String, default: List<String>?): List<String>? {
return this[key]?.let { gson.fromJson<List<String>>(it, object: TypeToken<List<String>>(){}.type) } ?: default
}
fun HashMap<String, String?>.getIntList(key: String, default: List<Int>?): List<Int>? {
return this[key]?.let { gson.fromJson<List<Int>>(it, object: TypeToken<List<Int>>(){}.type) } ?: default
}
fun HashMap<String, String?>.getLongList(key: String, default: List<Long>?): List<Long>? {
return this[key]?.let { gson.fromJson<List<Long>>(it, object: TypeToken<List<Long>>(){}.type) } ?: default
}
fun List<ConfigEntry>.toHashMap(profileId: Int, map: HashMap<String, String?>) {
map.clear()
forEach {
if (it.profileId == profileId)
map[it.key] = it.value
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-2.
*/
package pl.szczodrzynski.edziennik.config.utils
import com.google.gson.Gson
import com.google.gson.JsonParser
import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel
import pl.szczodrzynski.edziennik.utils.models.Time
class ConfigGsonUtils {
fun <T> deserializeList(gson: Gson, str: String?, classOfT: Class<T>): List<T> {
val json = JsonParser().parse(str)
val list: MutableList<T> = mutableListOf()
if (!json.isJsonArray)
return list
json.asJsonArray.forEach { e ->
when (classOfT) {
String::class.java -> {
list += e.asString as T
}
HomeCardModel::class.java -> {
val o = e.asJsonObject
list += HomeCardModel(
o.getInt("profileId", 0),
o.getInt("cardId", 0)
) as T
}
Time::class.java -> {
val o = e.asJsonObject
list += Time(
o.getInt("hour", 0),
o.getInt("minute", 0),
o.getInt("second", 0)
) as T
}
}
}
return list
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-27.
*/
package pl.szczodrzynski.edziennik.config.utils
import android.content.Context
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.utils.models.Time
class ConfigMigration(app: App, config: Config) {
init { config.apply {
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
val s = "app.appConfig"
if (dataVersion < 1) {
ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1
sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true
sync.interval = p.getString("$s.registerSyncEnabled", null)?.toIntOrNull() ?: 3600
val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str ->
str.replace("[\\[\\]]*".toRegex(), "")
.split(",\\s?".toRegex())
.mapNotNull { it.toIntOrNull() }
}
ui.miniMenuButtons = oldButtons ?: listOf(
MainActivity.DRAWER_ITEM_HOME,
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
MainActivity.DRAWER_ITEM_GRADES,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_HOMEWORK,
MainActivity.DRAWER_ITEM_SETTINGS
)
dataVersion = 1
}
if (dataVersion < 2) {
devModePassword = p.getString("$s.devModePassword", null).fix()
sync.tokenApp = p.getString("$s.fcmToken", null).fix()
timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0
sync.quietHoursStart = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0
appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0
sync.quietHoursEnd = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0
timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false
ui.headerBackground = p.getString("$s.headerBackground", null).fix()
ui.appBackground = p.getString("$s.appBackground", null).fix()
ui.language = p.getString("$s.language", null).fix()
appVersion = p.getString("$s.lastAppVersion", null)?.toIntOrNull() ?: BuildConfig.VERSION_CODE
appInstalledTime = p.getString("$s.appInstalledTime", null)?.toLongOrNull() ?: 0
grades.orderBy = p.getString("$s.gradesOrderBy", null)?.toIntOrNull() ?: 0
sync.quietDuringLessons = p.getString("$s.quietDuringLessons", null)?.toBoolean() ?: false
ui.miniMenuVisible = p.getString("$s.miniDrawerVisible", null)?.toBoolean() ?: false
loginFinished = p.getString("$s.loginFinished", null)?.toBoolean() ?: false
sync.onlyWifi = p.getString("$s.registerSyncOnlyWifi", null)?.toBoolean() ?: false
sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true
timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) }
sync.tokenMobidziennikList = listOf()
sync.tokenVulcanList = listOf()
sync.tokenLibrusList = listOf()
val tokens = p.getString("$s.fcmTokens", null)?.let { Gson().fromJson<Map<Int, Pair<String, List<Int>>>>(it, object: TypeToken<Map<Int, Pair<String, List<Int>>>>(){}.type) }
tokens?.forEach {
val token = it.value.first
when (it.key) {
LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token
LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token
LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token
}
}
dataVersion = 2
}
}}
private fun String?.fix(): String? {
return this?.replace("\"", "")?.let { if (it == "null") null else it }
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-1.
*/
package pl.szczodrzynski.edziennik.config.utils
import android.content.Context
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.config.Config
class ProfileConfigMigration(app: App, config: Config) {
init { config.apply {
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
val s = "app.appConfig"
if (dataVersion < 1) {
//dataVersion = 1
}
if (dataVersion < 2) {
//gradesColorMode do profilu !
//agendaViewType do profilu !
// app.appConfig.dontCountZeroToAverage do profilu !
}
}}
}

View File

@ -10,6 +10,8 @@ import androidx.room.TypeConverters;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import pl.szczodrzynski.edziennik.config.db.ConfigDao;
import pl.szczodrzynski.edziennik.config.db.ConfigEntry;
import pl.szczodrzynski.edziennik.data.db.converters.ConverterDate;
import pl.szczodrzynski.edziennik.data.db.converters.ConverterDateInt;
import pl.szczodrzynski.edziennik.data.db.converters.ConverterJsonObject;
@ -105,7 +107,8 @@ import pl.szczodrzynski.edziennik.utils.models.Date;
NoticeType.class,
AttendanceType.class,
pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson.class,
Metadata.class}, version = 64)
ConfigEntry.class,
Metadata.class}, version = 66)
@TypeConverters({
ConverterTime.class,
ConverterDate.class,
@ -144,6 +147,7 @@ public abstract class AppDb extends RoomDatabase {
public abstract NoticeTypeDao noticeTypeDao();
public abstract AttendanceTypeDao attendanceTypeDao();
public abstract TimetableDao timetableDao();
public abstract ConfigDao configDao();
public abstract MetadataDao metadataDao();
private static volatile AppDb INSTANCE;
@ -763,6 +767,27 @@ public abstract class AppDb extends RoomDatabase {
database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);");
}
};
private static final Migration MIGRATION_64_65 = new Migration(64, 65) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE config (" +
"profileId INTEGER NOT NULL DEFAULT -1," +
"`key` TEXT NOT NULL," +
"value TEXT NOT NULL," +
"PRIMARY KEY(profileId, `key`));");
}
};
private static final Migration MIGRATION_65_66 = new Migration(65, 66) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE config;");
database.execSQL("CREATE TABLE config (" +
"profileId INTEGER NOT NULL DEFAULT -1," +
"`key` TEXT NOT NULL," +
"value TEXT," +
"PRIMARY KEY(profileId, `key`));");
}
};
public static AppDb getDatabase(final Context context) {
@ -824,7 +849,9 @@ public abstract class AppDb extends RoomDatabase {
MIGRATION_60_61,
MIGRATION_61_62,
MIGRATION_62_63,
MIGRATION_63_64
MIGRATION_63_64,
MIGRATION_64_65,
MIGRATION_65_66
)
.allowMainThreadQueries()
//.fallbackToDestructiveMigration()

View File

@ -1,16 +1,17 @@
package pl.szczodrzynski.edziennik.data.db.modules.events;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Transaction;
import androidx.annotation.NonNull;
import android.util.Log;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
@ -130,6 +131,12 @@ public abstract class EventDao {
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate")
public abstract void removeFuture(int profileId, Date todayDate);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType = :type")
public abstract void removeFutureWithType(int profileId, Date todayDate, int type);
@Query("DELETE FROM events WHERE profileId = :profileId AND eventAddedManually = 0 AND eventDate >= :todayDate AND eventType != :exceptType")
public abstract void removeFutureExceptType(int profileId, Date todayDate, int exceptType);
@Query("UPDATE metadata SET seen = :seen WHERE profileId = :profileId AND (thingType = "+TYPE_EVENT+" OR thingType = "+TYPE_LESSON_CHANGE+" OR thingType = "+TYPE_HOMEWORK+") AND thingId IN (SELECT eventId FROM events WHERE profileId = :profileId AND eventDate = :date)")
public abstract void setSeenByDate(int profileId, Date date, boolean seen);

View File

@ -1,18 +1,16 @@
package pl.szczodrzynski.edziennik.data.db.modules.grades;
import android.util.LongSparseArray;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.RawQuery;
import androidx.room.Transaction;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
@ -133,4 +131,8 @@ public abstract class GradeDao {
}
}
}
public LiveData<List<GradeFull>> getAllFromDate(int profileId, int semester, long date) {
return getAllWhere(profileId, "gradeSemester = " + semester + " AND addedDate > " + date);
}
}

View File

@ -9,7 +9,6 @@ import androidx.room.Transaction;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice;
import pl.szczodrzynski.edziennik.data.db.modules.announcements.Announcement;
import pl.szczodrzynski.edziennik.data.db.modules.attendance.Attendance;
import pl.szczodrzynski.edziennik.data.db.modules.events.Event;
@ -17,6 +16,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message;
import pl.szczodrzynski.edziennik.data.db.modules.notices.Notice;
import pl.szczodrzynski.edziennik.utils.models.UnreadCounter;
import static pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.TYPE_ANNOUNCEMENT;
@ -85,6 +85,11 @@ public abstract class MetadataDao {
updateSeen(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).changeId, seen);
}
}
if (o instanceof pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull) {
if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull) o).getId(), seen, false, 0)) == -1) {
updateSeen(profileId, TYPE_LESSON_CHANGE, ((pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull) o).getId(), seen);
}
}
if (o instanceof Announcement) {
if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, seen, false, 0)) == -1) {
updateSeen(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, seen);
@ -129,6 +134,11 @@ public abstract class MetadataDao {
updateNotified(profileId, TYPE_LESSON_CHANGE, ((LessonFull) o).changeId, notified);
}
}
if (o instanceof pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull) {
if (add(new Metadata(profileId, TYPE_LESSON_CHANGE, ((pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull) o).getId(), false, notified, 0)) == -1) {
updateNotified(profileId, TYPE_LESSON_CHANGE, ((pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull) o).getId(), notified);
}
}
if (o instanceof Announcement) {
if (add(new Metadata(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, false, notified, 0)) == -1) {
updateNotified(profileId, TYPE_ANNOUNCEMENT, ((Announcement) o).id, notified);

View File

@ -1,5 +1,6 @@
package pl.szczodrzynski.edziennik.data.db.modules.profiles;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
@ -24,6 +25,7 @@ public interface ProfileDao {
@Query("SELECT profiles.*, loginStores.loginStoreType, loginStores.loginStoreData FROM profiles LEFT JOIN loginStores ON profiles.loginStoreId = loginStores.loginStoreId WHERE profileId = :profileId")
LiveData<ProfileFull> getById(int profileId);
@Nullable
@Query("SELECT profiles.*, loginStores.loginStoreType, loginStores.loginStoreData FROM profiles LEFT JOIN loginStores ON profiles.loginStoreId = loginStores.loginStoreId WHERE profileId = :profileId")
ProfileFull getFullByIdNow(int profileId);

View File

@ -2,14 +2,15 @@ package pl.szczodrzynski.edziennik.data.db.modules.profiles
import android.content.Context
import androidx.room.ColumnInfo
import androidx.room.Ignore
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.*
@ -74,6 +75,7 @@ class ProfileFull : Profile {
fragmentIds.add(DRAWER_ITEM_AGENDA)
fragmentIds.add(DRAWER_ITEM_GRADES)
fragmentIds.add(DRAWER_ITEM_MESSAGES)
fragmentIds.add(DRAWER_ITEM_HOMEWORK)
fragmentIds.add(DRAWER_ITEM_BEHAVIOUR)
fragmentIds.add(DRAWER_ITEM_ATTENDANCE)
fragmentIds.add(DRAWER_ITEM_ANNOUNCEMENTS)
@ -87,9 +89,7 @@ class ProfileFull : Profile {
return fragmentIds
}
constructor() : super() {
}
constructor() : super()
constructor(profile: Profile, loginStore: LoginStore) {
@ -122,7 +122,10 @@ class ProfileFull : Profile {
}*/
}
constructor(context: Context) : super(context) {}
constructor(context: Context) : super(context)
@Ignore
constructor(id: Int, name: String, subname: String, loginStoreId: Int) : super(id, name, subname, loginStoreId)
fun canChangeLoginPassword(): Boolean {
return loginStoreType == LOGIN_TYPE_MOBIDZIENNIK || loginStoreType == LOGIN_TYPE_LIBRUS || loginStoreType == LOGIN_TYPE_IUCZNIOWIE

View File

@ -59,6 +59,11 @@ open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
return startTime ?: oldStartTime
}
val isCancelled
get() = type == TYPE_CANCELLED || type == TYPE_SHIFTED_SOURCE
val isChange
get() = type == TYPE_CHANGE || type == TYPE_SHIFTED_TARGET
fun buildId(): Long = (displayDate?.combineWith(displayStartTime) ?: 0L) / 6L * 10L + (hashCode() and 0xFFFF)
override fun toString(): String {
@ -110,7 +115,7 @@ open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
return true
}
override fun hashCode(): Int {
override fun hashCode(): Int { // intentionally ignoring ID and display* here
var result = profileId
result = 31 * result + type
result = 31 * result + (date?.hashCode() ?: 0)
@ -131,32 +136,4 @@ open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
result = 31 * result + (oldClassroom?.hashCode() ?: 0)
return result
}
}
/*
DROP TABLE lessons;
DROP TABLE lessonChanges;
CREATE TABLE lessons (
profileId INTEGER NOT NULL,
type INTEGER NOT NULL,
date TEXT DEFAULT NULL,
lessonNumber INTEGER DEFAULT NULL,
startTime TEXT DEFAULT NULL,
endTime TEXT DEFAULT NULL,
teacherId INTEGER DEFAULT NULL,
subjectId INTEGER DEFAULT NULL,
teamId INTEGER DEFAULT NULL,
classroom TEXT DEFAULT NULL,
oldDate TEXT DEFAULT NULL,
oldLessonNumber INTEGER DEFAULT NULL,
oldStartTime TEXT DEFAULT NULL,
oldEndTime TEXT DEFAULT NULL,
oldTeacherId INTEGER DEFAULT NULL,
oldSubjectId INTEGER DEFAULT NULL,
oldTeamId INTEGER DEFAULT NULL,
oldClassroom TEXT DEFAULT NULL,
PRIMARY KEY(profileId)
);
*/
}

View File

@ -84,24 +84,44 @@ class LessonFull(profileId: Int, id: Long) : Lesson(profileId, id) {
})
}
val changeSubjectName: String
get() {
val first = when (type) {
TYPE_CHANGE, TYPE_CANCELLED, TYPE_SHIFTED_SOURCE -> oldSubjectName
else -> subjectName
}
val second = when (type) {
TYPE_CHANGE -> subjectName
else -> null
}
return when (second) {
null -> first ?: ""
else -> "$first -> $second"
}
private fun changeText(actual: String?, old: String?): String {
val first = when (type) {
TYPE_CHANGE, TYPE_CANCELLED, TYPE_SHIFTED_SOURCE -> old
else -> actual
}
val second = when (type) {
TYPE_CHANGE -> actual
else -> null
}
return when (second) {
null -> first.orEmpty()
first -> second
else -> if (first != null) "$first -> $second" else second.orEmpty()
}
}
val changeSubjectName: String
get() = changeText(subjectName, oldSubjectName)
val isSubjectNameChanged: Boolean
get() = type == TYPE_CHANGE && subjectName != oldSubjectName
val changeTeacherName: String
get() = changeText(teacherName, oldTeacherName)
val isTeacherNameChanged: Boolean
get() = type == TYPE_CHANGE && teacherName != oldTeacherName
val changeClassroom: String
get() = changeText(classroom, oldClassroom)
val isClassroomChanged: Boolean
get() = type == TYPE_CHANGE && classroom != oldClassroom
// metadata
var seen: Boolean = false
var notified: Boolean = false

View File

@ -52,6 +52,13 @@ interface TimetableDao {
""")
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
ORDER BY id, type
""")
fun getForDateNow(profileId: Int, date: Date) : List<LessonFull>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId

View File

@ -90,12 +90,12 @@ public class ServerRequest {
.addParameter("device_id", Settings.Secure.getString(app.getContext().getContentResolver(), Settings.Secure.ANDROID_ID))
.addParameter("device_model", Build.MANUFACTURER+" "+Build.MODEL)
.addParameter("device_os_version", Build.VERSION.RELEASE)
.addParameter("fcm_token", app.appConfig.fcmToken)
.addParameter("fcm_token", app.config.getSync().getTokenApp())
.addParameter("signature", sign(app.signature, timestamp))
.addParameter("signature_timestamp", timestamp)
.addParameter("package_name", "pl.szczodrzynski.edziennik")
.addParameter("source", source)
.addParameter("update_frequency", app.appConfig.registerSyncEnabled ? app.appConfig.registerSyncInterval : -1)
.addParameter("update_frequency", app.config.getSync().getEnabled() ? app.config.getSync().getInterval() : -1)
.post()
.callback(new JsonCallbackHandler() {
@Override
@ -127,12 +127,12 @@ public class ServerRequest {
.addParameter("device_id", Settings.Secure.getString(app.getContext().getContentResolver(), Settings.Secure.ANDROID_ID))
.addParameter("device_model", Build.MANUFACTURER+" "+Build.MODEL)
.addParameter("device_os_version", Build.VERSION.RELEASE)
.addParameter("fcm_token", app.appConfig.fcmToken)
.addParameter("fcm_token", app.config.getSync().getTokenApp())
.addParameter("signature", sign(app.signature, timestamp))
.addParameter("signature_timestamp", timestamp)
.addParameter("package_name", "pl.szczodrzynski.edziennik")
.addParameter("source", source)
.addParameter("update_frequency", app.appConfig.registerSyncEnabled ? app.appConfig.registerSyncInterval : -1)
.addParameter("update_frequency", app.config.getSync().getEnabled() ? app.config.getSync().getInterval() : -1)
.post()
.build()
.execute();

View File

@ -47,9 +47,8 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
Log.d(TAG, "New token: "+s);
App app = (App)getApplicationContext();
if (app.appConfig.fcmToken == null || !app.appConfig.fcmToken.equals(s)) {
app.appConfig.fcmToken = s;
app.saveConfig();
if (app.config.getSync().getTokenApp() == null || !app.config.getSync().getTokenApp().equals(s)) {
app.config.getSync().setTokenApp(s);
}
}
@ -198,8 +197,7 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
else {
feedbackMessage.sentTime = Long.parseLong(remoteMessage.getData().get("sent_time"));
if (feedbackMessage.text.startsWith("devmode")) {
app.appConfig.devModePassword = feedbackMessage.text.replace("devmode", "");
app.saveConfig("devModePassword");
app.config.setDevModePassword(feedbackMessage.text.replace("devmode", ""));
app.checkDevModePassword();
feedbackMessage.text = "devmode "+(App.devMode ? "allowed" : "disallowed");
}

View File

@ -57,16 +57,16 @@ class SyncWorker(val context: Context, val params: WorkerParameters) : Worker(co
/**
* Cancel any existing sync jobs and schedule a new one.
*
* If [registerSyncEnabled] is not true, just cancel every job.
* If [ConfigSync.enabled] is not true, just cancel every job.
*/
fun rescheduleNext(app: App) {
cancelNext(app)
val enableSync = app.appConfig.registerSyncEnabled
val enableSync = app.config.sync.enabled
if (!enableSync) {
return
}
val onlyWifi = app.appConfig.registerSyncOnlyWifi
val syncInterval = app.appConfig.registerSyncInterval.toLong()
val onlyWifi = app.config.sync.onlyWifi
val syncInterval = app.config.sync.interval.toLong()
val syncAt = System.currentTimeMillis() + syncInterval*1000
d(TAG, "Scheduling work at ${syncAt.formatDate()}")

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-30
*/
package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull
import pl.szczodrzynski.edziennik.databinding.RowDialogEventListItemBinding
import pl.szczodrzynski.edziennik.utils.Utils.bs
import pl.szczodrzynski.edziennik.utils.models.Date
class EventListAdapter(
val context: Context,
val parentDialog: EventListDialog
) : RecyclerView.Adapter<EventListAdapter.ViewHolder>() {
private val app by lazy { context.applicationContext as App }
val eventList = mutableListOf<EventFull>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view: RowDialogEventListItemBinding = DataBindingUtil.inflate(inflater, R.layout.row_dialog_event_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val event = eventList[position]
holder.apply {
b.eventListItemRoot.background.colorFilter = when (event.type) {
Event.TYPE_HOMEWORK -> PorterDuffColorFilter((0xffffffff).toInt(), PorterDuff.Mode.CLEAR)
else -> PorterDuffColorFilter(event.color, PorterDuff.Mode.MULTIPLY)
}
b.eventListItemStartTime.text = if (event.startTime == null) app.getString(R.string.event_all_day) else event.startTime?.stringHM
b.eventListItemTeamName.text = bs(event.teamName)
b.eventListItemTeacherName.text = app.getString(R.string.concat_2_strings, bs(null, event.teacherFullName, "\n"), bs(event.subjectLongName))
b.eventListItemAddedDate.text = Date.fromMillis(event.addedDate).formattedStringShort
b.eventListItemType.text = event.typeName
b.eventListItemTopic.text = event.topic
b.eventListItemHomework.visibility = if (event.type == Event.TYPE_HOMEWORK) View.VISIBLE else View.GONE
b.eventListItemSharedBy.text = app.getString(R.string.event_shared_by_format, if (event.sharedBy == "self") app.getString(R.string.event_shared_by_self) else event.sharedByName)
b.eventListItemSharedBy.visibility = if (event.sharedByName.isNullOrBlank()) View.GONE else View.VISIBLE
b.eventListItemEdit.visibility = if (event.addedManually) View.VISIBLE else View.GONE
b.eventListItemEdit.setOnClickListener {
parentDialog.dismiss()
EventManualV2Dialog(
context as MainActivity,
event.profileId,
editingEvent = event,
onShowListener = parentDialog.onShowListener,
onDismissListener = parentDialog.onDismissListener
)
}
}
}
override fun getItemCount(): Int = eventList.size
class ViewHolder(val b: RowDialogEventListItemBinding) : RecyclerView.ViewHolder(b.root)
}

View File

@ -3,14 +3,15 @@ package pl.szczodrzynski.edziennik.ui.dialogs.event;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import com.mikepenz.iconics.view.IconicsImageView;
import com.mikepenz.iconics.view.IconicsTextView;
@ -20,21 +21,21 @@ import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.events.Event;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.Date;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
import static pl.szczodrzynski.edziennik.utils.Utils.d;
public class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> {
private static final String TAG = "EventListAdapter";
public class EventListAdapterOld extends RecyclerView.Adapter<EventListAdapterOld.ViewHolder> {
private static final String TAG = "EventListAdapterOld";
private Context context;
private List<EventFull> examList;
private EventListDialog parentDialog;
private EventListDialogOld parentDialog;
//getting the context and product list with constructor
public EventListAdapter(Context mCtx, List<EventFull> examList, EventListDialog parentDialog) {
public EventListAdapterOld(Context mCtx, List<EventFull> examList, EventListDialogOld parentDialog) {
this.context = mCtx;
this.examList = examList;
this.parentDialog = parentDialog;
@ -42,15 +43,15 @@ public class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.View
@NonNull
@Override
public EventListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
public EventListAdapterOld.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//inflating and returning our view holder
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.row_dialog_event_list_item, parent, false);
return new EventListAdapter.ViewHolder(view);
return new EventListAdapterOld.ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull EventListAdapter.ViewHolder holder, int position) {
public void onBindViewHolder(@NonNull EventListAdapterOld.ViewHolder holder, int position) {
App app = (App) context.getApplicationContext();
EventFull event = examList.get(position);

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-30
*/
package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.Typeface
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventListBinding
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import kotlin.coroutines.CoroutineContext
class EventListDialog(
val activity: AppCompatActivity,
val profileId: Int,
val date: Date,
val time: Time? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope {
companion object {
const val TAG = "EventListDialog"
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val app by lazy { activity.application as App }
private lateinit var b: DialogEventListBinding
private lateinit var dialog: AlertDialog
private lateinit var adapter: EventListAdapter
private var lesson: LessonFull? = null
init {
run {
if (activity.isFinishing)
return@run
job = Job()
onShowListener?.invoke(TAG)
b = DialogEventListBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(date.formattedString + (time?.let { ", " + it.stringHM } ?: ""))
.setView(b.root)
.setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() }
.setNeutralButton(R.string.add) { _, _ ->
EventManualV2Dialog(
activity,
lesson?.profileId ?: profileId,
lesson,
date,
time,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
}
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
app.db.timetableDao().getForDate(profileId, date).observe(activity, Observer { lessons ->
lesson = lessons.firstOrNull { it.displayStartTime == time }
update()
})
}
}
fun dismiss() = dialog.dismiss()
private fun update() {
b.eventListLessonDetails.visibility = if (lesson == null) View.GONE else View.VISIBLE
if (lesson != null) {
dialog.setTitle(if (time == null) date.formattedString else (lesson?.displaySubjectName
?: date.formattedString) + ", " + time.stringHM)
b.eventListLessonDate.text = app.getString(R.string.date_time_format, date.formattedString, "")
if (lesson?.type == Lesson.TYPE_CANCELLED) {
b.eventListLessonChange.text = app.getString(R.string.lesson_cancelled)
b.eventListLessonChange.setTypeface(null, Typeface.BOLD_ITALIC)
b.eventListTeacher.visibility = View.GONE
b.eventListClassroom.visibility = View.GONE
} else {
b.eventListLessonChange.text = lesson?.changeSubjectName
b.eventListLessonChange.setTypeface(null, Typeface.ITALIC)
b.eventListLessonChange.visibility = if (lesson?.isSubjectNameChanged == true) View.VISIBLE else View.GONE
b.eventListTeacher.text = lesson?.changeTeacherName
b.eventListTeacher.setTypeface(null, if (lesson?.isTeacherNameChanged == true) Typeface.ITALIC else Typeface.NORMAL)
b.eventListClassroom.text = lesson?.changeClassroom
b.eventListClassroom.setTypeface(null, if (lesson?.isClassroomChanged == true) Typeface.ITALIC else Typeface.NORMAL)
}
}
b.eventListView.apply {
setHasFixedSize(false)
isNestedScrollingEnabled = true
layoutManager = LinearLayoutManager(activity)
}
adapter = EventListAdapter(activity, this@EventListDialog)
b.eventListView.adapter = adapter
app.db.eventDao().getAllByDateTime(profileId, date, time).observe(activity, Observer { events ->
if (events.isNullOrEmpty()) {
b.eventListView.visibility = View.GONE
b.textNoEvents.visibility = View.VISIBLE
} else {
adapter.run {
eventList.apply {
clear()
addAll(events)
}
notifyDataSetChanged()
}
}
})
}
}

View File

@ -8,14 +8,14 @@ import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialdialogs.MaterialDialog;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange;
@ -26,16 +26,16 @@ import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
public class EventListDialog {
public class EventListDialogOld {
private App app;
private Context context;
private int profileId;
public EventListDialog(Context context) {
public EventListDialogOld(Context context) {
this.context = context;
this.profileId = App.profileId;
}
public EventListDialog(Context context, int profileId) {
public EventListDialogOld(Context context, int profileId) {
this.context = context;
this.profileId = profileId;
}
@ -47,7 +47,7 @@ public class EventListDialog {
public boolean callDismissListener = true;
private LessonFull lesson;
public EventListDialog withDismissListener(DialogInterface.OnDismissListener dismissListener) {
public EventListDialogOld withDismissListener(DialogInterface.OnDismissListener dismissListener) {
this.dismissListener = dismissListener;
return this;
}
@ -222,7 +222,7 @@ public class EventListDialog {
dialogView.findViewById(R.id.textNoEvents).setVisibility(View.VISIBLE);
}
else {
EventListAdapter adapter = new EventListAdapter(context, events, this);
EventListAdapterOld adapter = new EventListAdapterOld(context, events, this);
examsView.setAdapter(adapter);
}
});

View File

@ -7,7 +7,9 @@ package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker
@ -18,7 +20,9 @@ import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
@ -68,25 +72,27 @@ class EventManualV2Dialog(
.setTitle(R.string.dialog_event_manual_title)
.setView(b.root)
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setPositiveButton(R.string.save) { _, _ -> saveEvent() }
.setPositiveButton(R.string.save, null)
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show()
.create()
.apply {
setOnShowListener { dialog ->
val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE)
positiveButton.setOnClickListener {
saveEvent()
}
}
show()
}
event = editingEvent?.clone() ?: Event().also { event ->
event.profileId = profileId
/*defaultDate?.let {
event.eventDate = it
b.date = it
}
defaultTime?.let {
event.startTime = it
b.time = it
}
defaultType?.let {
event.type = it
}*/
}
b.shareSwitch.isChecked = event.sharedBy != null
}
@ -180,6 +186,8 @@ class EventManualV2Dialog(
b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId)
b.topic.setText(it.topic)
b.shareSwitch.isChecked = true
b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color
}
@ -288,6 +296,16 @@ class EventManualV2Dialog(
val dates = deferred.await()
b.dateDropdown.clear().append(dates)
defaultDate?.let {
event.eventDate = it
if (b.dateDropdown.select(it) == null)
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
}
editingEvent?.eventDate?.let {
b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
@ -425,6 +443,16 @@ class EventManualV2Dialog(
b.teacherDropdown.deselect()
}
else {
defaultTime?.let {
event.startTime = it
if (b.timeDropdown.select(it) == null)
b.timeDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.stringHM,
tag = it
))
}
editingEvent?.let {
b.timeDropdown.select(it.startTime?.value?.toLong())
}
@ -442,17 +470,16 @@ class EventManualV2Dialog(
// attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item ->
when {
when (item.id) {
// no lessons this day
item.id == -2L -> {
-2L -> {
b.timeDropdown.deselect()
return@setOnChangeListener false
}
// custom start hour
item.id == -1L -> {
return@setOnChangeListener false
}
// custom start hour
-1L -> return@setOnChangeListener false
// selected a specific lesson
else -> {
if (item.tag is LessonFull) {
@ -461,7 +488,7 @@ class EventManualV2Dialog(
b.teamDropdown.deselect()
b.subjectDropdown.deselect()
b.teacherDropdown.deselect()
item.tag.displayTeamId?.let {
item.tag.displayTeamId?.let {
b.teamDropdown.select(it)
}
item.tag.displaySubjectId?.let {
@ -479,6 +506,80 @@ class EventManualV2Dialog(
}
private fun saveEvent() {
val date = b.dateDropdown.selected?.tag.instanceOfOrNull<Date>()
val lesson = b.timeDropdown.selected?.tag.instanceOfOrNull<LessonFull>()
val team = b.teamDropdown.selected?.tag.instanceOfOrNull<Team>()
val share = b.shareSwitch.isChecked
val type = b.typeDropdown.selected?.tag.instanceOfOrNull<EventType>()
val topic = b.topic.text?.toString()
val subject = b.subjectDropdown.selected?.tag.instanceOfOrNull<Subject>()
val teacher = b.teacherDropdown.selected?.tag.instanceOfOrNull<Teacher>()
b.teamDropdown.error = null
b.typeDropdown.error = null
b.topic.error = null
var isError = false
if (share && team == null) {
b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose)
isError = true
}
if (type == null) {
b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose)
isError = true
}
if (topic.isNullOrBlank()) {
b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose)
isError = true
}
if (isError) return
val id = System.currentTimeMillis()
val eventObject = Event(
profileId,
id,
date,
lesson?.displayStartTime,
topic,
customColor ?: -1,
type?.id?.toInt() ?: Event.TYPE_DEFAULT,
true,
teacher?.id ?: -1,
subject?.id ?: -1,
team?.id ?: -1
)
val metadataObject = Metadata(
profileId,
when (type?.id?.toInt()) {
Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK
else -> Metadata.TYPE_EVENT
},
eventObject.id,
true,
true,
System.currentTimeMillis()
)
finishAdding(eventObject, metadataObject)
}
private fun finishAdding(eventObject: Event, metadataObject: Metadata) {
launch {
val deferred = async(Dispatchers.Default) {
app.db.eventDao().add(eventObject)
app.db.metadataDao().add(metadataObject)
}
deferred.await()
}
dialog.dismiss()
Toast.makeText(app, R.string.saved, Toast.LENGTH_SHORT).show()
}
}

View File

@ -42,7 +42,7 @@ class LessonDetailsDialog(
.setPositiveButton(R.string.close) { dialog, _ ->
dialog.dismiss()
}
.setNeutralButton(R.string.add) { dialog, _ ->
.setNeutralButton(R.string.add) { _, _ ->
EventManualV2Dialog(
activity,
lesson.profileId,

View File

@ -84,7 +84,7 @@ public class AgendaFragment extends Fragment {
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
viewType = app.profile.getAgendaViewType();
viewType = app.config.getUi().getAgendaViewType();
if (viewType == AGENDA_DEFAULT) {
b_default = DataBindingUtil.inflate(inflater, R.layout.fragment_agenda_default, container, false);
return b_default.getRoot();
@ -128,8 +128,7 @@ public class AgendaFragment extends Fragment {
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
viewType = viewType == AGENDA_DEFAULT ? AGENDA_CALENDAR : AGENDA_DEFAULT;
app.profile.setAgendaViewType(viewType);
app.profileSaveAsync();
app.config.getUi().setAgendaViewType(viewType);
activity.reloadTarget();
}),
new BottomSheetSeparatorItem(true),
@ -311,7 +310,7 @@ public class AgendaFragment extends Fragment {
int scrolledDate = Date.fromCalendar(calendar).getValue();
if (unreadEventDates.contains(scrolledDate)) {
AsyncTask.execute(() -> app.db.eventDao().setSeenByDate(App.profileId, Date.fromYmd(intToStr(scrolledDate)), true));
unreadEventDates.remove(unreadEventDates.indexOf(scrolledDate));
unreadEventDates.remove((Integer) scrolledDate);
}
}
@ -319,9 +318,23 @@ public class AgendaFragment extends Fragment {
public void onEventSelected(CalendarEvent calendarEvent) {
if (calendarEvent instanceof BaseCalendarEvent) {
if (!calendarEvent.isPlaceholder() && !calendarEvent.isAllDay()) {
new EventListDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()), Time.fromMillis(calendarEvent.getStartTime().getTimeInMillis()), true);
// new EventListDialogOld(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()), Time.fromMillis(calendarEvent.getStartTime().getTimeInMillis()), true);
new EventListDialog(
activity,
App.profileId,
Date.fromCalendar(calendarEvent.getInstanceDay()),
Time.fromMillis(calendarEvent.getStartTime().getTimeInMillis()),
null,
null);
} else {
new EventListDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()));
// new EventListDialogOld(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()));
new EventListDialog(
activity,
App.profileId,
Date.fromCalendar(calendarEvent.getInstanceDay()),
null,
null,
null);
}
} else if (calendarEvent instanceof LessonChangeEvent) {
new LessonChangeDialog(activity).show(app, Date.fromCalendar(calendarEvent.getInstanceDay()));
@ -403,10 +416,18 @@ public class AgendaFragment extends Fragment {
int scrolledDate = dayDate.getValue();
if (unreadEventDates.contains(scrolledDate)) {
AsyncTask.execute(() -> app.db.eventDao().setSeenByDate(App.profileId, Date.fromYmd(intToStr(scrolledDate)), true));
unreadEventDates.remove(unreadEventDates.indexOf(scrolledDate));
unreadEventDates.remove((Integer) scrolledDate);
}
new EventListDialog(getContext()).show(app, dayDate);
// new EventListDialogOld(getContext()).show(app, dayDate);
new EventListDialog(
activity,
App.profileId,
dayDate,
null,
null,
null
);
});
b_calendar.progressBar.setVisibility(View.GONE);
});

View File

@ -23,6 +23,7 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.config.ProfileConfigGrades;
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade;
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeFull;
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject;
@ -32,15 +33,15 @@ import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem;
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem;
import static pl.szczodrzynski.edziennik.config.ConfigGrades.ORDER_BY_DATE_ASC;
import static pl.szczodrzynski.edziennik.config.ConfigGrades.ORDER_BY_DATE_DESC;
import static pl.szczodrzynski.edziennik.config.ConfigGrades.ORDER_BY_SUBJECT_ASC;
import static pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.TYPE_GRADE;
import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.YEAR_1_AVG_2_AVG;
import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.YEAR_1_AVG_2_SEM;
import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.YEAR_1_SEM_2_AVG;
import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.YEAR_1_SEM_2_SEM;
import static pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.YEAR_ALL_GRADES;
import static pl.szczodrzynski.edziennik.utils.models.AppConfig.ORDER_BY_DATE_ASC;
import static pl.szczodrzynski.edziennik.utils.models.AppConfig.ORDER_BY_DATE_DESC;
import static pl.szczodrzynski.edziennik.utils.models.AppConfig.ORDER_BY_SUBJECT_ASC;
public class GradesFragment extends Fragment {
@ -135,12 +136,12 @@ public class GradesFragment extends Fragment {
.withIcon(CommunityMaterial.Icon2.cmd_palette_outline)
.withOnClickListener(v3 -> {
activity.getBottomSheet().close();
ProfileConfigGrades config = app.config.getFor(app.profileId).getGrades();
new MaterialDialog.Builder(activity)
.title(R.string.dialog_grades_color_mode_title)
.items(R.array.dialog_grades_color_modes)
.itemsCallbackSingleChoice(app.profile.getGradeColorMode(), (dialog, view1, which, text) -> {
app.profile.setGradeColorMode(which);
app.profileSaveAsync();
.itemsCallbackSingleChoice(config.getColorMode(), (dialog, view1, which, text) -> {
config.setColorMode(which);
activity.reloadTarget();
return true;
})
@ -154,9 +155,8 @@ public class GradesFragment extends Fragment {
new MaterialDialog.Builder(activity)
.title(R.string.dialog_grades_sort_title)
.items(R.array.dialog_grades_sort_modes)
.itemsCallbackSingleChoice(app.appConfig.gradesOrderBy, (dialog, view1, which, text) -> {
app.appConfig.gradesOrderBy = which;
app.saveConfig("gradesOrderBy");
.itemsCallbackSingleChoice(app.config.getGrades().getOrderBy(), (dialog, view1, which, text) -> {
app.config.getGrades().setOrderBy(which);
activity.reloadTarget();
return true;
})
@ -228,13 +228,13 @@ public class GradesFragment extends Fragment {
long finalExpandSubjectId = expandSubjectId;
String orderBy;
if (app.appConfig.gradesOrderBy == ORDER_BY_SUBJECT_ASC) {
if (app.config.getGrades().getOrderBy() == ORDER_BY_SUBJECT_ASC) {
orderBy = "subjectLongName ASC, addedDate DESC";
}
else if (app.appConfig.gradesOrderBy == ORDER_BY_DATE_DESC) {
else if (app.config.getGrades().getOrderBy() == ORDER_BY_DATE_DESC) {
orderBy = "addedDate DESC";
}
else if (app.appConfig.gradesOrderBy == ORDER_BY_DATE_ASC) {
else if (app.config.getGrades().getOrderBy() == ORDER_BY_DATE_ASC) {
orderBy = "addedDate ASC";
}
else {
@ -247,11 +247,13 @@ public class GradesFragment extends Fragment {
subjectList = new ArrayList<>();
ProfileConfigGrades config = app.config.getFor(app.profileId).getGrades();
// now we have all grades from the newest to the oldest
for (GradeFull grade: grades) {
ItemGradesSubjectModel model = ItemGradesSubjectModel.searchModelBySubjectId(subjectList, grade.subjectId);
if (model == null) {
model = new ItemGradesSubjectModel(app.profile, new Subject(App.profileId, grade.subjectId, grade.subjectLongName, grade.subjectShortName), new ArrayList<>(), new ArrayList<>());//ItemGradesSubjectModel.searchModelBySubjectId(subjectList, grade.subjectId);
model = new ItemGradesSubjectModel(app.profile, new Subject(app.profileId, grade.subjectId, grade.subjectLongName, grade.subjectShortName), new ArrayList<>(), new ArrayList<>());//ItemGradesSubjectModel.searchModelBySubjectId(subjectList, grade.subjectId);
subjectList.add(model);
if (model.subject != null && model.subject.id == finalExpandSubjectId) {
model.expandView = true;
@ -303,7 +305,7 @@ public class GradesFragment extends Fragment {
// do not show *normal* grades with negative weight - these are historical grades - Iuczniowie
continue;
}
if (app.appConfig.dontCountZeroToAverage && grade.name.equals("0")) {
if (!config.getCountZeroToAvg() && grade.name.equals("0")) {
weight = 0;
}
float valueWeighted = grade.value * weight;

View File

@ -28,15 +28,15 @@ import java.text.DecimalFormat;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeFull;
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject;
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Colors;
import pl.szczodrzynski.edziennik.utils.Utils;
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@ -219,7 +219,7 @@ public class GradesSubjectAdapter extends ArrayAdapter<ItemGradesSubjectModel> i
layoutParams.setMargins(0, 0, _5dp, 0);
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
int maxWidthPx = displayMetrics.widthPixels - Utils.dpToPx((app.appConfig.miniDrawerVisible ? 72 : 0)/*miniDrawer size*/ + 8 + 8/*left and right offsets*/ + 24/*ellipsize width*/);
int maxWidthPx = displayMetrics.widthPixels - Utils.dpToPx((app.config.getUi().getMiniMenuVisible() ? 72 : 0)/*miniDrawer size*/ + 8 + 8/*left and right offsets*/ + 24/*ellipsize width*/);
int totalWidthPx = 0;
boolean ellipsized = false;

View File

@ -13,12 +13,12 @@ import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.databinding.FragmentGradesEditorBinding
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.Companion.YEAR_1_AVG_2_AVG
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.Companion.YEAR_1_AVG_2_SEM
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.Companion.YEAR_1_SEM_2_AVG
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile.Companion.YEAR_ALL_GRADES
import pl.szczodrzynski.edziennik.databinding.FragmentGradesEditorBinding
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Themes
import java.text.DecimalFormat
@ -34,6 +34,8 @@ class GradesEditorFragment : Fragment() {
private val navController: NavController by lazy { Navigation.findNavController(b.root) }
*/
private val config by lazy { app.config.getFor(App.profileId).grades }
private var subjectId: Long = -1
private var semester: Int = 1
@ -108,7 +110,7 @@ class GradesEditorFragment : Fragment() {
continue
}
var weight = editorGrade.weight
if (app.appConfig.dontCountZeroToAverage && editorGrade.name == "0") {
if (!config.countZeroToAvg && editorGrade.name == "0") {
weight = 0f
}
val value = editorGrade.value * weight
@ -173,7 +175,7 @@ class GradesEditorFragment : Fragment() {
averageSemester = 0f
for (editorGrade in editorGrades) {
var weight = editorGrade.weight
if (app.appConfig.dontCountZeroToAverage && editorGrade.name == "0") {
if (!config.countZeroToAvg && editorGrade.name == "0") {
weight = 0f
}
val value = editorGrade.value * weight
@ -216,7 +218,7 @@ class GradesEditorFragment : Fragment() {
continue
}
var weight = grade.weight
if (app.appConfig.dontCountZeroToAverage && grade.name == "0") {
if (!config.countZeroToAvg && grade.name == "0") {
weight = 0f
}
val value = grade.value * weight

View File

@ -49,17 +49,17 @@ public class CounterActivity extends AppCompatActivity {
Time now = Time.getNow();
Time syncedNow = now;
//Time updateDiff = null;
if (app.appConfig.bellSyncDiff != null) {
if (app.appConfig.bellSyncMultiplier < 0) {
if (app.config.getTimetable().getBellSyncDiff() != null) {
if (app.config.getTimetable().getBellSyncMultiplier() < 0) {
// the bell is too fast, need to step further to go with it
// add some time
syncedNow = Time.sum(now, app.appConfig.bellSyncDiff);
syncedNow = Time.sum(now, app.config.getTimetable().getBellSyncDiff());
//Toast.makeText(c, "Bell sync diff is "+app.appConfig.bellSyncDiff.getStringHMS()+"\n\n Synced now is "+syncedNow.getStringHMS(), Toast.LENGTH_LONG).show();
}
if (app.appConfig.bellSyncMultiplier > 0) {
if (app.config.getTimetable().getBellSyncMultiplier() > 0) {
// the bell is delayed, need to roll the "now" time back
// subtract some time
syncedNow = Time.diff(now, app.appConfig.bellSyncDiff);
syncedNow = Time.diff(now, app.config.getTimetable().getBellSyncDiff());
}
}
@ -153,7 +153,7 @@ public class CounterActivity extends AppCompatActivity {
private short counterType = TIME_LEFT;
private long updateCounter(Time syncedNow) {
Time diff = Time.diff(counterTarget, syncedNow);
b.timeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.appConfig.countInSeconds, "\n") : HomeFragment.timeLeft(app, diff, app.appConfig.countInSeconds, "\n"));
b.timeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.config.getTimetable().getCountInSeconds(), "\n") : HomeFragment.timeLeft(app, diff, app.config.getTimetable().getCountInSeconds(), "\n"));
return updateInterval(app, diff);
}

View File

@ -5,6 +5,29 @@
package pl.szczodrzynski.edziennik.ui.modules.home
interface HomeCard {
companion object {
/**
* A card is visible on every profile.
* On a unified home fragment, shows
* summary data from every profile.
* Not every card may work like this.
*/
const val PROFILE_UNIFIED = -1
/**
* A card is visible on every profile, but does
* not show every profile, just the currently
* loaded one.
*/
const val PROFILE_ALL = 0
const val CARD_LUCKY_NUMBER = 1
const val CARD_TIMETABLE = 2
const val CARD_GRADES = 3
const val CARD_EVENTS = 4
}
val id: Int
fun bind(position: Int, holder: HomeCardAdapter.ViewHolder)
fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder)
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-12-2.
*/
package pl.szczodrzynski.edziennik.ui.modules.home
import java.io.Serializable
data class HomeCardModel(
val profileId: Int,
val cardId: Int
) : Serializable

View File

@ -15,7 +15,7 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.startCoroutineTimer
import kotlin.coroutines.CoroutineContext
class HomeDummyCard(val id: Int) : HomeCard, CoroutineScope {
class HomeDummyCard(override val id: Int) : HomeCard, CoroutineScope {
companion object {
private const val TAG = "HomeDummyCard"
}

View File

@ -310,7 +310,7 @@ public class HomeFragment extends Fragment {
public static long updateInterval(App app, Time diff) {
//Log.d(TAG, "Millis is "+System.currentTimeMillis() % 1000+", update in "+(1000-(System.currentTimeMillis() % 1000)));
if (app.appConfig.countInSeconds) {
if (app.config.getTimetable().getCountInSeconds()) {
return 1000-(System.currentTimeMillis() % 1000);
}
if (diff.minute > 10) {
@ -587,7 +587,7 @@ public class HomeFragment extends Fragment {
private void configCardGrades(Context c, LayoutInflater layoutInflater, Activity a, ViewGroup insertPoint) {
View root = layoutInflater.inflate(R.layout.card_grades, null);
DisplayMetrics displayMetrics = c.getResources().getDisplayMetrics();
updateCardGrades(c, a, root, displayMetrics.widthPixels - Utils.dpToPx((app.appConfig.miniDrawerVisible ? 72 : 0)/*miniDrawer size*/ + 24 + 24/*left and right offsets*/ + 16/*ellipsize width*/));
updateCardGrades(c, a, root, displayMetrics.widthPixels - Utils.dpToPx((app.config.getUi().getMiniMenuVisible() ? 72 : 0)/*miniDrawer size*/ + 24 + 24/*left and right offsets*/ + 16/*ellipsize width*/));
insertPoint.addView(root, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}

View File

@ -28,6 +28,7 @@ import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentHomeV2Binding
import pl.szczodrzynski.edziennik.ui.dialogs.home.StudentNumberDialog
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeGradesCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeLuckyNumberCard
import pl.szczodrzynski.edziennik.ui.modules.home.cards.HomeTimetableCard
import pl.szczodrzynski.edziennik.utils.Themes
@ -40,6 +41,12 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
private const val TAG = "HomeFragment"
fun swapCards(fromPosition: Int, toPosition: Int, cardAdapter: HomeCardAdapter) {
val homeCards = App.getConfig().ui.homeCards.toMutableList()
val fromPair = homeCards[fromPosition]
homeCards[fromPosition] = homeCards[toPosition]
homeCards[toPosition] = fromPair
App.getConfig().ui.homeCards = homeCards
val fromCard = cardAdapter.items[fromPosition]
cardAdapter.items[fromPosition] = cardAdapter.items[toPosition]
cardAdapter.items[toPosition] = fromCard
@ -78,7 +85,7 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
.withOnClickListener(OnClickListener {
activity.bottomSheet.close()
StudentNumberDialog(activity, app.profile) {
app.profileSaveAsync()
app.profileSave()
}
}),
BottomSheetSeparatorItem(true),
@ -92,10 +99,28 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
})
)
val items = mutableListOf<HomeCard>(
HomeLuckyNumberCard(0, app, activity, this, app.profile),
HomeTimetableCard(1, app, activity, this, app.profile)
)
val showUnified = false
val cards = app.config.ui.homeCards.filter { it.profileId == app.profile.id }.toMutableList()
if (cards.isEmpty()) {
cards += listOf(
HomeCardModel(app.profile.id, HomeCard.CARD_LUCKY_NUMBER),
HomeCardModel(app.profile.id, HomeCard.CARD_TIMETABLE),
/*HomeCardModel(app.profile.id, HomeCard.CARD_EVENTS),*/
HomeCardModel(app.profile.id, HomeCard.CARD_GRADES)
)
app.config.ui.homeCards = app.config.ui.homeCards.toMutableList().also { it.addAll(cards) }
}
val items = mutableListOf<HomeCard>()
cards.mapNotNullTo(items) {
when (it.cardId) {
HomeCard.CARD_LUCKY_NUMBER -> HomeLuckyNumberCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_TIMETABLE -> HomeTimetableCard(it.cardId, app, activity, this, app.profile)
HomeCard.CARD_GRADES -> HomeGradesCard(it.cardId, app, activity, this, app.profile)
else -> null
}
}
val adapter = HomeCardAdapter(items)
val itemTouchHelper = ItemTouchHelper(CardItemTouchHelperCallback(adapter, b.refreshLayout))
@ -138,4 +163,4 @@ class HomeFragmentV2 : Fragment(), CoroutineScope {
})
itemTouchHelper.attachToRecyclerView(b.list)
}
}
}

View File

@ -74,8 +74,8 @@ class HomeTimetableCard(
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_howto, bellSyncTime!!.stringHM).toString() +
when {
app.appConfig.bellSyncDiff != null -> app.getString(R.string.bell_sync_current_dialog,
(if (app.appConfig.bellSyncMultiplier == -1) "-" else "+") + app.appConfig.bellSyncDiff.stringHMS)
app.config.timetable.bellSyncDiff != null -> app.getString(R.string.bell_sync_current_dialog,
(if (app.config.timetable.bellSyncMultiplier == -1) "-" else "+") + app.config.timetable.bellSyncDiff?.stringHMS)
else -> ""
})
.positiveText(R.string.ok)
@ -83,9 +83,8 @@ class HomeTimetableCard(
.neutralText(R.string.reset)
.onPositive { _, _: DialogAction? ->
val bellDiff = Time.diff(Time.getNow(), bellSyncTime)
app.appConfig.bellSyncDiff = bellDiff
app.appConfig.bellSyncMultiplier = if (bellSyncTime!!.value > Time.getNow().value) -1 else 1
app.saveConfig("bellSyncDiff", "bellSyncMultiplier")
app.config.timetable.bellSyncDiff = bellDiff
app.config.timetable.bellSyncMultiplier = if (bellSyncTime!!.value > Time.getNow().value) -1 else 1
MaterialDialog.Builder(activity)
.title(R.string.bell_sync_title)
@ -100,8 +99,8 @@ class HomeTimetableCard(
.positiveText(R.string.yes)
.negativeText(R.string.no)
.onPositive { _, _ ->
app.appConfig.bellSyncDiff = null
app.appConfig.bellSyncMultiplier = 0
app.config.timetable.bellSyncDiff = null
app.config.timetable.bellSyncMultiplier = 0
app.saveConfig("bellSyncDiff", "bellSyncMultiplier")
}
.show()
@ -129,10 +128,10 @@ class HomeTimetableCard(
val now = Time.getNow()
val syncedNow: Time = when (app.appConfig.bellSyncDiff != null) {
val syncedNow: Time = when (app.config.timetable.bellSyncDiff != null) {
true -> when {
app.appConfig.bellSyncMultiplier < 0 -> Time.sum(now, app.appConfig.bellSyncDiff)
app.appConfig.bellSyncMultiplier > 0 -> Time.diff(now, app.appConfig.bellSyncDiff)
app.config.timetable.bellSyncMultiplier < 0 -> Time.sum(now, app.config.timetable.bellSyncDiff)
app.config.timetable.bellSyncMultiplier > 0 -> Time.diff(now, app.config.timetable.bellSyncDiff)
else -> now
}
else -> now
@ -148,8 +147,8 @@ class HomeTimetableCard(
private fun updateCounter(syncedNow: Time): Long {
val diff = Time.diff(counterTarget, syncedNow)
b.cardTimetableTimeLeft.text = when (counterType) {
TIME_TILL -> HomeFragment.timeTill(app, diff, app.appConfig.countInSeconds)
else -> HomeFragment.timeLeft(app, diff, app.appConfig.countInSeconds)
TIME_TILL -> HomeFragment.timeTill(app, diff, app.config.timetable.countInSeconds)
else -> HomeFragment.timeLeft(app, diff, app.config.timetable.countInSeconds)
}
bellSyncTime = counterTarget.clone()
b.cardTimetableFullscreenCounter.visibility = View.VISIBLE

View File

@ -77,17 +77,16 @@ public class HomeTimetableCardOld {
new MaterialDialog.Builder(a)
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_howto, bellSyncTime.getStringHM())+
(app.appConfig.bellSyncDiff != null ?
""+app.getString(R.string.bell_sync_current_dialog, (app.appConfig.bellSyncMultiplier == -1 ? "-" : "+")+app.appConfig.bellSyncDiff.getStringHMS())
(app.config.getTimetable().getBellSyncDiff() != null ?
""+app.getString(R.string.bell_sync_current_dialog, (app.config.getTimetable().getBellSyncMultiplier() == -1 ? "-" : "+")+app.config.getTimetable().getBellSyncDiff().getStringHMS())
: ""))
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.neutralText(R.string.reset)
.onPositive((dialog, which) -> {
Time bellDiff = Time.diff(Time.getNow(), bellSyncTime);
app.appConfig.bellSyncDiff = bellDiff;
app.appConfig.bellSyncMultiplier = (bellSyncTime.getValue() > Time.getNow().getValue() ? -1 : 1);
app.saveConfig("bellSyncDiff", "bellSyncMultiplier");
app.config.getTimetable().setBellSyncDiff(bellDiff);
app.config.getTimetable().setBellSyncMultiplier(bellSyncTime.getValue() > Time.getNow().getValue() ? -1 : 1);
new MaterialDialog.Builder(a)
.title(R.string.bell_sync_title)
.content(app.getString(R.string.bell_sync_results, (bellSyncTime.getValue() > Time.getNow().getValue() ? "-" : "+"), bellDiff.getStringHMS()))
@ -101,9 +100,8 @@ public class HomeTimetableCardOld {
.positiveText(R.string.yes)
.negativeText(R.string.no)
.onPositive(((dialog1, which1) -> {
app.appConfig.bellSyncDiff = null;
app.appConfig.bellSyncMultiplier = 0;
app.saveConfig("bellSyncDiff", "bellSyncMultiplier");
app.config.getTimetable().setBellSyncDiff(null);
app.config.getTimetable().setBellSyncMultiplier(0);
}))
.show();
})
@ -125,17 +123,17 @@ public class HomeTimetableCardOld {
Time now = Time.getNow();
Time syncedNow = now;
//Time updateDiff = null;
if (app.appConfig.bellSyncDiff != null) {
if (app.appConfig.bellSyncMultiplier < 0) {
if (app.config.getTimetable().getBellSyncDiff() != null) {
if (app.config.getTimetable().getBellSyncMultiplier() < 0) {
// the bell is too fast, need to step further to go with it
// add some time
syncedNow = Time.sum(now, app.appConfig.bellSyncDiff);
syncedNow = Time.sum(now, app.config.getTimetable().getBellSyncDiff());
//Toast.makeText(c, "Bell sync diff is "+app.appConfig.bellSyncDiff.getStringHMS()+"\n\n Synced now is "+syncedNow.getStringHMS(), Toast.LENGTH_LONG).show();
}
if (app.appConfig.bellSyncMultiplier > 0) {
if (app.config.getTimetable().getBellSyncMultiplier() > 0) {
// the bell is delayed, need to roll the "now" time back
// subtract some time
syncedNow = Time.diff(now, app.appConfig.bellSyncDiff);
syncedNow = Time.diff(now, app.config.getTimetable().getBellSyncDiff());
}
}
@ -230,7 +228,7 @@ public class HomeTimetableCardOld {
private short counterType = TIME_TILL;
private long updateCounter(Time syncedNow) {
Time diff = Time.diff(counterTarget, syncedNow);
b.cardTimetableTimeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.appConfig.countInSeconds) : HomeFragment.timeLeft(app, diff, app.appConfig.countInSeconds));
b.cardTimetableTimeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.config.getTimetable().getCountInSeconds()) : HomeFragment.timeLeft(app, diff, app.config.getTimetable().getCountInSeconds()));
bellSyncTime = counterTarget;
b.cardTimetableFullscreenCounter.setVisibility(View.VISIBLE);
return updateInterval(app, diff);

View File

@ -0,0 +1,191 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-29
*/
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.os.Build
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.LinearLayout.HORIZONTAL
import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
import android.widget.TextView
import androidx.core.graphics.ColorUtils
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.lifecycle.Observer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade.*
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeFull
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.databinding.CardHomeGradesBinding
import pl.szczodrzynski.edziennik.dp
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCard
import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.utils.Colors
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.ItemGradesSubjectModel
import kotlin.coroutines.CoroutineContext
class HomeGradesCard(
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
val profile: Profile
) : HomeCard, CoroutineScope {
private var job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var b: CardHomeGradesBinding
private val grades = mutableListOf<GradeFull>()
private val subjects = mutableListOf<ItemGradesSubjectModel>()
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
holder.root.removeAllViews()
b = CardHomeGradesBinding.inflate(LayoutInflater.from(holder.root.context))
b.root.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
setMargins(8.dp)
}
holder.root += b.root
val sevenDaysAgo = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000
app.db.gradeDao().getAllFromDate(profile.id, profile.currentSemester, sevenDaysAgo).observe(fragment, Observer {
grades.apply {
clear()
addAll(it)
}
update()
})
b.root.setOnClickListener {
activity.loadTarget(MainActivity.DRAWER_ITEM_GRADES)
}
}
private fun update() {
subjects.clear()
grades.forEach { grade ->
val model = ItemGradesSubjectModel.searchModelBySubjectId(subjects, grade.subjectId)
?: run {
subjects.add(ItemGradesSubjectModel(
profile,
Subject(profile.id, grade.subjectId, grade.subjectLongName, grade.subjectShortName),
mutableListOf(),
mutableListOf()
))
ItemGradesSubjectModel.searchModelBySubjectId(subjects, grade.subjectId)
}
model?.grades1?.add(grade)
}
b.gradeList.removeAllViews()
val textLayoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(0, 0, 5.dp, 0)
}
val linearLayoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
setMargins(2.dp, 0, 2.dp, 5.dp)
}
subjects.forEach { subject ->
val gradeItem = LinearLayout(b.gradeList.context)
gradeItem.orientation = HORIZONTAL
var totalWidth = 0
val maxWidth = (app.resources.displayMetrics.widthPixels -
Utils.dpToPx((if (app.config.ui.miniMenuVisible) 72 else 0) /*miniDrawer size*/ +
24 + 24 /*left and right offsets*/ +
16 /*ellipsize width*/)) / 1.5f
subject.grades1.onEach { grade ->
val gradeColor = when (app.profile.gradeColorMode) {
Profile.COLOR_MODE_DEFAULT -> grade.color
else -> Colors.gradeToColor(grade)
}
val gradeName = TextView(gradeItem.context).apply {
text = when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format, grade.name)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format, grade.name)
TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format, grade.name)
TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format, grade.name)
else -> grade.name
}
setTextColor(when (ColorUtils.calculateLuminance(gradeColor) > 0.25) {
true -> 0xff000000
else -> 0xffffffff
}.toInt())
setTypeface(null, Typeface.BOLD)
setBackgroundResource(R.drawable.bg_rounded_4dp)
background.colorFilter = PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)
setPadding(5.dp, 0, 5.dp, 0)
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
totalWidth += gradeName.measuredWidth + 5.dp
if (totalWidth >= maxWidth) {
val ellipsisText = TextView(gradeItem.context).apply {
text = app.getString(R.string.ellipsis)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
setTextAppearance(context, R.style.NavView_TextView)
} else {
setTextAppearance(R.style.NavView_TextView)
}
setTypeface(null, Typeface.BOLD)
setPadding(0, 0, 0, 0)
}
gradeItem.addView(ellipsisText, textLayoutParams)
return@forEach
} else {
gradeItem.addView(gradeName, textLayoutParams)
}
}
val subjectName = TextView(gradeItem.context).apply {
text = app.getString(R.string.grade_subject_format, subject.subject?.longName
?: "")
ellipsize = TextUtils.TruncateAt.END
setSingleLine()
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
gradeItem.addView(subjectName, textLayoutParams)
b.gradeList.addView(gradeItem, linearLayoutParams)
}
b.noData.visibility = if (subjects.isEmpty()) VISIBLE else GONE
b.gradeList.visibility = if (subjects.isEmpty()) GONE else VISIBLE
}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}

View File

@ -26,7 +26,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class HomeLuckyNumberCard(
val id: Int,
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,

View File

@ -4,12 +4,17 @@
package pl.szczodrzynski.edziennik.ui.modules.home.cards
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.plusAssign
import androidx.core.view.setMargins
import androidx.lifecycle.Observer
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
@ -22,10 +27,12 @@ import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardAdapter
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragmentV2
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import pl.szczodrzynski.navlib.colorAttr
import kotlin.coroutines.CoroutineContext
class HomeTimetableCard(
val id: Int,
override val id: Int,
val app: App,
val activity: MainActivity,
val fragment: HomeFragmentV2,
@ -42,12 +49,24 @@ class HomeTimetableCard(
private lateinit var b: CardHomeTimetableBinding
private val today = Date.getToday()
private var timetableDate: Date = Date.getToday()
private val searchEnd = today.clone().stepForward(0, 0, 7)
private var allLessons = listOf<LessonFull>()
private var lessons = listOf<LessonFull>()
private var events = listOf<Event>()
private var bellSyncDiffMillis = 0L
private val syncedNow: Time
get() = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
private var counterJob: Job? = null
private var counterStart: Time? = null
private var counterEnd: Time? = null
private var subjectSpannable: CharSequence? = null
private val ignoreCancelled = true
override fun bind(position: Int, holder: HomeCardAdapter.ViewHolder) {
holder.root.removeAllViews()
b = CardHomeTimetableBinding.inflate(LayoutInflater.from(holder.root.context))
@ -56,45 +75,209 @@ class HomeTimetableCard(
}
holder.root += b.root
b.settings.setImageDrawable(IconicsDrawable(activity, CommunityMaterial.Icon2.cmd_settings_outline)
.colorAttr(activity, R.attr.colorIcon)
.sizeDp(20))
// get current bell-sync params
app.config.timetable.bellSyncDiff?.let {
bellSyncDiffMillis = (it.hour * 60 * 60 * 1000 + it.minute * 60 * 1000 + it.second * 1000).toLong()
bellSyncDiffMillis *= app.config.timetable.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
// get all lessons within the search bounds
app.db.timetableDao().getBetweenDates(today, searchEnd).observe(fragment, Observer {
allLessons = it
update()
})
b.root.setOnClickListener {
activity.loadTarget(MainActivity.DRAWER_ITEM_TIMETABLE, Bundle().apply {
putString("timetableDate", timetableDate.stringY_m_d)
})
}
}
private fun update() { launch {
val deferred = async(Dispatchers.Default) {
// get current bell-sync params
var bellSyncDiffMillis: Long = 0
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
// get the current bell-synced time
val now = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
val now = syncedNow
// search for lessons to display
val timetableDate = Date.getToday()
var checkedDays = 0
lessons = allLessons.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.displayEndTime > now && it.type != Lesson.TYPE_NO_LESSONS }
lessons = allLessons.filter {
it.profileId == profile.id
&& it.displayDate == timetableDate
&& it.displayEndTime > now
&& it.type != Lesson.TYPE_NO_LESSONS
&& !(it.isCancelled && ignoreCancelled)
}
while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = allLessons.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
lessons = allLessons.filter {
it.profileId == profile.id
&& it.displayDate == timetableDate
&& it.type != Lesson.TYPE_NO_LESSONS
&& !(it.isCancelled && ignoreCancelled)
}
checkedDays++
}
timetableDate
}
deferred.await()
val text = StringBuilder()
for (lesson in lessons) {
text += lesson.displayStartTime?.stringHM+" "+lesson.displaySubjectName+"\n"
timetableDate = deferred.await()
val isToday = today == timetableDate
b.progress.visibility = View.GONE
b.counter.visibility = View.GONE
val now = syncedNow
val firstLesson = lessons.firstOrNull()
val lastLesson = lessons.lastOrNull()
if (isToday) {
// today
b.dayInfo.setText(R.string.home_timetable_today)
b.lessonInfo.setText(
R.string.home_timetable_lessons_remaining,
lessons.size,
lastLesson?.displayEndTime?.stringHM ?: "?"
)
counterStart = firstLesson?.displayStartTime
counterEnd = firstLesson?.displayEndTime
val isOngoing = counterStart <= now && now <= counterEnd
val lessonRes = if (isOngoing)
R.string.home_timetable_lesson_ongoing
else
R.string.home_timetable_lesson_not_started
b.lessonBig.setText(lessonRes, firstLesson.subjectSpannable)
firstLesson?.displayClassroom?.let {
b.classroom.visibility = View.VISIBLE
b.classroom.text = it
} ?: run {
b.classroom.visibility = View.GONE
}
subjectSpannable = firstLesson.subjectSpannable
counterJob = startCoroutineTimer(repeatMillis = 1000) {
count()
}
}
b.text.text = text.toString()
else {
val isTomorrow = today.clone().stepForward(0, 0, 1) == timetableDate
val dayInfoRes = if (isTomorrow) {
// tomorrow
R.string.home_timetable_tomorrow
}
else {
val todayWeekStart = today.weekStart
val dateWeekStart = timetableDate.weekStart
if (todayWeekStart == dateWeekStart) {
// this week
R.string.home_timetable_date_this_week
}
else {
// future: not this week
R.string.home_timetable_date_future
}
}
b.dayInfo.setText(dayInfoRes, Week.getFullDayName(timetableDate.weekDay), timetableDate.formattedString)
b.lessonInfo.setText(
R.string.home_timetable_lessons_info,
lessons.size,
firstLesson?.displayStartTime?.stringHM ?: "?",
lastLesson?.displayEndTime?.stringHM ?: "?"
)
b.lessonBig.setText(R.string.home_timetable_lesson_first, firstLesson.subjectSpannable)
firstLesson?.displayClassroom?.let {
b.classroom.visibility = View.VISIBLE
b.classroom.text = it
} ?: run {
b.classroom.visibility = View.GONE
}
}
val text = mutableListOf<CharSequence>(
activity.getString(R.string.home_timetable_later)
)
var first = true
for (lesson in lessons) {
if (first) { first = false; continue }
text += listOf(
lesson.displayStartTime?.stringHM,
lesson.subjectSpannable
).concat(" ")
}
if (text.size == 1)
text += activity.getString(R.string.home_timetable_later_no_lessons)
b.nextLessons.text = text.concat("\n")
}}
private val LessonFull?.subjectSpannable: CharSequence
get() = if (this == null) "?" else when {
isCancelled -> displaySubjectName?.asStrikethroughSpannable() ?: "?"
isChange -> displaySubjectName?.asItalicSpannable() ?: "?"
else -> displaySubjectName ?: "?"
}
private fun count() {
val counterStart = counterStart
val counterEnd = counterEnd
if (counterStart == null || counterEnd == null) {
// there is no lesson to count
b.progress.visibility = View.GONE
b.counter.visibility = View.GONE
this.counterJob?.cancel()
return
}
val now = syncedNow
if (now > counterEnd) {
// the lesson is already over
b.progress.visibility = View.GONE
b.counter.visibility = View.GONE
this.counterJob?.cancel()
this.counterStart = null
this.counterEnd = null
update() // check for new lessons to display
return
}
val isOngoing = counterStart <= now && now <= counterEnd
val lessonRes = if (isOngoing)
R.string.home_timetable_lesson_ongoing
else
R.string.home_timetable_lesson_not_started
b.lessonBig.setText(lessonRes, subjectSpannable ?: "")
if (now < counterStart) {
// the lesson hasn't yet started
b.progress.visibility = View.GONE
b.counter.visibility = View.VISIBLE
val diff = counterStart - now
b.counter.text = activity.timeTill(diff.toInt(), "\n")
}
else {
// the lesson is right now
b.progress.visibility = View.VISIBLE
b.counter.visibility = View.VISIBLE
val lessonLength = counterEnd - counterStart
val timePassed = now - counterStart
val timeLeft = counterEnd - now
b.counter.text = activity.timeLeft(timeLeft.toInt(), "\n")
b.progress.max = lessonLength.toInt()
b.progress.progress = timePassed.toInt()
}
}
override fun unbind(position: Int, holder: HomeCardAdapter.ViewHolder) = Unit
}
}

View File

@ -4,21 +4,23 @@ import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog;
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualV2Dialog;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.utils.models.Date;
@ -92,7 +94,16 @@ public class HomeworkAdapter extends RecyclerView.Adapter<HomeworkAdapter.ViewHo
holder.homeworkItemEdit.setVisibility((homework.addedManually ? View.VISIBLE : View.GONE));
holder.homeworkItemEdit.setOnClickListener(v -> {
new EventManualDialog(context).show(app, homework, null, null, EventManualDialog.DIALOG_HOMEWORK);
new EventManualV2Dialog(
(MainActivity) context,
homework.profileId,
null,
null,
null,
null,
homework,
null,
null);
});
if (homework.sharedBy == null) {

View File

@ -13,9 +13,10 @@ import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualV2Dialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@ -56,7 +57,7 @@ class HomeworkFragment : Fragment() {
.withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline)
.withOnClickListener(View.OnClickListener {
activity.bottomSheet.close()
EventManualDialog(activity).show(app, null, null, null, EventManualDialog.DIALOG_HOMEWORK)
EventManualV2Dialog(activity, App.profileId, defaultType = Event.TYPE_HOMEWORK)
}),
BottomSheetSeparatorItem(true),
BottomSheetPrimaryItem(true)
@ -102,7 +103,7 @@ class HomeworkFragment : Fragment() {
activity.navView.bottomBar.fabExtendedText = getString(R.string.add)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon2.cmd_plus
activity.navView.setFabOnClickListener(View.OnClickListener {
EventManualDialog(activity).show(app, null, null, null, EventManualDialog.DIALOG_HOMEWORK)
EventManualV2Dialog(activity, App.profileId)
})
activity.gainAttention()

View File

@ -78,7 +78,6 @@ public class ChangelogIntroActivity extends IntroActivity {
.build());
}*/
app.appConfig.lastAppVersion = BuildConfig.VERSION_CODE;
app.appConfig.savePending = true;
app.config.setAppVersion(BuildConfig.VERSION_CODE);
}
}

View File

@ -91,9 +91,8 @@ public class LoginActivity extends AppCompatActivity {
app = (App) getApplication();
if (!app.appConfig.loginFinished) {
app.appConfig.miniDrawerVisible = getResources().getConfiguration().smallestScreenWidthDp > 480;
app.saveConfig("miniDrawerVisible");
if (!app.config.getLoginFinished()) {
app.config.getUi().setMiniMenuVisible(getResources().getConfiguration().smallestScreenWidthDp > 480);
}
/*b.getRoot().addOnLayoutChangeListener(((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {

View File

@ -59,7 +59,7 @@ public class LoginChooserFragment extends Fragment {
b.cancelButton.setVisibility(View.VISIBLE);
b.cancelButton.setOnClickListener((v -> nav.navigateUp()));
}
else if (app.appConfig.loginFinished) {
else if (app.config.getLoginFinished()) {
// we are navigated here from AppDrawer
b.cancelButton.setVisibility(View.VISIBLE);
b.cancelButton.setOnClickListener((v -> {

View File

@ -19,8 +19,8 @@ import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_SCHOOL_NAME;
import static pl.szczodrzynski.edziennik.api.v2.models.AppError.CODE_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.api.v2.models.AppError.CODE_INVALID_SCHOOL_NAME;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_IUCZNIOWIE;
public class LoginIuczniowieFragment extends Fragment {

Some files were not shown because too many files have changed in this diff Show More