Compare commits

..

35 Commits

Author SHA1 Message Date
d8a8bed68d [4.0-beta.5] Update build.gradle and signing. 2020-01-19 22:42:29 +01:00
eedbd954bd [Updates] Add toast for error while checking and for no updates. 2020-01-19 22:35:12 +01:00
0eb8366027 [Changelog] Fix changelog dialog appearance on Android N+. 2020-01-19 22:29:34 +01:00
894135104b [4.0-beta.4] Update build.gradle, signing and changelog. 2020-01-19 22:18:20 +01:00
7b2e408efc [Sync] Make sync not possible for archived profiles. Translate some error codes. 2020-01-19 22:17:57 +01:00
e4115c122e [Errors] Add reporting app version name along with the error. 2020-01-19 21:57:17 +01:00
537b16949e [Updates] Fix running update worker in Java. 2020-01-19 21:52:08 +01:00
ca60ceb2a7 [Firebase] Implement handling app updates. 2020-01-19 21:49:58 +01:00
0fad12fea5 [Updates] Change update channel to beta. 2020-01-19 21:39:28 +01:00
6cd2c23aac [Firebase] Implement handling server messages. 2020-01-19 21:31:37 +01:00
512baaa43f [Errors] Include parser error body when reporting HTTP errors. 2020-01-19 20:34:10 +01:00
d097fcc973 [API/Librus] Fix classrooms name short extraction when name contains two spaces. 2020-01-19 20:22:41 +01:00
621dbd459c [API/Vulcan] Fix marking messages as seen. 2020-01-19 20:03:25 +01:00
840ab4b0c4 [Models] Remove unused models and classes. 2020-01-19 19:30:38 +01:00
904be34a87 [Notifications] Fix showing an empty notification where the list is empty. 2020-01-19 19:19:30 +01:00
b7fc6fcc38 [Structure] Refactor App class to Kotlin. Rewrite SzkolnyTask and posting notifications. Remove dependency on AppConfig. Update libraries and gradle. 2020-01-19 19:07:27 +01:00
55c6e40d6d [DB] Convert AppDb to Kotlin. 2020-01-19 18:44:57 +01:00
4dfb015057 [Firebase/Librus] Implement basic push integration. 2020-01-19 01:17:33 +01:00
e40a0ba2bb [Strings] Add missing translations. 2020-01-18 00:44:13 +01:00
fd48f10df9 [Dialog/EventDetails] Show toast when calendar app not found instead of crashing. 2020-01-18 00:26:26 +01:00
6a54e7fef7 [Firebase] Implement Mobidziennik push service. 2020-01-16 09:27:30 +01:00
5c4d6ed140 [API/Edudziennik] Fix getting attendances. 2020-01-15 23:19:33 +01:00
9ed1be3594 [UI/DrawerProfiles] Add button for marking everything as read in every profile. 2020-01-15 22:58:22 +01:00
c5ce582678 [API/Edudziennik] Fix fix for semesters and getting grades on first login. 2020-01-15 22:26:38 +01:00
2050083bce [API/Edudziennik] Fix semesters and getting grades on first login. 2020-01-15 21:19:48 +01:00
92e6bdb562 [API/iDziennik] Fix regex getting school year in first login. 2020-01-13 22:42:30 +01:00
93e70c38b7 [Firebase] Implement base for per-register FCM tasks. 2020-01-12 21:39:06 +01:00
45b96179a5 [DB] Fix timetable migration crashing app. 2020-01-12 21:33:46 +01:00
a29a534a40 [API/Edudziennik] Fix showing notifications for presence attendances. 2020-01-12 19:45:20 +01:00
8e2297359c [Firebase] Implement new custom FCM service. 2020-01-11 19:07:25 +01:00
92ba7248ef [UI] Fix vector drawables crashing on API < 21. 2020-01-11 13:56:09 +01:00
f657d37cbd [DB/Timetable] Fix migration fixing wrong primary key columns. 2020-01-10 22:23:14 +01:00
9e312f60bf [APIService] Fix showing notification with no service running. 2020-01-10 21:34:55 +01:00
85f72b78f7 [DB/Timetable] Fix wrong primary key columns. 2020-01-10 21:33:59 +01:00
40acb67ceb [Errors] Fix Timeout error detection (SocketTimeoutException inherits from InterruptedIOException). 2020-01-10 16:44:54 +01:00
188 changed files with 4522 additions and 4955 deletions

View File

@ -2,7 +2,7 @@ apply plugin: 'com.android.library'
//apply plugin: 'me.tatarka.retrolambda'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdkVersion setup.compileSdk
android {
lintOptions {
@ -12,7 +12,7 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion rootProject.ext.targetSdkVersion
targetSdkVersion setup.targetSdk
versionCode 1
versionName "1.0"
}
@ -43,9 +43,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Google libraries
implementation "androidx.appcompat:appcompat:${androidXAppCompat}"
implementation "androidx.recyclerview:recyclerview:${androidXRecyclerView}"
implementation "com.google.android.material:material:${googleMaterial}"
implementation "androidx.appcompat:appcompat:${versions.appcompat}"
implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}"
implementation "com.google.android.material:material:${versions.material}"
// other libraries
//implementation 'se.emilsjolander:stickylistheaders:2.7.0'

View File

@ -1,13 +1,14 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
android {
signingConfigs {
}
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdkVersion setup.compileSdk
defaultConfig {
applicationId 'pl.szczodrzynski.edziennik'
minSdkVersion setup.minSdk
@ -103,7 +104,7 @@ tasks.whenTaskAdded { task ->
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
annotationProcessor "androidx.room:room-compiler:${versions.room}"
kapt "androidx.room:room-compiler:${versions.room}"
debugImplementation "com.amitshekhar.android:debug-db:1.0.5"
implementation "android.arch.navigation:navigation-fragment-ktx:${versions.navigationFragment}"

View File

@ -28,7 +28,7 @@
-keepclassmembers class pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig { public *; }
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider
-keepnames class pl.szczodrzynski.edziennik.widgets.luckynumber.WidgetLuckyNumber
-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider
-keep class .R
-keep class **.R$* {

View File

@ -80,6 +80,7 @@
<service android:name=".ui.widgets.timetable.WidgetTimetableService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity android:name=".ui.widgets.LessonDialogActivity"
android:label=""
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
@ -141,12 +142,8 @@
android:label="@string/app_name"
android:theme="@style/AppTheme" />
<activity android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme" />
<activity android:name=".ui.modules.webpush.WebPushConfigActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@style/AppTheme.Dark" />
<activity android:name=".ui.modules.webpush.QrScannerActivity" />
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:configChanges="orientation|keyboardHidden"
@ -165,18 +162,9 @@
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.BootReceiver">
<receiver android:name=".sync.UpdateDownloaderService$DownloadProgressReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
<receiver android:name=".sync.FirebaseBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</receiver>
<receiver android:name=".receivers.SzkolnyReceiver"
@ -193,16 +181,22 @@
____) | __/ | \ V /| | (_| __/\__ \
|_____/ \___|_| \_/ |_|\___\___||___/
-->
<service android:name=".sync.MyFirebaseMessagingService"
<!--<service android:name=".sync.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".receivers.BootReceiver$NotificationActionService" />
<service android:name=".Notifier$GetDataRetryService" />
</service>-->
<service android:name=".data.api.ApiService" />
<service android:name=".data.firebase.MyFirebaseService"
android:exported="false">
<intent-filter android:priority="10000000">
<action android:name="com.google.firebase.MESSAGING_EVENT" />
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service android:name=".sync.UpdateDownloaderService" />
<!--
_____ _ _

View File

@ -1,4 +1,4 @@
<h3>Wersja 4.0-beta.3, 2020-01-10</h3>
<h3>Wersja 4.0-beta.4, 2020-01-19</h3>
<ul>
<li><b>Przebudowaliśmy cały moduł synchronizacji</b>, co oznacza większą stabilność aplikacji, szybkosć oraz poprawność pobieranych danych.</li>
<li><b><u>Wysyłanie wiadomości</u></b> - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli &#x1F44F;</li>
@ -6,6 +6,7 @@
<li>Nowa <b>Strona główna</b> - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu</li>
<li>Nowy <b>Plan lekcji</b> - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie</li>
<li>Nowe okienka informacji o wydarzeniach oraz lekcjach</li>
<li>Nowe, przyjemniejsze powiadomienia</li>
<li>Łatwiejsze dodawanie własnych wydarzeń</li>
<li>Dużo poprawek w widoku <b>Wiadomości</b> oraz <b>Ogłoszeń</b></li>
<li>Częściowa <b>Obsługa dziennika EduDziennik</b></li>
@ -14,7 +15,9 @@
<li>Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)</li>
<li>Lepsze <b>przekazywanie powiadomień na komputer</b> oraz łatwiejsze parowanie</li>
<li>Poprawiliśmy synchronizację w tle na niektórych telefonach</li>
<li>Usunąłem denerwujący brak zaznaczenia w lewym menu</li>
<li>Znaczna ilość błędów z poprzednich wersji już nie występuje</li>
<li><strike>Występują natomiast nowe błędy, dlatego proszę o ich zgłaszanie :)</strike></li>
</ul>
<br>
<br>
@ -24,6 +27,7 @@ Staramy się usuwać takie przypadki, jednak na chwilę obecną mogą występowa
<ul>
<li>Wysyłanie wiadomości może czasami nie działać - proszę o zgłaszanie wszystkich błędów na naszym serwerze Discord</li>
<li>Terminarz - brak informacji o odwołanych lekcjach w dialogu</li>
<li>Cisza nocna w powiadomieniach jeszcze nie działa.</li>
</ul>
<br>
<br>

View File

@ -9,7 +9,7 @@
/*secret password - removed for source code publication*/
static toys AES_IV[16] = {
0x1e, 0xef, 0x4e, 0xc6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
0x90, 0xad, 0xe8, 0xea, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat);

View File

@ -1,714 +0,0 @@
package pl.szczodrzynski.edziennik;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.pm.Signature;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.work.Configuration;
import com.chuckerteam.chucker.api.ChuckerCollector;
import com.chuckerteam.chucker.api.ChuckerInterceptor;
import com.chuckerteam.chucker.api.RetentionManager;
import com.google.android.gms.security.ProviderInstaller;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.hypertrack.hyperlog.HyperLog;
import com.mikepenz.iconics.Iconics;
import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.IIcon;
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont;
import java.lang.reflect.Field;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import cat.ereza.customactivityoncrash.config.CaocConfig;
import im.wangchao.mhttp.MHttp;
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar;
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache;
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor;
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.api.szkolny.interceptor.Signing;
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.entity.DebugLog;
import pl.szczodrzynski.edziennik.data.db.entity.Profile;
import pl.szczodrzynski.edziennik.network.NetworkUtils;
import pl.szczodrzynski.edziennik.network.TLSSocketFactory;
import pl.szczodrzynski.edziennik.sync.SyncWorker;
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity;
import pl.szczodrzynski.edziennik.utils.DebugLogFormat;
import pl.szczodrzynski.edziennik.utils.PermissionChecker;
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.entity.LoginStore.LOGIN_TYPE_MOBIDZIENNIK;
public class App extends androidx.multidex.MultiDexApplication implements Configuration.Provider {
private static final String TAG = "App";
public static int profileId = -1;
private Context mContext;
@Override
public Configuration getWorkManagerConfiguration() {
return new Configuration.Builder()
.setMinimumLoggingLevel(Log.VERBOSE)
.build();
}
public static final int REQUEST_TIMEOUT = 10 * 1000;
// notifications
//public NotificationManager mNotificationManager;
//public final String NOTIFICATION_CHANNEL_ID_UPDATES = "4566";
//public String NOTIFICATION_CHANNEL_NAME_UPDATES;
public Notifier notifier;
public static final String APP_URL = "://edziennik.szczodrzynski.pl/app/";
public ShortcutManager shortcutManager;
public PermissionChecker permissionChecker;
public String signature = "";
public String deviceId = "";
public AppDb db;
public void debugLog(String text) {
if (!devMode)
return;
db.debugLogDao().add(new DebugLog(Utils.getCurrentTimeUsingCalendar()+": "+text));
}
public void debugLogAsync(String text) {
if (!devMode)
return;
AsyncTask.execute(() -> {
db.debugLogDao().add(new DebugLog(Utils.getCurrentTimeUsingCalendar()+": "+text));
});
}
// network & APIs
public NetworkUtils networkUtils;
public PersistentCookieJar cookieJar;
public OkHttpClient http;
public OkHttpClient httpLazy;
public SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE
public AppConfig appConfig; // APPCONFIG: common for all profiles
//public AppProfile profile; // current profile
public SharedPreferences registerStore; // sharedPreferences for REGISTER
//public Register register; // REGISTER for current profile, read from registerStore
public Profile profile;
public Config config;
private static Config mConfig;
public static Config getConfig() {
return mConfig;
}
// other stuff
public Gson gson;
public String requestScheme = "https";
public boolean unreadBadgesAvailable = true;
public static boolean devMode = false;
public static final boolean UPDATES_ON_PLAY_STORE = true;
@RequiresApi(api = Build.VERSION_CODES.M)
public Icon getDesktopIconFromIconics(IIcon icon) {
final IconicsDrawable drawable = new IconicsDrawable(mContext, icon)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(48))
.padding(IconicsSize.dp(8))
.backgroundColor(IconicsColor.colorRes(R.color.colorPrimaryDark))
.roundedCorners(IconicsSize.dp(10));
//drawable.setStyle(Paint.Style.FILL);
final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return Icon.createWithBitmap(bitmap);
}
@Override
public void onCreate() {
super.onCreate();
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
CaocConfig.Builder.create()
.backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM) //default: CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM
.enabled(true) //default: true
.showErrorDetails(true) //default: true
.showRestartButton(true) //default: true
.logErrorOnRestart(true) //default: true
.trackActivities(true) //default: false
.minTimeBetweenCrashesMs(2000) //default: 3000
.errorDrawable(R.drawable.ic_rip) //default: bug image
.restartActivity(MainActivity.class) //default: null (your app's launch activity)
.errorActivity(CrashActivity.class) //default: null (default error activity)
//.eventListener(new YourCustomEventListener()) //default: null
.apply();
mContext = this;
db = AppDb.getDatabase(this);
gson = new Gson();
networkUtils = new NetworkUtils(this);
config = new Config(db);
config.migrate(this);
mConfig = config;
Iconics.init(getApplicationContext());
Iconics.registerFont(SzkolnyFont.INSTANCE);
notifier = new Notifier(this);
permissionChecker = new PermissionChecker(mContext);
deviceId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(this));
appSharedPrefs = getSharedPreferences(getString(R.string.preference_file_global), Context.MODE_PRIVATE);
loadConfig();
Signing.INSTANCE.getCert(this);
Themes.INSTANCE.setThemeInt(config.getUi().getTheme());
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature: packageInfo.signatures) {
byte[] signatureBytes = signature.toByteArray();
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signatureBytes);
this.signature = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
//Log.d(TAG, "Signature is "+this.signature);
}
}
catch (Exception e) {
e.printStackTrace();
}
if ("f054761fbdb6a238".equals(deviceId) || BuildConfig.DEBUG) {
devMode = true;
}
else if (config.getDevModePassword() != null) {
checkDevModePassword();
}
OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder()
.cache(null)
.followRedirects(true)
.followSslRedirects(true)
.retryOnConnectionFailure(true)
.cookieJar(cookieJar)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(40, TimeUnit.SECONDS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
try {
try {
ProviderInstaller.installIfNeeded(this);
} catch (Exception e) {
Log.e("OkHttpTLSCompat", "Play Services not found or outdated");
X509TrustManager x509TrustManager = null;
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
for (TrustManager trustManager: trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager)
x509TrustManager = (X509TrustManager) trustManager;
}
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, null, null);
httpBuilder.sslSocketFactory(new TLSSocketFactory(sc.getSocketFactory()), x509TrustManager);
ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_0)
.tlsVersions(TlsVersion.TLS_1_1)
.tlsVersions(TlsVersion.TLS_1_2)
.build();
List<ConnectionSpec> specs = new ArrayList<>();
specs.add(cs);
specs.add(ConnectionSpec.COMPATIBLE_TLS);
specs.add(ConnectionSpec.CLEARTEXT);
httpBuilder.connectionSpecs(specs);
}
} catch (Exception exc) {
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc);
}
}
if (App.devMode || BuildConfig.DEBUG) {
HyperLog.initialize(this);
HyperLog.setLogLevel(Log.VERBOSE);
HyperLog.setLogFormat(new DebugLogFormat(this));
ChuckerCollector chuckerCollector = new ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR);
ChuckerInterceptor chuckerInterceptor = new ChuckerInterceptor(this, chuckerCollector);
httpBuilder.addInterceptor(chuckerInterceptor);
}
http = httpBuilder.build();
httpLazy = http.newBuilder().followRedirects(false).followSslRedirects(false).build();
MHttp.instance()
.customOkHttpClient(http);
//register = new Register(mContext);
//profileLoadById(appSharedPrefs.getInt("current_profile_id", 1));
if (config.getSync().getEnabled()) {
SyncWorker.Companion.scheduleNext(this, false);
}
else {
SyncWorker.Companion.cancelNext(this);
}
db.metadataDao().countUnseen().observeForever(count -> {
Log.d("MainActivity", "Overall unseen count changed");
assert count != null;
if (unreadBadgesAvailable) {
unreadBadgesAvailable = ShortcutBadger.applyCount(this, count);
}
});
//new IonCookieManager(mContext);
new Handler().post(() -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
shortcutManager = getSystemService(ShortcutManager.class);
ShortcutInfo shortcutTimetable = new ShortcutInfo.Builder(mContext, "item_timetable")
.setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable))
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_timetable))
//.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon2.cmd_timetable))
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE))
.build();
ShortcutInfo shortcutAgenda = new ShortcutInfo.Builder(mContext, "item_agenda")
.setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda))
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_agenda))
//.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon.cmd_calendar))
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA))
.build();
ShortcutInfo shortcutGrades = new ShortcutInfo.Builder(mContext, "item_grades")
.setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades))
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_grades))
//.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon2.cmd_numeric_5_box))
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES))
.build();
ShortcutInfo shortcutHomework = new ShortcutInfo.Builder(mContext, "item_homeworks")
.setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework))
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_homework))
//.setIcon(getDesktopIconFromIconics(SzkolnyFont.Icon.szf_file_document_edit))
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK))
.build();
ShortcutInfo shortcutMessages = new ShortcutInfo.Builder(mContext, "item_messages")
.setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages))
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_messages))
//.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon.cmd_email))
.setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES ))
.build();
shortcutManager.setDynamicShortcuts(Arrays.asList(shortcutTimetable, shortcutAgenda, shortcutGrades, shortcutHomework, shortcutMessages));
}
if (config.getAppInstalledTime() == 0) {
try {
config.setAppInstalledTime(getPackageManager().getPackageInfo(getPackageName(), 0).firstInstallTime);
config.setAppRateSnackbarTime(config.getAppInstalledTime() + 7 * 24 * 60 * 60 * 1000);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
/*Task<CapabilityInfo> capabilityInfoTask =
Wearable.getCapabilityClient(this)
.getCapability("edziennik_wear_app", CapabilityClient.FILTER_REACHABLE);
capabilityInfoTask.addOnCompleteListener((task) -> {
if (task.isSuccessful()) {
CapabilityInfo capabilityInfo = task.getResult();
assert capabilityInfo != null;
Set<Node> nodes;
nodes = capabilityInfo.getNodes();
Log.d(TAG, "Nodes "+nodes);
if (nodes.size() > 0) {
Wearable.getMessageClient(this).sendMessage(
nodes.toArray(new Node[]{})[0].getId(), "/ping", "Hello world".getBytes());
}
} else {
Log.d(TAG, "Capability request failed to return any results.");
}
});
Wearable.getDataClient(this).addListener(dataEventBuffer -> {
Log.d(TAG, "onDataChanged(): " + dataEventBuffer);
for (DataEvent event : dataEventBuffer) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath();
Log.d(TAG, "Data "+path+ " :: "+Arrays.toString(event.getDataItem().getData()));
}
}
});*/
FirebaseApp pushMobidziennikApp = FirebaseApp.initializeApp(
this,
new FirebaseOptions.Builder()
.setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE")
.setApplicationId("1:747285019373:android:f6341bf7b158621d")
.build(),
"Mobidziennik2"
);
FirebaseApp pushLibrusApp = FirebaseApp.initializeApp(
this,
new FirebaseOptions.Builder()
.setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o")
.setApplicationId("1:513056078587:android:1e29083b760af544")
.build(),
"Librus"
);
FirebaseApp pushVulcanApp = FirebaseApp.initializeApp(
this,
new FirebaseOptions.Builder()
.setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA")
.setApplicationId("1:987828170337:android:ac97431a0a4578c3")
.build(),
"Vulcan"
);
if (config.getRunSync()) {
config.setRunSync(false);
EdziennikTask.Companion.sync().enqueue(this);
}
try {
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));
config.getSync().setTokenApp(instanceIdResult.getToken());
});
/*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<>()));
});
FirebaseInstanceId.getInstance(pushLibrusApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
Log.d(TAG, "Token for Librus is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId());
appConfig.fcmTokens.put(LOGIN_TYPE_LIBRUS, new Pair<>(instanceIdResult.getToken(), new ArrayList<>()));
});
FirebaseInstanceId.getInstance(pushVulcanApp).getInstanceId().addOnSuccessListener(instanceIdResult -> {
Log.d(TAG, "Token for Vulcan is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId());
Pair<String, List<Integer>> pair = appConfig.fcmTokens.get(LOGIN_TYPE_VULCAN);
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());
}
catch (IllegalStateException e) {
e.printStackTrace();
}
});
}
public void loadConfig()
{
appConfig = new AppConfig(this);
if (appSharedPrefs.contains("config")) {
// remove old-format config, save the new one and empty the incorrectly-nulled config
appConfig = gson.fromJson(appSharedPrefs.getString("config", ""), AppConfig.class);
appSharedPrefs.edit().remove("config").apply();
saveConfig();
appConfig = new AppConfig(this);
}
if (appSharedPrefs.contains("profiles")) {
SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit();
/*List<Integer> appProfileIds = gson.fromJson(appSharedPrefs.getString("profiles", ""), new TypeToken<List<Integer>>(){}.getType());
for (int id: appProfileIds) {
AppProfile appProfile = gson.fromJson(appSharedPrefs.getString("profile"+id, ""), AppProfile.class);
if (appProfile != null) {
appConfig.profiles.add(appProfile);
}
appSharedPrefsEditor.remove("profile"+id);
}*/
appSharedPrefsEditor.remove("profiles");
appSharedPrefsEditor.apply();
//profilesSave();
}
Map<String,?> keys = appSharedPrefs.getAll();
for (Map.Entry<String,?> entry : keys.entrySet()) {
if (entry.getKey().startsWith("app.appConfig.")) {
String fieldName = entry.getKey().replace("app.appConfig.", "");
try {
Field field = AppConfig.class.getField(fieldName);
Object object;
try {
object = gson.fromJson(entry.getValue().toString(), field.getGenericType());
} catch (JsonSyntaxException e) {
Log.d(TAG, "For field "+fieldName);
e.printStackTrace();
object = entry.getValue().toString();
}
if (object != null)
field.set(appConfig, object);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
Log.w(TAG, "Should remove app.appConfig."+fieldName);
//appSharedPrefs.edit().remove("app.appConfig."+fieldName).apply(); TODO migration
}
}
}
/*if (appConfig.lastAppVersion > BuildConfig.VERSION_CODE) {
BootReceiver br = new BootReceiver();
Intent i = new Intent();
//i.putExtra("UserChecked", true);
br.onReceive(getContext(), i);
Toast.makeText(mContext, R.string.warning_older_version_running, Toast.LENGTH_LONG).show();
//Toast.makeText(mContext, "Zaktualizuj aplikację.", Toast.LENGTH_LONG).show();
//System.exit(0);
}*/
if (appConfig == null) {
appConfig = new AppConfig(this);
}
}
public void saveConfig()
{
try {
appConfig.savePending = false;
SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit();
JsonObject appConfigJson = gson.toJsonTree(appConfig).getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : appConfigJson.entrySet()) {
String jsonObj;
jsonObj = entry.getValue().toString();
/*if (entry.getValue().isJsonObject()) {
jsonObj = entry.getValue().getAsJsonObject().toString();
}
else if (entry.getValue().isJsonArray()) {
jsonObj = entry.getValue().getAsJsonArray().toString();
}
else {
jsonObj = entry.getValue().toString();
}*/
appSharedPrefsEditor.putString("app.appConfig." + entry.getKey(), jsonObj);
}
appSharedPrefsEditor.apply();
}
catch (ConcurrentModificationException e) {
e.printStackTrace();
}
//appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply();
}
public void saveConfig(String ... fieldNames)
{
appConfig.savePending = false;
SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit();
for (String fieldName: fieldNames) {
try {
Object object = AppConfig.class.getField(fieldName).get(appConfig);
appSharedPrefsEditor.putString("app.appConfig."+fieldName, gson.toJson(object));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ConcurrentModificationException e) {
e.printStackTrace();
}
}
appSharedPrefsEditor.apply();
//appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply();
}
public void profileSave() {
AsyncTask.execute(() -> {
db.profileDao().add(profile);
});
}
public void profileSaveAsync() {
AsyncTask.execute(() -> {
db.profileDao().add(profile);
});
}
public void profileSaveAsync(Profile profile) {
AsyncTask.execute(() -> {
db.profileDao().add(profile);
});
}
public void profileLoadById(int id) {
profileLoadById(id, false);
}
public void profileLoadById(int id, boolean loadedLast) {
//Log.d(TAG, "Loading ID "+id);
/*if (profile == null) {
profile = profileNew();
AppDb.profileId = profile.id;
appSharedPrefs.edit().putInt("current_profile_id", profile.id).apply();
return;
}*/
if (profile == null || profile.getId() != id) {
profile = db.profileDao().getByIdNow(id);
/*if (profile == null) {
profileLoadById(id);
return;
}*/
if (profile != null) {
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);
}
else {
profileId = -1;
}
}
}
/*public void profileRemove(int id)
{
Profile profile = db.profileDao().getFullByIdNow(id);
if (profile.id == profile.loginStoreId) {
// this profile is the owner of the login store
// we need to check if any other profile is using it
List<Integer> transferProfileIds = db.profileDao().getIdsByLoginStoreIdNow(profile.loginStoreId);
if (transferProfileIds.size() == 1) {
// this login store is free of users, remove it along with the profile
db.loginStoreDao().remove(profile.loginStoreId);
// the current store is removed, we are ready to remove the profile
}
else if (transferProfileIds.size() > 1) {
transferProfileIds.remove(transferProfileIds.indexOf(profile.id));
// someone is using the store
// we need to transfer it to the firstProfileId
db.loginStoreDao().changeId(profile.loginStoreId, transferProfileIds.get(0));
db.profileDao().changeStoreId(profile.loginStoreId, transferProfileIds.get(0));
// the current store is removed, we are ready to remove the profile
}
}
// else, the profile uses a store that it doesn't own
// leave the store and go on with removing
Log.d(TAG, "Before removal: "+db.profileDao().getAllNow().toString());
db.profileDao().remove(profile.id);
Log.d(TAG, "After removal: "+db.profileDao().getAllNow().toString());
*//*int newId = 1;
if (appConfig.profiles.size() > 0) {
newId = appConfig.profiles.get(appConfig.profiles.size() - 1).id;
}
Log.d(TAG, "New ID: "+newId);
//Toast.makeText(mContext, "selected new id "+newId, Toast.LENGTH_SHORT).show();
profileLoadById(newId);*//*
}*/
public int profileFirstId() {
Integer id = db.profileDao().getFirstId();
return id == null ? 1 : id;
}
public int profileLastId() {
Integer id = db.profileDao().getLastId();
return id == null ? 1 : id;
}
public Context getContext()
{
return mContext;
}
public void checkDevModePassword() {
try {
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();
devMode = false;
}
}
}

View File

@ -4,20 +4,93 @@
package pl.szczodrzynski.edziennik
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.provider.Settings
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication
import androidx.work.Configuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import cat.ereza.customactivityoncrash.config.CaocConfig
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import com.hypertrack.hyperlog.HyperLog
import com.mikepenz.iconics.Iconics
import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont
import im.wangchao.mhttp.MHttp
import im.wangchao.mhttp.internal.cookie.PersistentCookieJar
import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache
import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor
import kotlinx.coroutines.*
import me.leolin.shortcutbadger.ShortcutBadger
import okhttp3.OkHttpClient
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.db.AppDb
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.network.NetworkUtils
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity
import pl.szczodrzynski.edziennik.utils.*
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext
class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScope {
class App : MultiDexApplication(), Configuration.Provider, CoroutineScope {
companion object {
@Volatile
lateinit var db: AppDb
val config: Config by lazy { Config(db) }
var profile: Profile by mutableLazy { Profile(0, 0, 0, "") }
val profileId
get() = profile.id
var devMode = false
}
//lateinit var db: AppDb
//val config by lazy { Config(db); // TODO migrate }
val notifications by lazy { Notifications() }
inner class Notifications {
val syncId = 1
val syncKey = "pl.szczodrzynski.edziennik.SYNC"
val syncChannelName: String by lazy { getString(R.string.notification_channel_get_data_name) }
val syncChannelDesc: String by lazy { getString(R.string.notification_channel_get_data_desc) }
val dataId = 50
val dataKey = "pl.szczodrzynski.edziennik.DATA"
val dataChannelName: String by lazy { getString(R.string.notification_channel_notifications_name) }
val dataChannelDesc: String by lazy { getString(R.string.notification_channel_notifications_desc) }
val dataQuietId = 60
val dataQuietKey = "pl.szczodrzynski.edziennik.DATA_QUIET"
val dataQuietChannelName: String by lazy { getString(R.string.notification_channel_notifications_quiet_name) }
val dataQuietChannelDesc: String by lazy { getString(R.string.notification_channel_notifications_quiet_desc) }
val updatesId = 100
val updatesKey = "pl.szczodrzynski.edziennik.UPDATES"
val updatesChannelName: String by lazy { getString(R.string.notification_channel_updates_name) }
val updatesChannelDesc: String by lazy { getString(R.string.notification_channel_updates_desc) }
}
val db
get() = App.db
val config
get() = App.config
val profile
get() = App.profile
val profileId
get() = App.profileId
private val job = Job()
override val coroutineContext: CoroutineContext
@ -26,11 +99,10 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
.setMinimumLoggingLevel(Log.VERBOSE)
.build()
/*val preferences by lazy { getSharedPreferences(getString(R.string.preference_file), Context.MODE_PRIVATE) }
val notifier by lazy { Notifier(this) }
val preferences by lazy { getSharedPreferences(getString(R.string.preference_file), Context.MODE_PRIVATE) }
val permissionChecker by lazy { PermissionChecker(this) }
lateinit var profile: ProfileFull
val networkUtils by lazy { NetworkUtils(this) }
val gson by lazy { Gson() }
/* _ _ _______ _______ _____
| | | |__ __|__ __| __ \
@ -48,13 +120,13 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
builder.installHttpsSupport()
builder.installHttpsSupport(this)
if (devMode || BuildConfig.DEBUG) {
HyperLog.initialize(this)
HyperLog.setLogLevel(Log.VERBOSE)
HyperLog.setLogFormat(DebugLogFormat(this))
val chuckerCollector = ChuckerCollector(this, true, Period.ONE_HOUR)
val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR)
val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector)
builder.addInterceptor(chuckerInterceptor)
}
@ -77,22 +149,7 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
|_____/|_|\__, |_| |_|\__,_|\__|\__,_|_| \___|
__/ |
|__*/
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
}
val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" }
private var unreadBadgesAvailable = true
/* _____ _
@ -118,193 +175,214 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
.apply()
Iconics.init(applicationContext)
Iconics.registerFont(SzkolnyFont)
db = AppDb.getDatabase(this)
App.db = AppDb(this)
Themes.themeInt = config.ui.theme
MHttp.instance().customOkHttpClient(http)
if (!profileLoadById(config.lastProfileId)) {
db.profileDao().firstId?.let { profileLoadById(it) }
}
devMode = "f054761fbdb6a238" == deviceId || BuildConfig.DEBUG
if (config.devModePassword != null)
checkDevModePassword()
Signing.getCert(this)
launch { async(Dispatchers.Default) {
if (config.sync.enabled) {
scheduleNext(this@App, false)
} else {
cancelNext(this@App)
launch {
withContext(Dispatchers.Default) {
config.migrate(this@App)
if (config.devModePassword != null)
checkDevModePassword()
if (config.sync.enabled)
SyncWorker.scheduleNext(this@App, false)
else
SyncWorker.cancelNext(this@App)
if (config.sync.notifyAboutUpdates)
UpdateWorker.scheduleNext(this@App, false)
else
UpdateWorker.cancelNext(this@App)
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(
NotificationChannel(notifications.syncKey, notifications.syncChannelName, NotificationManager.IMPORTANCE_MIN).apply {
description = notifications.syncChannelDesc
})
notificationManager.createNotificationChannel(
NotificationChannel(notifications.dataKey, notifications.dataChannelName, NotificationManager.IMPORTANCE_HIGH).apply {
description = notifications.dataChannelDesc
enableLights(true)
lightColor = 0xff2196f3.toInt()
})
notificationManager.createNotificationChannel(
NotificationChannel(notifications.dataQuietKey, notifications.dataQuietChannelName, NotificationManager.IMPORTANCE_LOW).apply {
description = notifications.dataQuietChannelDesc
setSound(null, null)
enableVibration(false)
})
notificationManager.createNotificationChannel(
NotificationChannel(notifications.updatesKey, notifications.updatesChannelName, NotificationManager.IMPORTANCE_DEFAULT).apply {
description = notifications.updatesChannelDesc
})
}
if (config.appInstalledTime == 0L)
try {
config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
config.appRateSnackbarTime = config.appInstalledTime + 7 * DAY * MS
} catch (e: PackageManager.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()
}
}
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) {
private fun profileLoadById(profileId: Int): Boolean {
db.profileDao().getByIdNow(profileId)?.also {
App.profile = it
App.config.lastProfileId = it.id
return true
}
return false
}
fun profileLoad(profileId: Int, onSuccess: (profile: Profile) -> Unit) {
launch {
val deferred = async(Dispatchers.Default) {
profileLoad(profileId)
val success = withContext(Dispatchers.Default) {
profileLoadById(profileId)
}
deferred.await()
onSuccess(profile)
if (success)
onSuccess(profile)
else
profileLoadLast(onSuccess)
}
}
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 profileLoadLast(onSuccess: (profile: Profile) -> Unit) {
launch {
val success = withContext(Dispatchers.Default) {
profileLoadById(db.profileDao().lastId ?: return@withContext false)
}
if (!success) {
EventBus.getDefault().post(ProfileListEmptyEvent())
}
}
}
fun profileSave() = profileSave(profile)
fun profileSave(profile: Profile) {
launch(Dispatchers.Default) {
App.db.profileDao().add(profile)
}
}
@ -315,5 +393,5 @@ class Szkolny : /*MultiDexApplication(),*/ Configuration.Provider, CoroutineScop
e.printStackTrace()
false
}
}*/
}
}

View File

@ -3,6 +3,7 @@ package pl.szczodrzynski.edziennik
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.content.res.Resources
@ -34,6 +35,7 @@ import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.android.gms.security.ProviderInstaller
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
@ -41,23 +43,31 @@ import im.wangchao.mhttp.Response
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.TlsVersion
import okio.Buffer
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.entity.Team
import pl.szczodrzynski.edziennik.network.TLSSocketFactory
import pl.szczodrzynski.edziennik.utils.models.Time
import java.io.PrintWriter
import java.io.StringWriter
import java.math.BigInteger
import java.nio.charset.Charset
import java.security.KeyStore
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.CRC32
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import kotlin.Pair
@ -642,6 +652,34 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
}
}
}
fun Intent(action: String? = null, vararg properties: Pair<String, Any?>): Intent {
return Intent(action).putExtras(Bundle(*properties))
}
fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair<String, Any?>): Intent {
return Intent(packageContext, cls).putExtras(Bundle(*properties))
}
fun Bundle.toJsonObject(): JsonObject {
val json = JsonObject()
keySet()?.forEach { key ->
get(key)?.let {
when (it) {
is String -> json.addProperty(key, it)
is Char -> json.addProperty(key, it)
is Int -> json.addProperty(key, it)
is Long -> json.addProperty(key, it)
is Float -> json.addProperty(key, it)
is Short -> json.addProperty(key, it)
is Double -> json.addProperty(key, it)
is Boolean -> json.addProperty(key, it)
is Bundle -> json.add(key, it.toJsonObject())
else -> json.addProperty(key, it.toString())
}
}
}
return json
}
fun Intent.toJsonObject() = extras?.toJsonObject()
fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0
fun JsonArray.isEmpty(): Boolean = this.size() == 0
@ -934,6 +972,8 @@ fun Context.getNotificationTitle(type: Int): String {
Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event
Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework
Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event
Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework
Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event
Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message
Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice
Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance
@ -950,3 +990,45 @@ fun Context.getNotificationTitle(type: Int): String {
fun Cursor?.getString(columnName: String) = this?.getStringOrNull(getColumnIndex(columnName))
fun Cursor?.getInt(columnName: String) = this?.getIntOrNull(getColumnIndex(columnName))
fun Cursor?.getLong(columnName: String) = this?.getLongOrNull(getColumnIndex(columnName))
fun OkHttpClient.Builder.installHttpsSupport(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
try {
try {
ProviderInstaller.installIfNeeded(context)
} 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 CharSequence.containsAll(list: List<CharSequence>, ignoreCase: Boolean = false): Boolean {
for (i in list) {
if (!contains(i, ignoreCase))
return false
}
return true
}

View File

@ -1,6 +1,5 @@
package pl.szczodrzynski.edziennik
import android.app.Activity
import android.app.ActivityManager
import android.content.BroadcastReceiver
import android.content.Context
@ -9,7 +8,6 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Environment
@ -36,18 +34,22 @@ import com.mikepenz.materialdrawer.model.ProfileDrawerItem
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IProfile
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.api.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Metadata.*
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.sync.UpdateWorker
import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
@ -93,9 +95,10 @@ import pl.szczodrzynski.navlib.drawer.items.withAppTitle
import java.io.File
import java.io.IOException
import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
class MainActivity : AppCompatActivity(), CoroutineScope {
companion object {
var useOldMessages = false
@ -108,6 +111,7 @@ class MainActivity : AppCompatActivity() {
const val DRAWER_PROFILE_SYNC_ALL = 201
const val DRAWER_PROFILE_EXPORT_DATA = 202
const val DRAWER_PROFILE_MANAGE = 203
const val DRAWER_PROFILE_MARK_ALL_AS_READ = 204
const val DRAWER_ITEM_HOME = 1
const val DRAWER_ITEM_TIMETABLE = 11
const val DRAWER_ITEM_AGENDA = 12
@ -208,6 +212,10 @@ class MainActivity : AppCompatActivity() {
.withDescription(R.string.drawer_manage_profiles_desc)
.isInProfileList(false)
list += NavTarget(DRAWER_PROFILE_MARK_ALL_AS_READ, R.string.menu_mark_everything_as_read, null)
.withIcon(CommunityMaterial.Icon.cmd_eye_check_outline)
.isInProfileList(true)
list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null)
.withIcon(CommunityMaterial.Icon.cmd_download_outline)
.isInProfileList(true)
@ -226,6 +234,10 @@ class MainActivity : AppCompatActivity() {
}
}
private var job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
val b: ActivitySzkolnyBinding by lazy { ActivitySzkolnyBinding.inflate(layoutInflater) }
val navView: NavView by lazy { b.navView }
val drawer: NavDrawer by lazy { navView.drawer }
@ -257,12 +269,21 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
d(TAG, "Activity created")
setTheme(Themes.appTheme)
app.config.ui.language?.let {
setLanguage(it)
}
if (App.profileId == 0) {
onProfileListEmptyEvent(ProfileListEmptyEvent())
return
}
d(TAG, "Profile is valid, inflating views")
setContentView(b.root)
Log.d(TAG, Signing.appPassword)
@ -332,8 +353,7 @@ class MainActivity : AppCompatActivity() {
setAccountHeaderBackground(app.config.ui.headerBackground)
drawerProfileListEmptyListener = {
app.config.loginFinished = false
profileListEmptyListener()
onProfileListEmptyEvent(ProfileListEmptyEvent())
}
drawerItemSelectedListener = { id, position, drawerItem ->
loadTarget(id)
@ -362,30 +382,19 @@ class MainActivity : AppCompatActivity() {
navTarget = navTargetList[0]
var profileListEmpty = drawer.profileListEmpty
if (savedInstanceState != null) {
intent?.putExtras(savedInstanceState)
savedInstanceState.clear()
}
if (!profileListEmpty) {
handleIntent(intent?.extras)
}
app.db.profileDao().all.observe(this, Observer { profiles ->
drawer.setProfileList(profiles.filter { it.id >= 0 }.toMutableList())
if (profileListEmpty) {
profileListEmpty = false
handleIntent(intent?.extras)
}
else if (app.profile != null) {
drawer.currentProfile = app.profile.id
}
drawer.currentProfile = App.profileId
})
// if null, getAllFull will load a profile and update drawerItems
if (app.profile != null)
setDrawerItems()
setDrawerItems()
handleIntent(intent?.extras)
app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters ->
unreadCounters.map {
@ -405,6 +414,7 @@ class MainActivity : AppCompatActivity() {
isStoragePermissionGranted()
SyncWorker.scheduleNext(app)
UpdateWorker.scheduleNext(app)
// APP BACKGROUND
if (app.config.ui.appBackground != null) {
@ -509,17 +519,25 @@ class MainActivity : AppCompatActivity() {
}
}
var profileListEmptyListener = {
startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY)
}
private var profileSettingClickListener = { id: Int, view: View? ->
when (id) {
DRAWER_PROFILE_ADD_NEW -> {
profileListEmptyListener()
startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY)
}
DRAWER_PROFILE_SYNC_ALL -> {
EdziennikTask.sync().enqueue(this)
}
DRAWER_PROFILE_MARK_ALL_AS_READ -> { launch {
withContext(Dispatchers.Default) {
app.db.profileDao().allNow.forEach { profile ->
if (profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS)
app.db.metadataDao().setAllSeenExceptMessagesAndAnnouncements(profile.id, true)
else
app.db.metadataDao().setAllSeenExceptMessages(profile.id, true)
}
}
Toast.makeText(this@MainActivity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show()
}}
else -> {
loadTarget(id)
}
@ -564,6 +582,13 @@ class MainActivity : AppCompatActivity() {
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onProfileListEmptyEvent(event: ProfileListEmptyEvent) {
d(TAG, "Profile list is empty. Launch LoginActivity.")
app.config.loginFinished = false
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) {
if (event.profileId == App.profileId) {
navView.toolbar.apply {
@ -579,6 +604,7 @@ class MainActivity : AppCompatActivity() {
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) {
EventBus.getDefault().removeStickyEvent(event)
if (event.profileId == App.profileId) {
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
@ -589,6 +615,7 @@ class MainActivity : AppCompatActivity() {
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) {
EventBus.getDefault().removeStickyEvent(event)
swipeRefreshLayout.isRefreshing = false
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
@ -604,7 +631,8 @@ class MainActivity : AppCompatActivity() {
}
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
if (app.appConfig.dontShowAppManagerDialog)
EventBus.getDefault().removeStickyEvent(event)
if (app.config.sync.dontShowAppManagerDialog)
return
MaterialAlertDialogBuilder(this)
.setTitle(R.string.app_manager_dialog_title)
@ -626,8 +654,7 @@ class MainActivity : AppCompatActivity() {
}
}
.setNeutralButton(R.string.dont_ask_again) { dialog, which ->
app.appConfig.dontShowAppManagerDialog = true
app.saveConfig("dontShowAppManagerDialog")
app.config.sync.dontShowAppManagerDialog = true
}
.setCancelable(false)
.show()
@ -669,10 +696,26 @@ class MainActivity : AppCompatActivity() {
}
d(TAG, "}")
if (extras?.containsKey("action") == true) {
val handled = when (extras.getString("action")) {
"serverMessage" -> {
ServerMessageDialog(
this,
extras.getString("serverMessageTitle") ?: getString(R.string.app_name),
extras.getString("serverMessageText") ?: ""
)
true
}
else -> false
}
if (handled)
return
}
if (extras?.containsKey("reloadProfileId") == true) {
val reloadProfileId = extras.getInt("reloadProfileId", -1)
extras.remove("reloadProfileId")
if (reloadProfileId == -1 || (app.profile != null && app.profile.id == reloadProfileId)) {
if (reloadProfileId == -1 || app.profile.id == reloadProfileId) {
reloadTarget()
return
}
@ -702,9 +745,9 @@ class MainActivity : AppCompatActivity() {
}
when {
app.profile == null || app.profile.id == -1 -> {
app.profile.id == 0 -> {
if (intentProfileId == -1)
intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1)
intentProfileId = app.config.lastProfileId
loadProfile(intentProfileId, intentTargetId, extras)
}
intentProfileId != -1 -> {
@ -743,7 +786,16 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
}
override fun onStart() {
d(TAG, "Activity started")
super.onStart()
}
override fun onStop() {
d(TAG, "Activity stopped")
super.onStop()
}
override fun onResume() {
d(TAG, "Activity resumed")
val filter = IntentFilter()
filter.addAction(Intent.ACTION_MAIN)
registerReceiver(intentReceiver, filter)
@ -751,10 +803,15 @@ class MainActivity : AppCompatActivity() {
super.onResume()
}
override fun onPause() {
d(TAG, "Activity paused")
unregisterReceiver(intentReceiver)
EventBus.getDefault().unregister(this)
super.onPause()
}
override fun onDestroy() {
d(TAG, "Activity destroyed")
super.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
@ -768,15 +825,10 @@ class MainActivity : AppCompatActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_LOGIN_ACTIVITY) {
if (resultCode == Activity.RESULT_CANCELED && false) {
if (!app.config.loginFinished)
finish()
}
else {
if (!app.config.loginFinished)
finish()
else {
handleIntent(data?.extras)
}
handleIntent(data?.extras)
}
}
}
@ -797,34 +849,23 @@ class MainActivity : AppCompatActivity() {
fun loadProfile(id: Int) = loadProfile(id, navTargetId)
fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments)
fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) {
//d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)")
if (app.profile != null && App.profileId == id) {
if (App.profileId == id) {
drawer.currentProfile = app.profile.id
loadTarget(drawerSelection, arguments)
return
}
AsyncTask.execute {
app.profileLoadById(id)
app.profileLoad(id) {
MessagesFragment.pageSelection = -1
MessagesListFragment.tapPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION)
MessagesListFragment.topPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION)
MessagesListFragment.bottomPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION)
this.runOnUiThread {
if (app.profile == null) {
if (app.config.loginFinished) {
// this shouldn't run
profileListEmptyListener()
}
} else {
setDrawerItems()
// 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)
}
}
setDrawerItems()
// 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.profileId
loadTarget(drawerSelection, arguments)
}
}
fun loadTarget(id: Int, arguments: Bundle? = null) {
@ -969,7 +1010,7 @@ class MainActivity : AppCompatActivity() {
* that something has changed in the bottom sheet.
*/
fun gainAttention() {
if (app.config.ui.bottomSheetOpened || true)
if (app.config.ui.bottomSheetOpened)
return
b.navView.postDelayed({
navView.gainAttentionOnBottomBar()
@ -1018,12 +1059,11 @@ class MainActivity : AppCompatActivity() {
}
fun setDrawerItems() {
d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}")
d("NavDebug", "setDrawerItems() app.profile = ${app.profile}")
val drawerItems = arrayListOf<IDrawerItem<*>>()
val drawerProfiles = arrayListOf<ProfileSettingDrawerItem>()
val supportedFragments = if (app.profile == null) arrayListOf<Int>()
else app.profile.supportedFragments
val supportedFragments = app.profile.supportedFragments
targetPopToHomeList.clear()
@ -1087,7 +1127,7 @@ class MainActivity : AppCompatActivity() {
private var targetHomeId: Int = -1
override fun onBackPressed() {
if (!b.navView.onBackPressed()) {
if (App.getConfig().ui.openDrawerOnBackPressed) {
if (App.config.ui.openDrawerOnBackPressed) {
b.navView.drawer.toggle()
} else {
navigateUp()

View File

@ -1,350 +0,0 @@
package pl.szczodrzynski.edziennik;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT;
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
public class Notifier {
private static final String TAG = "Notifier";
public static final int ID_GET_DATA = 1337000;
public static final int ID_GET_DATA_ERROR = 1337001;
private static String CHANNEL_GET_DATA_NAME;
private static String CHANNEL_GET_DATA_DESC;
private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA";
public static final int ID_NOTIFICATIONS = 1337002;
public static String CHANNEL_NOTIFICATIONS_NAME;
public static String CHANNEL_NOTIFICATIONS_DESC;
public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS";
public static final int ID_NOTIFICATIONS_QUIET = 1337002;
public static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
public static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET";
private static final int ID_UPDATES = 1337003;
private static String CHANNEL_UPDATES_NAME;
private static String CHANNEL_UPDATES_DESC;
private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES";
private App app;
public NotificationManager notificationManager;
private NotificationCompat.Builder getDataNotificationBuilder;
public int notificationColor;
Notifier(App _app) {
this.app = _app;
CHANNEL_GET_DATA_NAME = app.getString(R.string.notification_channel_get_data_name);
CHANNEL_GET_DATA_DESC = app.getString(R.string.notification_channel_get_data_desc);
CHANNEL_NOTIFICATIONS_NAME = app.getString(R.string.notification_channel_notifications_name);
CHANNEL_NOTIFICATIONS_DESC = app.getString(R.string.notification_channel_notifications_desc);
CHANNEL_NOTIFICATIONS_QUIET_NAME = app.getString(R.string.notification_channel_notifications_quiet_name);
CHANNEL_NOTIFICATIONS_QUIET_DESC = app.getString(R.string.notification_channel_notifications_quiet_desc);
CHANNEL_UPDATES_NAME = app.getString(R.string.notification_channel_updates_name);
CHANNEL_UPDATES_DESC = app.getString(R.string.notification_channel_updates_desc);
notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary);
notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_MIN);
channelGetData.setDescription(CHANNEL_GET_DATA_DESC);
notificationManager.createNotificationChannel(channelGetData);
NotificationChannel channelNotifications = new NotificationChannel(GROUP_KEY_NOTIFICATIONS, CHANNEL_NOTIFICATIONS_NAME, NotificationManager.IMPORTANCE_HIGH);
channelNotifications.setDescription(CHANNEL_NOTIFICATIONS_DESC);
channelNotifications.enableLights(true);
channelNotifications.setLightColor(notificationColor);
notificationManager.createNotificationChannel(channelNotifications);
NotificationChannel channelNotificationsQuiet = new NotificationChannel(GROUP_KEY_NOTIFICATIONS_QUIET, CHANNEL_NOTIFICATIONS_QUIET_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channelNotificationsQuiet.setDescription(CHANNEL_NOTIFICATIONS_QUIET_DESC);
channelNotificationsQuiet.setSound(null, null);
channelNotificationsQuiet.enableVibration(false);
notificationManager.createNotificationChannel(channelNotificationsQuiet);
NotificationChannel channelUpdates = new NotificationChannel(GROUP_KEY_UPDATES, CHANNEL_UPDATES_NAME, NotificationManager.IMPORTANCE_HIGH);
channelUpdates.setDescription(CHANNEL_UPDATES_DESC);
notificationManager.createNotificationChannel(channelUpdates);
}
}
public boolean shouldBeQuiet() {
long now = Time.getNow().getInMillis();
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");
}
if (start > now) {
now += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Now is smaller");
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return start > 0 && now >= start && now <= end;
}
public int getNotificationDefaults() {
return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL);
}
public String getNotificationGroup() {
return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS;
}
public int getNotificationPriority() {
return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX;
}
/* _____ _ _____ _
| __ \ | | / ____| | |
| | | | __ _| |_ __ _ | | __ ___| |_
| | | |/ _` | __/ _` | | | |_ |/ _ \ __|
| |__| | (_| | || (_| | | |__| | __/ |_
|_____/ \__,_|\__\__,_| \_____|\___|\_*/
public Notification notificationGetDataShow(int maxProgress) {
/*Intent notificationIntent = new Intent(app.getContext(), SyncService.class);
notificationIntent.setAction(ACTION_CANCEL);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);*/
getDataNotificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_GET_DATA)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_get_data_title))
.setContentText(app.getString(R.string.notification_get_data_text))
//.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent)
//.setGroup(GROUP_KEY_GET_DATA)
.setOngoing(true)
.setProgress(maxProgress, 0, false)
.setTicker(app.getString(R.string.notification_get_data_summary))
.setPriority(NotificationCompat.PRIORITY_LOW);
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataProgress(int progress, int maxProgress) {
getDataNotificationBuilder.setProgress(maxProgress, progress, false);
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataAction(int stringResId) {
getDataNotificationBuilder.setContentTitle(app.getString(R.string.sync_action_format, app.getString(stringResId)));
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataProfile(String profileName) {
getDataNotificationBuilder.setContentText(profileName);
return getDataNotificationBuilder.build();
}
public Notification notificationGetDataError(String profileName, String error, int failedProfileId) {
Intent notificationIntent = new Intent(app.getContext(), Notifier.GetDataRetryService.class);
notificationIntent.putExtra("failedProfileId", failedProfileId);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
getDataNotificationBuilder.mActions.clear();
/*try {
//Use reflection clean up old actions
Field f = getDataNotificationBuilder.getClass().getDeclaredField("mActions");
f.setAccessible(true);
f.set(getDataNotificationBuilder, new ArrayList<NotificationCompat.Action>());
} catch (Exception e) {
e.printStackTrace();
}*/
getDataNotificationBuilder.setProgress(0, 0, false)
.setTicker(app.getString(R.string.notification_get_data_error_summary))
.setSmallIcon(android.R.drawable.stat_sys_warning)
.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_once_again), pendingIntent)
.setContentTitle(app.getString(R.string.notification_get_data_error_title, profileName))
.setContentText(error)
.setStyle(new NotificationCompat.BigTextStyle().bigText(error))
.setOngoing(false);
return getDataNotificationBuilder.build();
}
public void notificationPost(int id, Notification notification) {
notificationManager.notify(id, notification);
}
public void notificationCancel(int id) {
notificationManager.cancel(id);
}
//public void notificationGetDataHide() {
// notificationManager.cancel(ID_GET_DATA);
// }
public static class GetDataRetryService extends IntentService {
private static final String TAG = "Notifier/GetDataRetry";
public GetDataRetryService() {
super(Notifier.GetDataRetryService.class.getSimpleName());
}
@Override
protected void onHandleIntent(Intent intent) {
}
}
/* _ _ _ _ __ _ _ _
| \ | | | | (_)/ _(_) | | (_)
| \| | ___ | |_ _| |_ _ ___ __ _| |_ _ ___ _ __
| . ` |/ _ \| __| | _| |/ __/ _` | __| |/ _ \| '_ \
| |\ | (_) | |_| | | | | (_| (_| | |_| | (_) | | | |
|_| \_|\___/ \__|_|_| |_|\___\__,_|\__|_|\___/|_| |*/
public void add(pl.szczodrzynski.edziennik.utils.models.Notification notification) {
app.appConfig.notifications.add(notification);
}
public void postAll() {
Collections.sort(app.appConfig.notifications, (o1, o2) -> (o2.addedDate - o1.addedDate > 0) ? 1 : (o2.addedDate - o1.addedDate < 0) ? -1 : 0);
if (app.appConfig.notifications.size() > 40) {
app.appConfig.notifications.subList(40, app.appConfig.notifications.size() - 1).clear();
}
int unreadCount = 0;
List<pl.szczodrzynski.edziennik.utils.models.Notification> notificationList = new ArrayList<>();
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) {
if (!notification.notified) {
notification.seen = false;
notification.notified = true;
unreadCount++;
if (notificationList.size() < 10) {
notificationList.add(notification);
}
}
else {
notification.seen = true;
}
}
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: notificationList) {
Intent intent = new Intent(app, MainActivity.class);
notification.fillIntent(intent);
PendingIntent pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, getNotificationGroup())
// title, text, type, date
.setContentTitle(notification.title)
.setContentText(notification.text)
.setSubText(pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type))
.setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type)))
// icon, color, lights, priority
.setSmallIcon(R.drawable.ic_notification)
.setColor(notificationColor)
.setLights(0xFF00FFFF, 2000, 2000)
.setPriority(getNotificationPriority())
// channel, group, style
.setChannelId(getNotificationGroup())
.setGroup(getNotificationGroup())
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setStyle(new NotificationCompat.BigTextStyle().bigText(notification.text))
// intent, auto cancel
.setContentIntent(pendingIntent)
.setAutoCancel(true);
if (!shouldBeQuiet()) {
notificationBuilder.setDefaults(getNotificationDefaults());
}
notificationManager.notify(notification.id, notificationBuilder.build());
}
if (notificationList.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent = new Intent(app, MainActivity.class);
intent.setAction("android.intent.action.MAIN");
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS);
PendingIntent pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
intent, 0);
NotificationCompat.Builder groupBuilder =
new NotificationCompat.Builder(app, getNotificationGroup())
.setSmallIcon(R.drawable.ic_notification)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
.setGroupSummary(true)
.setAutoCancel(true)
.setChannelId(getNotificationGroup())
.setGroup(getNotificationGroup())
.setLights(0xFF00FFFF, 2000, 2000)
.setPriority(getNotificationPriority())
.setContentIntent(pendingIntent)
.setStyle(new NotificationCompat.BigTextStyle());
if (!shouldBeQuiet()) {
groupBuilder.setDefaults(getNotificationDefaults());
}
notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build());
}
}
/* _ _ _ _
| | | | | | | |
| | | |_ __ __| | __ _| |_ ___ ___
| | | | '_ \ / _` |/ _` | __/ _ \/ __|
| |__| | |_) | (_| | (_| | || __/\__ \
\____/| .__/ \__,_|\__,_|\__\___||___/
| |
|*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
if (!app.config.getSync().getNotifyAboutUpdates())
return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion)
.putExtra("update_url", updateUrl)
.putExtra("update_filename", updateFilename)
.putExtra("update_direct", updateDirect);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_UPDATES)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setColor(notificationColor)
.setContentTitle(app.getString(R.string.notification_updates_title))
.setContentText(app.getString(R.string.notification_updates_text, updateVersion))
.setLights(0xFF00FFFF, 2000, 2000)
.setContentIntent(pendingIntent)
.setTicker(app.getString(R.string.notification_updates_summary))
.setPriority(PRIORITY_MAX)
.setAutoCancel(true);
if (!shouldBeQuiet()) {
notificationBuilder.setDefaults(getNotificationDefaults());
}
notificationManager.notify(ID_UPDATES, notificationBuilder.build());
}
public void notificationUpdatesHide() {
if (!app.config.getSync().getNotifyAboutUpdates())
return;
notificationManager.cancel(ID_UPDATES);
}
public void dump() {
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) {
Log.d(TAG, "Profile"+notification.profileId+" Notification from "+ Date.fromMillis(notification.addedDate).getFormattedString()+" "+ Time.fromMillis(notification.addedDate).getStringHMS()+" - "+notification.text);
}
}
}

View File

@ -16,12 +16,13 @@ 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.api.szkolny.response.Update
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
const val DATA_VERSION = 10
}
private val job = Job()
@ -45,6 +46,20 @@ class Config(val db: AppDb) : CoroutineScope, AbstractConfig {
get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" }
set(value) { set("hash", value); mHash = value }
private var mLastProfileId: Int? = null
var lastProfileId: Int
get() { mLastProfileId = mLastProfileId ?: values.get("lastProfileId", 0); return mLastProfileId ?: 0 }
set(value) { set("lastProfileId", value); mLastProfileId = value }
private var mUpdatesChannel: String? = null
var updatesChannel: String
get() { mUpdatesChannel = mUpdatesChannel ?: values.get("updatesChannel", "release"); return mUpdatesChannel ?: "release" }
set(value) { set("updatesChannel", value); mUpdatesChannel = value }
private var mUpdate: Update? = null
var update: Update?
get() { mUpdate = mUpdate ?: values.get("update", null as Update?); return mUpdate ?: null as Update? }
set(value) { set("update", value); mUpdate = value }
private var mAppVersion: Int? = null
var appVersion: Int
get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE }

View File

@ -17,6 +17,6 @@ class ConfigGrades(private val config: Config) {
private var mOrderBy: Int? = null
var orderBy: Int
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: 0 }
get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: ORDER_BY_DATE_DESC }
set(value) { config.set("gradesOrderBy", value); mOrderBy = value }
}

View File

@ -9,6 +9,11 @@ import pl.szczodrzynski.edziennik.config.utils.getIntList
import pl.szczodrzynski.edziennik.config.utils.set
class ConfigSync(private val config: Config) {
private var mDontShowAppManagerDialog: Boolean? = null
var dontShowAppManagerDialog: Boolean
get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false }
set(value) { config.set("dontShowAppManagerDialog", value); mDontShowAppManagerDialog = value }
private var mSyncEnabled: Boolean? = null
var enabled: Boolean
get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true }

View File

@ -45,11 +45,6 @@ class ConfigUI(private val config: Config) {
get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false }
set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = 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() }

View File

@ -9,6 +9,7 @@ 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.ProfileConfigMigration
import pl.szczodrzynski.edziennik.config.utils.get
import pl.szczodrzynski.edziennik.config.utils.set
import pl.szczodrzynski.edziennik.config.utils.toHashMap
@ -27,6 +28,7 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
val values: HashMap<String, String?> = hashMapOf()
val grades by lazy { ProfileConfigGrades(this) }
val ui by lazy { ProfileConfigUI(this) }
/*
val sync by lazy { ConfigSync(this) }
val timetable by lazy { ConfigTimetable(this) }
@ -44,8 +46,8 @@ class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List<ConfigEn
init {
rawEntries.toHashMap(profileId, values)
/*if (dataVersion < DATA_VERSION)
ProfileConfigMigration(this)*/
if (dataVersion < DATA_VERSION)
ProfileConfigMigration(this)
}
override fun set(key: String, value: String?) {

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-19.
*/
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.entity.Profile.Companion.AGENDA_DEFAULT
class ProfileConfigUI(private val config: ProfileConfig) {
private var mAgendaViewType: Int? = null
var agendaViewType: Int
get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT }
set(value) { config.set("agendaViewType", value); mAgendaViewType = value }
}

View File

@ -22,4 +22,7 @@ interface ConfigDao {
@Query("SELECT * FROM config WHERE profileId = :profileId")
fun getAllNow(profileId: Int): List<ConfigEntry>
@Query("DELETE FROM config WHERE profileId = :profileId")
fun clear(profileId: Int)
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-19.
*/
package pl.szczodrzynski.edziennik.config.utils
import android.content.SharedPreferences
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.utils.models.Time
class AppConfigMigrationV3(p: SharedPreferences, config: Config) {
init { config.apply {
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.registerSyncInterval", 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

@ -37,6 +37,9 @@ fun AbstractConfig.set(key: String, value: JsonElement?) {
fun AbstractConfig.set(key: String, value: List<Any>?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.set(key: String, value: Any?) {
set(key, value?.let { gson.toJson(it) })
}
fun AbstractConfig.setStringList(key: String, value: List<String>?) {
set(key, value?.let { gson.toJson(it) })
}
@ -74,6 +77,9 @@ fun HashMap<String, String?>.get(key: String, default: JsonObject?): JsonObject?
fun HashMap<String, String?>.get(key: String, default: JsonArray?): JsonArray? {
return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default
}
inline fun <reified T> HashMap<String, String?>.get(key: String, default: T?): T? {
return this[key]?.let { Gson().fromJson(it, T::class.java) } ?: 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

View File

@ -5,32 +5,32 @@
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.HOUR
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK
import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.config.ConfigGrades.Companion.ORDER_BY_DATE_DESC
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(
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
if (p.contains("app.appConfig.appTheme")) {
// migrate appConfig from app version 3.x and lower.
// Updates dataVersion to level 2.
AppConfigMigrationV3(p, config)
}
if (dataVersion < 2) {
appVersion = BuildConfig.VERSION_CODE
loginFinished = false
ui.language = null
ui.theme = 1
ui.appBackground = null
ui.headerBackground = null
ui.miniMenuVisible = false
ui.miniMenuButtons = listOf(
MainActivity.DRAWER_ITEM_HOME,
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
@ -39,46 +39,36 @@ class ConfigMigration(app: App, config: Config) {
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.enabled = true
sync.interval = 1*HOUR.toInt()
sync.notifyAboutUpdates = true
sync.onlyWifi = false
sync.quietHoursStart = 0
sync.quietHoursEnd = 0
sync.quietDuringLessons = false
sync.tokenApp = null
sync.tokenMobidziennik = null
sync.tokenMobidziennikList = listOf()
sync.tokenVulcanList = listOf()
sync.tokenLibrus = null
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
}
}
sync.tokenVulcan = null
sync.tokenVulcanList = listOf()
timetable.bellSyncMultiplier = 0
timetable.bellSyncDiff = null
timetable.countInSeconds = false
grades.orderBy = ORDER_BY_DATE_DESC
dataVersion = 2
}
}}
private fun String?.fix(): String? {
return this?.replace("\"", "")?.let { if (it == "null") null else it }
}
if (dataVersion < 10) {
ui.openDrawerOnBackPressed = false
ui.homeCards = listOf()
ui.snowfall = false
ui.bottomSheetOpened = false
sync.dontShowAppManagerDialog = false
dataVersion = 10
}
}}
}

View File

@ -4,23 +4,21 @@
package pl.szczodrzynski.edziennik.config.utils
import android.content.Context
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.config.Config
import pl.szczodrzynski.edziennik.config.ProfileConfig
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.COLOR_MODE_WEIGHTED
import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.YEAR_ALL_GRADES
class ProfileConfigMigration(app: App, config: Config) {
class ProfileConfigMigration(config: ProfileConfig) {
init { config.apply {
val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE)
val s = "app.appConfig"
if (dataVersion < 1) {
grades.colorMode = COLOR_MODE_WEIGHTED
grades.countZeroToAvg = true
grades.yearAverageMode = YEAR_ALL_GRADES
ui.agendaViewType = AGENDA_DEFAULT
//dataVersion = 1
}
if (dataVersion < 2) {
//gradesColorMode do profilu !
//agendaViewType do profilu !
// app.appConfig.dontCountZeroToAverage do profilu !
dataVersion = 1
}
}}
}

View File

@ -12,12 +12,15 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.events.*
import pl.szczodrzynski.edziennik.data.api.events.requests.ServiceCloseRequest
import pl.szczodrzynski.edziennik.data.api.events.requests.TaskCancelRequest
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.task.*
import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
import kotlin.math.min
@ -40,15 +43,13 @@ class ApiService : Service() {
private val syncingProfiles = mutableListOf<Profile>()
private val finishingTaskQueue = mutableListOf(
SzkolnyTask.sync(syncingProfiles),
NotifyTask()
)
private val allTaskList = mutableListOf<IApiTask>()
private var szkolnyTaskFinished = false
private val allTaskRequestList = mutableListOf<Any>()
private val taskQueue = mutableListOf<IApiTask>()
private val errorList = mutableListOf<ApiError>()
private var serviceClosed = false
set(value) { field = value; notification.serviceClosed = value }
private var taskCancelled = false
private var taskIsRunning = false
private var taskRunning: IApiTask? = null // for debug purposes
@ -131,15 +132,20 @@ class ApiService : Service() {
checkIfTaskFrozen()
if (taskIsRunning)
return
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && finishingTaskQueue.isEmpty())) {
serviceClosed = false
if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && szkolnyTaskFinished)) {
allCompleted()
return
}
lastEventTime = System.currentTimeMillis()
val task = if (taskQueue.isEmpty()) finishingTaskQueue.removeAt(0) else taskQueue.removeAt(0)
val task = if (taskQueue.isNotEmpty()) {
taskQueue.removeAt(0)
} else {
szkolnyTaskFinished = true
SzkolnyTask(app, syncingProfiles)
}
task.taskId = ++taskMaximumId
task.prepare(app)
taskIsRunning = true
@ -162,9 +168,8 @@ class ApiService : Service() {
try {
when (task) {
is EdziennikTask -> task.run(app, taskCallback)
is NotifyTask -> task.run(app, taskCallback)
is ErrorReportTask -> task.run(app, taskCallback, notification, errorList)
is SzkolnyTask -> task.run(app, taskCallback)
is SzkolnyTask -> task.run(taskCallback)
}
} catch (e: Exception) {
taskCallback.onError(ApiError(TAG, EXCEPTION_API_TASK).withThrowable(e))
@ -214,6 +219,7 @@ class ApiService : Service() {
}
private fun allCompleted() {
serviceClosed = true
EventBus.getDefault().postSticky(ApiTaskAllFinishedEvent())
stopSelf()
}
@ -229,10 +235,12 @@ class ApiService : Service() {
EventBus.getDefault().removeStickyEvent(task)
d(TAG, task.toString())
// fix for duplicated tasks, thank you EventBus
if (task in allTaskList)
return
allTaskList += task
if (task is EdziennikTask) {
// fix for duplicated tasks, thank you EventBus
if (task.request in allTaskRequestList)
return
allTaskRequestList += task.request
}
if (task is EdziennikTask) {
when (task.request) {
@ -298,6 +306,8 @@ class ApiService : Service() {
}
override fun onDestroy() {
d(TAG, "Service destroyed")
serviceClosed = true
EventBus.getDefault().unregister(this)
}

View File

@ -1,209 +0,0 @@
package pl.szczodrzynski.edziennik.data.api
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_HOME
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_TIMETABLE
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
class DataNotifications(val data: Data) {
companion object {
private const val TAG = "DataNotifications"
}
val app = data.app
val profileId = data.profile?.id ?: -1
val profileName = data.profile?.name ?: ""
val profile = data.profile
val loginStore = data.loginStore
init { run {
if (profile == null) {
return@run
}
val today = Date.getToday()
val todayValue = today.value
for (lesson in app.db.timetableDao().getNotNotifiedNow(profileId)) {
val text = app.getString(R.string.notification_lesson_change_format, lesson.getDisplayChangeType(app), if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString, lesson.changeSubjectName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
type = TYPE_TIMETABLE_LESSON_CHANGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_TIMETABLE,
addedDate = lesson.addedDate
).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "")
}
for (event in app.db.eventDao().getNotNotifiedNow(profileId)) {
val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
)
else
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.subjectLongName
)
val type = if (event.type == Event.TYPE_HOMEWORK) TYPE_NEW_HOMEWORK else TYPE_NEW_EVENT
data.notifications += Notification(
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = profileId,
profileName = profileName,
viewId = if (event.type == Event.TYPE_HOMEWORK) DRAWER_ITEM_HOMEWORK else DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
for (grade in app.db.gradeDao().getNotNotifiedNow(profileId)) {
val gradeName = when (grade.type) {
TYPE_SEMESTER1_PROPOSED, TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name)
TYPE_SEMESTER1_FINAL, TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name)
TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name)
TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name)
else -> grade.name
}
val text = app.getString(R.string.notification_grade_format, gradeName, grade.subjectLongName)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_GRADE),
text = text,
type = TYPE_NEW_GRADE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_GRADES,
addedDate = grade.addedDate
).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId)
}
for (notice in app.db.noticeDao().getNotNotifiedNow(profileId)) {
val noticeTypeStr = if (notice.type == Notice.TYPE_POSITIVE) app.getString(R.string.notification_notice_praise) else if (notice.type == Notice.TYPE_NEGATIVE) app.getString(R.string.notification_notice_warning) else app.getString(R.string.notification_notice_new)
val text = app.getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).formattedString)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_NOTICE),
text = text,
type = TYPE_NEW_NOTICE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_BEHAVIOUR,
addedDate = notice.addedDate
).addExtra("noticeId", notice.id)
}
for (attendance in app.db.attendanceDao().getNotNotifiedNow(profileId)) {
var attendanceTypeStr = app.getString(R.string.notification_type_attendance)
when (attendance.type) {
Attendance.TYPE_ABSENT -> attendanceTypeStr = app.getString(R.string.notification_absence)
Attendance.TYPE_ABSENT_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_absence_excused)
Attendance.TYPE_BELATED -> attendanceTypeStr = app.getString(R.string.notification_belated)
Attendance.TYPE_BELATED_EXCUSED -> attendanceTypeStr = app.getString(R.string.notification_belated_excused)
Attendance.TYPE_RELEASED -> attendanceTypeStr = app.getString(R.string.notification_release)
}
val text = app.getString(
if (attendance.subjectLongName.isNullOrEmpty())
R.string.notification_attendance_no_lesson_format
else
R.string.notification_attendance_format,
attendanceTypeStr,
attendance.subjectLongName,
attendance.lessonDate.formattedString
)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ATTENDANCE),
text = text,
type = TYPE_NEW_ATTENDANCE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ATTENDANCE,
addedDate = attendance.addedDate
).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId)
}
for (announcement in app.db.announcementDao().getNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_announcement_format, announcement.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_ANNOUNCEMENT),
text = text,
type = TYPE_NEW_ANNOUNCEMENT,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_ANNOUNCEMENTS,
addedDate = announcement.addedDate
).addExtra("announcementId", announcement.id)
}
for (message in app.db.messageDao().getReceivedNotNotifiedNow(profileId)) {
val text = app.context.getString(R.string.notification_message_format, message.senderFullName, message.subject)
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_NEW_MESSAGE),
text = text,
type = TYPE_NEW_MESSAGE,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_MESSAGES,
addedDate = message.addedDate
).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id)
}
val luckyNumbers = app.db.luckyNumberDao().getNotNotifiedNow(profileId)
luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber ->
val text = when (luckyNumber.date.value) {
todayValue -> // LN for today
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_format else R.string.notification_lucky_number_format, luckyNumber.number)
todayValue + 1 -> // LN for tomorrow
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_tomorrow_format else R.string.notification_lucky_number_tomorrow_format, luckyNumber.number)
else -> // LN for later
app.getString(if (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) R.string.notification_lucky_number_yours_later_format else R.string.notification_lucky_number_later_format, luckyNumber.date.formattedString, luckyNumber.number)
}
data.notifications += Notification(
title = app.getNotificationTitle(TYPE_LUCKY_NUMBER),
text = text,
type = TYPE_LUCKY_NUMBER,
profileId = profileId,
profileName = profileName,
viewId = DRAWER_ITEM_HOME,
addedDate = luckyNumber.addedDate
)
}
data.db.metadataDao().setAllNotified(profileId, true)
}}
}

View File

@ -35,6 +35,7 @@ class EdziennikNotification(val context: Context) {
private var errorCount = 0
private var criticalErrorCount = 0
var serviceClosed = false
private fun cancelPendingIntent(taskId: Int): PendingIntent {
val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN")
@ -134,6 +135,8 @@ class EdziennikNotification(val context: Context) {
}
fun post() {
if (serviceClosed)
return
notificationManager.notify(NOTIFICATION_ID, notification)
}

View File

@ -91,7 +91,6 @@ fun Data.prepareFor(loginMethods: List<LoginMethod>, loginMethodId: Int) {
possibleLoginMethods += it.loginMethodId
}
targetEndpointIds.clear()
targetLoginMethodIds.clear()
// check the login method for any dependencies

View File

@ -51,6 +51,7 @@ const val ERROR_REQUEST_FAILURE_SSL_ERROR = 63
const val ERROR_RESPONSE_EMPTY = 100
const val ERROR_LOGIN_DATA_MISSING = 101
const val ERROR_PROFILE_MISSING = 105
const val ERROR_PROFILE_ARCHIVED = 106
const val ERROR_INVALID_LOGIN_MODE = 110
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
const val ERROR_NOT_IMPLEMENTED = 112
@ -129,6 +130,8 @@ const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216
const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213
const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216
const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217
const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301
const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302

View File

@ -41,6 +41,7 @@ internal const val FEATURE_PUSH_CONFIG = 120
object Features {
private fun getAllNecessary(): List<Int> = listOf(
FEATURE_ALWAYS_NEEDED,
FEATURE_PUSH_CONFIG,
FEATURE_STUDENT_INFO,
FEATURE_STUDENT_NUMBER,
FEATURE_SCHOOL_INFO,

View File

@ -11,6 +11,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginMessages
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginSynergia
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginApi2
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginWeb
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi
import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb
@ -86,11 +87,11 @@ const val LOGIN_METHOD_MOBIDZIENNIK_API2 = 300
val mobidziennikLoginMethods = listOf(
LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_WEB, MobidziennikLoginWeb::class.java)
.withIsPossible { _, _ -> true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }/*,
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED },
LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_API2, MobidziennikLoginApi2::class.java)
.withIsPossible { _, loginStore -> loginStore.hasLoginData("email") }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }*/
.withIsPossible { profile, _ -> profile?.hasStudentData("email") == true }
.withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }
)
const val LOGIN_TYPE_VULCAN = 4

View File

@ -74,7 +74,7 @@ object Regexes {
"""id="ctl00_CzyRodzic" value="([01])" />""".toRegex()
}
val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy {
"""name="ctl00\\${'$'}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9]+)/([0-9]+)<""".toRegex(DOT_MATCHES_ALL)
"""name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9]+)/([0-9]+)<""".toRegex(DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy {
"""<select.*?name="ctl00\${"$"}dxComboUczniowie".*?</select>""".toRegex(DOT_MATCHES_ALL)

View File

@ -1,4 +1,8 @@
package pl.szczodrzynski.edziennik.data.api.task
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-16.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.App
@ -12,11 +16,13 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.template.Template
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.Vulcan
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.task.IApiTask
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTask(profileId) {
companion object {
@ -57,6 +63,10 @@ open class EdziennikTask(override val profileId: Int, val request: Any) : IApiTa
private var edziennikInterface: EdziennikInterface? = null
internal fun run(app: App, taskCallback: EdziennikCallback) {
if (profile?.archived == true) {
taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED))
return
}
edziennikInterface = when (loginStore.type) {
LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback)
LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback)

View File

@ -7,11 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB
import pl.szczodrzynski.edziennik.data.api.models.Data
import pl.szczodrzynski.edziennik.data.db.entity.EventType
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Subject
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.entity.*
/**
* Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art

View File

@ -15,12 +15,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.Edudzienn
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -41,9 +41,7 @@ class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStor
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -12,6 +12,7 @@ import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
open class EdudziennikWeb(open val data: DataEdudziennik) {
companion object {
@ -24,11 +25,11 @@ open class EdudziennikWeb(open val data: DataEdudziennik) {
val profile
get() = data.profile
fun webGet(tag: String, endpoint: String, xhr: Boolean = false, onSuccess: (text: String) -> Unit) {
fun webGet(tag: String, endpoint: String, xhr: Boolean = false, semester: Int? = null, onSuccess: (text: String) -> Unit) {
val url = "https://dziennikel.appspot.com/" + when (endpoint.endsWith('/') || endpoint.contains('?') || endpoint.isEmpty()) {
true -> endpoint
else -> "$endpoint/"
}
} + (semester?.let { "?semester=" + if(it == -1) "all" else it } ?: "")
d(tag, "Request: Edudziennik/Web - $url")
@ -40,6 +41,18 @@ open class EdudziennikWeb(open val data: DataEdudziennik) {
return
}
if (semester == null && url.contains("start")) {
profile?.also { profile ->
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
val semesterCookie = cookies.firstOrNull { it.name() == "semester" }?.value()?.toIntOrNull()
semesterCookie?.let { data.currentSemester = it }
if (semesterCookie == 2 && profile.dateSemester2Start > Date.getToday())
profile.dateSemester2Start = Date.getToday().stepForward(0, 0, -1)
}
}
try {
onSuccess(text)
} catch (e: Exception) {
@ -67,11 +80,6 @@ open class EdudziennikWeb(open val data: DataEdudziennik) {
.name("sessionid")
.value(data.webSessionId!!)
.domain("dziennikel.appspot.com")
.secure().httpOnly().build(),
Cookie.Builder()
.name("semester")
.value((data.currentSemester).toString())
.domain("dziennikel.appspot.com")
.secure().httpOnly().build()
))

View File

@ -11,9 +11,9 @@ import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPES
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Attendance
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
@ -26,7 +26,7 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
}
init { data.profile?.also { profile ->
webGet(TAG, data.studentEndpoint + "Presence") { text ->
webGet(TAG, data.studentEndpoint + "Presence", semester = -1) { text ->
val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map {
val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim())
@ -69,7 +69,7 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
id,
lesson?.displayTeacherId ?: -1,
lesson?.displaySubjectId ?: -1,
data.currentSemester,
profile.currentSemester,
name,
date,
lesson?.displayStartTime ?: startTime,
@ -77,14 +77,16 @@ class EdudziennikWebAttendance(override val data: DataEdudziennik,
)
data.attendanceList.add(attendanceObject)
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
if(type != Attendance.TYPE_PRESENT) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_ATTENDANCE,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
}
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS)

View File

@ -13,9 +13,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EXAMS
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.models.Date
@ -60,7 +60,7 @@ class EdudziennikWebExams(override val data: DataEdudziennik,
startTime,
topic,
-1,
eventType.id.toInt(),
eventType.id,
false,
-1,
subject.id,

View File

@ -13,10 +13,10 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Grade.*
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
@ -27,10 +27,18 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
private const val TAG = "EdudziennikWebGrades"
}
init { data.profile?.also { profile ->
webGet(TAG, data.studentEndpoint + "start") { text ->
val doc = Jsoup.parse(text)
private var requestSemester: Int? = null
init {
if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1
getGrades()
}
private fun getGrades() { data.profile?.also { profile ->
webGet(TAG, data.studentEndpoint + "start", semester = requestSemester) { text ->
val semester = requestSemester ?: data.currentSemester
val doc = Jsoup.parse(text)
val subjects = doc.select("#student_grades tbody").firstOrNull()?.children()
subjects?.forEach { subjectElement ->
@ -107,7 +115,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
name,
value,
if (gradeCountToAverage) weight else 0f,
data.currentSemester,
semester,
teacher.id,
subject.id
).apply {
@ -137,7 +145,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
proposed,
proposed.toFloatOrNull() ?: 0f,
0f,
data.currentSemester,
semester,
-1,
subject.id
).apply {
@ -170,7 +178,7 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
final,
final.toFloatOrNull() ?: 0f,
0f,
data.currentSemester,
semester,
-1,
subject.id
).apply {
@ -201,12 +209,17 @@ class EdudziennikWebGrades(override val data: DataEdudziennik,
TYPE_SEMESTER1_FINAL,
TYPE_SEMESTER2_FINAL
).map {
DataRemoveModel.Grades.semesterWithType(data.currentSemester, it)
DataRemoveModel.Grades.semesterWithType(semester, it)
})
}
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess()
if (profile.empty && requestSemester == 1 && data.currentSemester == 2) {
requestSemester = null
getGrades()
} else {
data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_GRADES, SYNC_ALWAYS)
onSuccess()
}
}
}}
} ?: onSuccess() }
}

View File

@ -10,9 +10,9 @@ import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_NOTE_ID
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_NOTES
import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notice
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.models.Date
@ -42,7 +42,7 @@ class EdudziennikWebNotes(override val data: DataEdudziennik,
profileId,
id,
description,
data.currentSemester,
profile.currentSemester,
Notice.TYPE_NEUTRAL,
teacher.id
)

View File

@ -13,7 +13,6 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.isNotNullNorEmpty
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit) {
companion object {
@ -62,7 +61,6 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
val cookies = data.app.cookieJar.getForDomain("dziennikel.appspot.com")
val sessionId = cookies.firstOrNull { it.name() == "sessionid" }?.value()
val semester = cookies.firstOrNull { it.name() == "semester" }?.value()?.toIntOrNull()
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID)
@ -72,14 +70,6 @@ class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit)
}
data.webSessionId = sessionId
if (data.profile != null && semester != null) {
data.currentSemester = semester
if (semester == 2 && data.profile.dateSemester2Start > Date.getToday())
data.profile.dateSemester2Start = Date.getToday().stepForward(0, 0, -1)
}
data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45 min */
onSuccess()
}

View File

@ -17,12 +17,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLo
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -43,9 +43,7 @@ class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore,
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -19,12 +19,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -45,9 +45,7 @@ class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, va
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -25,9 +25,10 @@ class LibrusApiClassrooms(override val data: DataLibrus,
val id = classroom.getLong("Id") ?: return@forEach
val name = classroom.getString("Name")?.toLowerCase(Locale.getDefault()) ?: ""
val symbol = classroom.getString("Symbol")?.toLowerCase(Locale.getDefault()) ?: ""
val nameShort = name.split(" ").onEach { it[0] }.joinToString()
val nameShort = name.fixWhiteSpaces().split(" ").onEach { it[0] }.joinToString()
val symbolParts = symbol.fixWhiteSpaces().split(" ")
val friendlyName = if (name != symbol && !name.contains(symbol) && !nameShort.contains(symbol)) {
val friendlyName = if (name != symbol && !name.contains(symbol) && !name.containsAll(symbolParts) && !nameShort.contains(symbol)) {
classroom.getString("Symbol") + " " + classroom.getString("Name")
}
else {

View File

@ -10,9 +10,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENTS
import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -34,7 +34,7 @@ class LibrusApiEvents(override val data: DataLibrus,
val id = event.getLong("Id") ?: return@forEach
val eventDate = Date.fromY_m_d(event.getString("Date"))
val topic = event.getString("Content") ?: ""
val type = event.getJsonObject("Category")?.getInt("Id") ?: -1
val type = event.getJsonObject("Category")?.getLong("Id") ?: -1
val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1
val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1
val teamId = event.getJsonObject("Class")?.getLong("Id") ?: -1

View File

@ -23,6 +23,10 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
&& webSessionKey.isNotNullNorEmpty()
&& webServerId.isNotNullNorEmpty()
fun isApi2LoginValid() = loginEmail.isNotNullNorEmpty()
&& loginId.isNotNullNorEmpty()
&& globalId.isNotNullNorEmpty()
override fun satisfyLoginMethods() {
loginMethods.clear()
if (isWebLoginValid()) {
@ -44,11 +48,6 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
get() { mLoginServerName = mLoginServerName ?: loginStore.getLoginData("serverName", null); return mLoginServerName }
set(value) { loginStore.putLoginData("serverName", value); mLoginServerName = value }
private var mLoginEmail: String? = null
var loginEmail: String?
get() { mLoginEmail = mLoginEmail ?: loginStore.getLoginData("email", null); return mLoginEmail }
set(value) { loginStore.putLoginData("email", value); mLoginEmail = value }
private var mLoginUsername: String? = null
var loginUsername: String?
get() { mLoginUsername = mLoginUsername ?: loginStore.getLoginData("username", null); return mLoginUsername }
@ -90,6 +89,48 @@ class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Da
get() { mWebSessionIdExpiryTime = mWebSessionIdExpiryTime ?: loginStore.getLoginData("sessionIDTime", 0L); return mWebSessionIdExpiryTime ?: 0L }
set(value) { loginStore.putLoginData("sessionIDTime", value); mWebSessionIdExpiryTime = value }
/* _____ _____ ___
/\ | __ \_ _| |__ \
/ \ | |__) || | ) |
/ /\ \ | ___/ | | / /
/ ____ \| | _| |_ / /_
/_/ \_\_| |_____| |___*/
/**
* A global ID (whatever it is) used in API 2
* and Firebase push from Mobidziennik.
*/
var globalId: String?
get() { mGlobalId = mGlobalId ?: profile?.getStudentData("globalId", null); return mGlobalId }
set(value) { profile?.putStudentData("globalId", value) ?: return; mGlobalId = value }
private var mGlobalId: String? = null
/**
* User's email that may or may not
* be retrieved from Web by [MobidziennikWebAccountEmail].
* Used to log in to API 2.
*/
var loginEmail: String?
get() { mLoginEmail = mLoginEmail ?: profile?.getStudentData("email", null); return mLoginEmail }
set(value) { profile?.putStudentData("email", value); mLoginEmail = value }
private var mLoginEmail: String? = null
/**
* A login ID used in the API 2.
* Looks more or less like "7063@2019@zslpoznan".
*/
var loginId: String?
get() { mLoginId = mLoginId ?: profile?.getStudentData("loginId", null); return mLoginId }
set(value) { profile?.putStudentData("loginId", value) ?: return; mLoginId = value }
private var mLoginId: String? = null
/**
* No need to explain.
*/
var ciasteczkoAutoryzacji: String?
get() { mCiasteczkoAutoryzacji = mCiasteczkoAutoryzacji ?: profile?.getStudentData("ciasteczkoAutoryzacji", null); return mCiasteczkoAutoryzacji }
set(value) { profile?.putStudentData("ciasteczkoAutoryzacji", value) ?: return; mCiasteczkoAutoryzacji = value }
private var mCiasteczkoAutoryzacji: String? = null
override fun saveData() {
super.saveData()

View File

@ -17,12 +17,12 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.Mobidzie
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -45,9 +45,7 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -22,16 +22,16 @@ const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000
val MobidziennikFeatures = listOf(
// always synced
Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf(
ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB,
ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB
ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), // TODO divide features into separate view IDs (all with API_MAIN)
// push config
/*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf(
Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf(
ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB,
ENDPOINT_MOBIDZIENNIK_API2_MAIN to LOGIN_METHOD_MOBIDZIENNIK_API2
), listOf(LOGIN_METHOD_MOBIDZIENNIK_API2)).withShouldSync { data ->
data.app.appConfig.fcmTokens[LOGIN_TYPE_MOBIDZIENNIK]?.second?.contains(data.profileId) == false
},*/
), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_API2)).withShouldSync { data ->
!data.app.config.sync.tokenMobidziennikList.contains(data.profileId)
},

View File

@ -7,6 +7,7 @@ package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.*
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api.MobidziennikApi
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api2.MobidziennikApi2Main
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebCalendar
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebGrades
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebMessagesAll
@ -44,6 +45,10 @@ class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_data)
MobidziennikApi(data, onSuccess)
}
ENDPOINT_MOBIDZIENNIK_API2_MAIN -> {
data.startProgress(R.string.edziennik_progress_endpoint_push_config)
MobidziennikApi2Main(data, onSuccess)
}
ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
MobidziennikWebMessagesInbox(data) { onSuccess() }

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-12.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api2
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_API2_MAIN
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginApi2
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getJsonObject
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils
class MobidziennikApi2Main(val data: DataMobidziennik,
val onSuccess: () -> Unit) {
companion object {
private const val TAG = "MobidziennikApi2Main"
}
val profileId
get() = data.profile?.id ?: -1
val profile
get() = data.profile
init {
Utils.d(TAG, "Request: Mobidziennik/Api2/Main - https://${data.loginServerName}.mobidziennik.pl/api2/logowanie")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
json.getJsonObject("error")?.let {
val text = it.getString("type") ?: it.getString("message")
when (text) {
"LOGIN_ERROR" -> ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN
// TODO other error types
else -> ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
val user = json.getJsonObject("user")
data.ciasteczkoAutoryzacji = user.getString("auth_key")
// sync always: this endpoint has .shouldSync set
data.setSyncNext(ENDPOINT_MOBIDZIENNIK_API2_MAIN, SYNC_ALWAYS)
data.app.config.sync.tokenMobidziennikList =
data.app.config.sync.tokenMobidziennikList + profileId
onSuccess()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("https://${data.loginServerName}.mobidziennik.pl/api2/logowanie")
.userAgent(MOBIDZIENNIK_USER_AGENT)
.contentType("application/x-www-form-urlencoded; charset=UTF-8")
.addParameter("login", data.loginId)
.addParameter("email", data.loginEmail)
.addParameter("haslo", data.loginPassword)
.addParameter("device", MobidziennikLoginApi2.getDevice(data.app).toString())
.apply {
data.ciasteczkoAutoryzacji?.let { addParameter("ciasteczko_autoryzacji", it) }
}
.post()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -9,16 +9,16 @@ import pl.szczodrzynski.edziennik.data.api.Regexes
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.utils.Utils.crc16
import pl.szczodrzynski.edziennik.utils.models.Date
import java.util.*
class MobidziennikWebCalendar(override val data: DataMobidziennik,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebCalendar"
}

View File

@ -12,9 +12,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
@ -25,7 +25,7 @@ import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikWebGetMessage(
override val data: DataMobidziennik,
private val message: MessageFull,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebGetMessage"
}

View File

@ -11,9 +11,9 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidzienn
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_GRADES
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Grade
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.fixWhiteSpaces
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
@ -21,7 +21,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikWebGrades(override val data: DataMobidziennik,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebGrades"
}

View File

@ -19,7 +19,7 @@ import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
class MobidziennikWebMessagesAll(override val data: DataMobidziennik,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebMessagesAll"
}

View File

@ -8,16 +8,16 @@ import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.fixName
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.models.Date
class MobidziennikWebMessagesInbox(override val data: DataMobidziennik,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebMessagesInbox"
}

View File

@ -10,7 +10,7 @@ import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.Mobidzien
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
class MobidziennikWebNotices(override val data: DataMobidziennik,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebNotices"
}

View File

@ -52,7 +52,7 @@ class MobidziennikLogin(val data: DataMobidziennik, val onSuccess: () -> Unit) {
}
LOGIN_METHOD_MOBIDZIENNIK_API2 -> {
data.startProgress(R.string.edziennik_progress_login_mobidziennik_api2)
//MobidziennikLoginApi2(data) { onSuccess(loginMethodId) }
MobidziennikLoginApi2(data) { onSuccess(loginMethodId) }
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-12.
*/
package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login
import android.os.Build
import com.google.gson.JsonObject
import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.api.*
import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.utils.Utils
class MobidziennikLoginApi2(val data: DataMobidziennik, val onSuccess: () -> Unit) {
companion object {
private const val TAG = "MobidziennikLoginApi2"
fun getDevice(app: App) = JsonObject(
"available" to true,
"platform" to "Android",
"version" to Build.VERSION.RELEASE,
"uuid" to app.deviceId,
"cordova" to "7.1.2",
"model" to "${Build.MANUFACTURER} ${Build.MODEL}",
"manufacturer" to "Aplikacja Szkolny.eu",
"isVirtual" to false,
"serial" to try { System.getProperty("ro.serialno") ?: System.getProperty("ro.boot.serialno") } catch (_: Exception) { Build.UNKNOWN },
"appVersion" to "10.6, 2020.01.09-12.15.53",
"pushRegistrationId" to app.config.sync.tokenMobidziennik
)
}
init { run {
if (data.isApi2LoginValid()) {
onSuccess()
}
else {
if (data.loginServerName.isNotNullNorEmpty() && data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) {
loginWithCredentials()
}
else {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING))
}
}
}}
private fun loginWithCredentials() {
Utils.d(TAG, "Request: Mobidziennik/Login/Api2 - https://mobidziennik.pl/logowanie")
val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) {
if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response))
return
}
json.getJsonObject("error")?.let {
val text = it.getString("type") ?: it.getString("message")
when (text) {
"LOGIN_ERROR" -> ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN
// TODO other error types
else -> ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER
}.let { errorCode ->
data.error(ApiError(TAG, errorCode)
.withApiResponse(text)
.withResponse(response))
return
}
}
data.loginEmail = json.getString("email")
data.globalId = json.getString("id_global")
data.loginId = json.getString("login")
onSuccess()
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder()
.url("https://mobidziennik.pl/logowanie")
.userAgent(MOBIDZIENNIK_USER_AGENT)
.contentType("application/x-www-form-urlencoded; charset=UTF-8")
.addParameter("api2", true)
.addParameter("email", data.loginEmail)
.addParameter("haslo", data.loginPassword)
.addParameter("device", getDevice(data.app).toString())
.post()
.callback(callback)
.build()
.enqueue()
}
}

View File

@ -15,12 +15,12 @@ import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.prepare
import pl.szczodrzynski.edziennik.data.api.templateLoginMethods
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Template(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -40,9 +40,7 @@ class Template(val app: App, val profile: Profile?, val loginStore: LoginStore,
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -18,12 +18,12 @@ import pl.szczodrzynski.edziennik.data.api.models.ApiError
import pl.szczodrzynski.edziennik.data.api.prepare
import pl.szczodrzynski.edziennik.data.api.prepareFor
import pl.szczodrzynski.edziennik.data.api.vulcanLoginMethods
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.entity.LoginStore
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.utils.Utils.d
class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface {
@ -44,9 +44,7 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
private fun completed() {
data.saveData()
data.notify {
callback.onCompleted()
}
callback.onCompleted()
}
/* _______ _ _ _ _ _

View File

@ -4,13 +4,15 @@
package pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.api
import org.greenrobot.eventbus.EventBus
import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent
import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.full.MessageFull
class VulcanApiMessagesChangeStatus(
override val data: DataVulcan,
@ -22,40 +24,42 @@ class VulcanApiMessagesChangeStatus(
}
init {
data.profile?.also { profile ->
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS, parameters = mapOf(
"WiadomoscId" to messageObject.id,
"FolderWiadomosci" to "Odebrane",
"Status" to "Widoczna",
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)) { _, _ ->
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS, parameters = mapOf(
"WiadomoscId" to messageObject.id,
"FolderWiadomosci" to "Odebrane",
"Status" to "Widoczna",
"LoginId" to data.studentLoginId,
"IdUczen" to data.studentId
)) { _, _ ->
if (!messageObject.seen) {
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
messageObject.id,
true,
true,
messageObject.addedDate
))
}
if (!messageObject.seen) {
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
messageObject.id,
true,
true,
messageObject.addedDate
))
if (messageObject.type != TYPE_SENT) {
val messageRecipientObject = MessageRecipient(
profileId,
-1,
-1,
System.currentTimeMillis(),
messageObject.id
)
data.messageRecipientList.add(messageRecipientObject)
}
onSuccess()
messageObject.seen = true
}
if (messageObject.type != TYPE_SENT) {
val messageRecipientObject = MessageRecipient(
profileId,
-1,
-1,
System.currentTimeMillis(),
messageObject.id
)
data.messageRecipientList.add(messageRecipientObject)
}
EventBus.getDefault().postSticky(MessageGetEvent(messageObject))
onSuccess()
}
}
}

View File

@ -9,12 +9,8 @@ import pl.szczodrzynski.edziennik.data.api.VULCAN_API_ENDPOINT_MESSAGES_RECEIVED
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.ENDPOINT_VULCAN_API_MESSAGES_INBOX
import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.entity.Message
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Teacher
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.text.replace
@ -88,7 +84,7 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () ->
data.messageIgnoreList.add(messageObject)
data.messageRecipientList.add(messageRecipientObject)
data.metadataList.add(Metadata(
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
id,

View File

@ -99,7 +99,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
)
data.messageIgnoreList.add(messageObject)
data.metadataList.add(Metadata(
data.setSeenMetadataList.add(Metadata(
profileId,
Metadata.TYPE_MESSAGE,
id,

View File

@ -0,0 +1,7 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.data.api.events
class ProfileListEmptyEvent

View File

@ -93,7 +93,7 @@ class ApiError(val tag: String, var errorCode: Int) {
stackTrace = throwable?.stackTraceString,
request = requestString,
response = responseString,
apiResponse = apiResponse,
apiResponse = apiResponse ?: response?.parserErrorBody,
isCritical = isCritical
)
}

View File

@ -194,7 +194,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
if (profile == null)
return // return on first login
profile.empty = false
profile.userCode = generateUserCode()
db.profileDao().add(profile)
@ -294,21 +293,6 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList)
}
fun notify(onSuccess: () -> Unit) {
if (profile == null) {
onSuccess()
return
}
try {
DataNotifications(this)
db.notificationDao().addAll(notifications)
onSuccess()
} catch (e: Exception) {
error(ApiError(TAG, EXCEPTION_NOTIFY)
.withThrowable(e))
}
}
fun setSyncNext(endpointId: Int, syncIn: Long? = null, viewId: Int? = null, syncAt: Long? = null) {
EndpointTimer(profile?.id
?: -1, endpointId).apply {
@ -366,8 +350,8 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
apiError.errorCode = when (apiError.throwable) {
is UnknownHostException -> ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND
is SSLException -> ERROR_REQUEST_FAILURE_SSL_ERROR
is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET
is SocketTimeoutException -> ERROR_REQUEST_FAILURE_TIMEOUT
is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET
else ->
if (apiError.errorCode == ERROR_REQUEST_FAILURE)
when (apiError.response?.code()) {

View File

@ -47,11 +47,11 @@ open class DataRemoveModel {
}
}
class Events(private val type: Int?, private val exceptType: Int?, private val exceptTypes: List<Int>?) : DataRemoveModel() {
class Events(private val type: Long?, private val exceptType: Long?, private val exceptTypes: List<Long>?) : DataRemoveModel() {
companion object {
fun futureExceptType(exceptType: Int) = Events(null, exceptType, null)
fun futureExceptTypes(exceptTypes: List<Int>) = Events(null, null, exceptTypes)
fun futureWithType(type: Int) = Events(type, null, null)
fun futureExceptType(exceptType: Long) = Events(null, exceptType, null)
fun futureExceptTypes(exceptTypes: List<Long>) = Events(null, null, exceptTypes)
fun futureWithType(type: Long) = Events(type, null, null)
}
fun commit(profileId: Int, dao: EventDao) {

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-12-13
*/
package pl.szczodrzynski.edziennik.data.api.szkolny
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Profile
class Szkolny(val app: App, val callback: EdziennikCallback) {
private val api = SzkolnyApi(app)
fun sync(profileList: List<Profile>) {
val profiles = profileList.filter { it.registration == Profile.REGISTRATION_ENABLED }
if (profiles.isNotEmpty()) {
val events = api.getEvents(profiles)
if (events.isNotEmpty()) {
app.db.eventDao().addAll(events)
app.db.metadataDao().addAllIgnore(events.map { event ->
Metadata(
event.profileId,
Metadata.TYPE_EVENT,
event.id,
event.seen,
event.notified,
event.addedDate
)
})
}
}
completed()
}
/*fun shareEvent(event: EventFull) {
api.shareEvent(event)
completed()
}
fun unshareEvent(event: EventFull) {
api.unshareEvent(event)
completed()
}*/
private fun completed() {
callback.onCompleted()
}
}

View File

@ -12,15 +12,14 @@ import pl.szczodrzynski.edziennik.BuildConfig
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.DateAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.adapter.TimeAdapter
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.data.db.full.EventFull
import pl.szczodrzynski.edziennik.md5
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -56,9 +55,8 @@ class SzkolnyApi(val app: App) {
api = retrofit.create()
}
fun getEvents(profiles: List<Profile>): List<EventFull> {
fun getEvents(profiles: List<Profile>, notifications: List<Notification>, blacklistedIds: List<Long>): List<EventFull> {
val teams = app.db.teamDao().allNow
val notifications = app.db.notificationDao().getNotPostedNow()
val response = api.serverSync(ServerSyncRequest(
deviceId = app.deviceId,
@ -88,8 +86,8 @@ class SzkolnyApi(val app: App) {
val config = app.config.getFor(profile.id)
val user = ServerSyncRequest.User(
profile.userCode,
profile.studentNameLong ?: "",
profile.studentNameShort ?: "",
profile.studentNameLong,
profile.studentNameShort,
profile.loginStoreType,
teams.filter { it.profileId == profile.id }.map { it.code }
)
@ -108,17 +106,19 @@ class SzkolnyApi(val app: App) {
val events = mutableListOf<EventFull>()
response?.data?.events?.forEach { event ->
teams.filter { it.code == event.teamCode }.forEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId }
if (event.id in blacklistedIds)
return@forEach
teams.filter { it.code == event.teamCode }.onEach { team ->
val profile = profiles.firstOrNull { it.id == team.profileId } ?: return@onEach
events.add(event.apply {
events.add(EventFull(event).apply {
profileId = team.profileId
teamId = team.id
addedManually = true
seen = profile?.empty ?: false
notified = profile?.empty ?: false
seen = profile.empty
notified = profile.empty
if (profile?.userCode == event.sharedBy) sharedBy = "self"
if (profile.userCode == event.sharedBy) sharedBy = "self"
})
}
}
@ -190,7 +190,19 @@ class SzkolnyApi(val app: App) {
fun errorReport(errors: List<ErrorReportRequest.Error>): ApiResponse<Nothing>? {
return api.errorReport(ErrorReportRequest(
deviceId = app.deviceId,
appVersion = BuildConfig.VERSION_NAME,
errors = errors
)).execute().body()
}
fun unregisterAppUser(userCode: String): ApiResponse<Nothing>? {
return api.appUser(AppUserRequest(
deviceId = app.deviceId,
userCode = userCode
)).execute().body()
}
fun getUpdate(channel: String): ApiResponse<List<Update>>? {
return api.updates(channel).execute().body()
}
}

View File

@ -4,16 +4,16 @@
package pl.szczodrzynski.edziennik.data.api.szkolny
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.EventShareRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.ServerSyncRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.WebPushRequest
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ServerSyncResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
interface SzkolnyService {
@ -28,4 +28,10 @@ interface SzkolnyService {
@POST("errorReport")
fun errorReport(@Body request: ErrorReportRequest): Call<ApiResponse<Nothing>>
@POST("appUser")
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Nothing>>
@GET("updates/app")
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>
}

View File

@ -46,6 +46,6 @@ object Signing {
/*fun provideKey(param1: String, param2: Long): ByteArray {*/
fun pleaseStopRightNow(param1: String, param2: Long): ByteArray {
return "$param1.MTIzNDU2Nzg5MDJ9J602xT===.$param2".sha256()
return "$param1.MTIzNDU2Nzg5MDzsOS3K08===.$param2".sha256()
}
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.data.api.szkolny.request
data class AppUserRequest(
val action: String = "unregister",
val deviceId: String,
val userCode: String
)

View File

@ -6,6 +6,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny.request
data class ErrorReportRequest(
val deviceId: String,
val appVersion: String,
val errors: List<Error>
) {
data class Error(

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-18.
*/
package pl.szczodrzynski.edziennik.data.api.szkolny.response
data class Update(
val versionCode: Int,
val versionName: String,
val releaseDate: String,
val releaseNotes: String?,
val releaseType: String,
val isOnGooglePlay: Boolean,
val downloadUrl: String?
)

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-17.
*/
package pl.szczodrzynski.edziennik.data.api.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
class AppSync(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>, val api: SzkolnyApi) {
companion object {
private const val TAG = "AppSync"
}
/**
* Run the app sync, sending all pending notifications
* and retrieving a list of shared events.
*
* Events are automatically saved to app database,
* along with corresponding metadata objects.
*
* @return a number of events inserted to DB, possibly needing a notification
*/
fun run(): Int {
val profiles = profiles.filter { it.registration == Profile.REGISTRATION_ENABLED && !it.archived }
if (profiles.isNotEmpty()) {
val blacklistedIds = app.db.eventDao().blacklistedIds;
val events = api.getEvents(profiles, notifications, blacklistedIds)
if (events.isNotEmpty()) {
app.db.metadataDao().addAllIgnore(events.map { event ->
Metadata(
event.profileId,
Metadata.TYPE_EVENT,
event.id,
event.seen,
event.notified,
event.addedDate
)
})
return app.db.eventDao().addAll(events).size
}
}
return 0;
}
}

View File

@ -39,4 +39,18 @@ abstract class IApiTask(open val profileId: Int) {
override fun toString(): String {
return "IApiTask(profileId=$profileId, taskId=$taskId, profile=$profile, taskName=$taskName)"
}
companion object {
fun enqueueAll(context: Context, tasks: List<IApiTask>) {
Intent(context, ApiService::class.java).let {
if (SDK_INT >= O)
context.startForegroundService(it)
else
context.startService(it)
}
tasks.forEach {
EventBus.getDefault().postSticky(it)
}
}
}
}

View File

@ -0,0 +1,277 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-16.
*/
package pl.szczodrzynski.edziennik.data.api.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Date
class Notifications(val app: App, val notifications: MutableList<Notification>, val profiles: List<Profile>) {
companion object {
private const val TAG = "Notifications"
}
private val today by lazy { Date.getToday() }
private val todayValue by lazy { today.value }
/**
* Create a [Notification] from every possible
* data type. Notifications are posted whenever
* the object's metadata `notified` property is
* set to false.
*/
fun run() {
timetableNotifications()
eventNotifications()
gradeNotifications()
behaviourNotifications()
attendanceNotifications()
announcementNotifications()
messageNotifications()
luckyNumberNotifications()
}
private fun timetableNotifications() {
for (lesson in app.db.timetableDao().getNotNotifiedNow()) {
val text = app.getString(
R.string.notification_lesson_change_format,
lesson.getDisplayChangeType(app),
if (lesson.displayDate == null) "" else lesson.displayDate!!.formattedString,
lesson.changeSubjectName
)
notifications += Notification(
id = Notification.buildId(lesson.profileId, Notification.TYPE_TIMETABLE_LESSON_CHANGE, lesson.id),
title = app.getNotificationTitle(Notification.TYPE_TIMETABLE_LESSON_CHANGE),
text = text,
type = Notification.TYPE_TIMETABLE_LESSON_CHANGE,
profileId = lesson.profileId,
profileName = profiles.singleOrNull { it.id == lesson.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_TIMETABLE,
addedDate = lesson.addedDate
).addExtra("timetableDate", lesson.displayDate?.stringY_m_d ?: "")
}
}
private fun eventNotifications() {
for (event in app.db.eventDao().notNotifiedNow) {
val text = if (event.type == Event.TYPE_HOMEWORK)
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_homework_no_subject_format
else
R.string.notification_homework_format,
event.subjectLongName,
event.eventDate.formattedString
)
else
app.getString(
if (event.subjectLongName.isNullOrEmpty())
R.string.notification_event_no_subject_format
else
R.string.notification_event_format,
event.typeName,
event.eventDate.formattedString,
event.subjectLongName
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
}
fun sharedEventNotifications() {
for (event in app.db.eventDao().notNotifiedNow.filter { it.sharedBy != null }) {
val text = app.getString(
R.string.notification_shared_event_format,
event.sharedByName,
event.typeName ?: "wydarzenie",
event.eventDate.formattedString,
event.topic
)
val type = if (event.type == Event.TYPE_HOMEWORK) Notification.TYPE_NEW_HOMEWORK else Notification.TYPE_NEW_EVENT
notifications += Notification(
id = Notification.buildId(event.profileId, type, event.id),
title = app.getNotificationTitle(type),
text = text,
type = type,
profileId = event.profileId,
profileName = profiles.singleOrNull { it.id == event.profileId }?.name,
viewId = if (event.type == Event.TYPE_HOMEWORK) MainActivity.DRAWER_ITEM_HOMEWORK else MainActivity.DRAWER_ITEM_AGENDA,
addedDate = event.addedDate
).addExtra("eventId", event.id).addExtra("eventDate", event.eventDate.value.toLong())
}
}
private fun gradeNotifications() {
for (grade in app.db.gradeDao().notNotifiedNow) {
val gradeName = when (grade.type) {
Grade.TYPE_SEMESTER1_PROPOSED, Grade.TYPE_SEMESTER2_PROPOSED -> app.getString(R.string.grade_semester_proposed_format_2, grade.name)
Grade.TYPE_SEMESTER1_FINAL, Grade.TYPE_SEMESTER2_FINAL -> app.getString(R.string.grade_semester_final_format_2, grade.name)
Grade.TYPE_YEAR_PROPOSED -> app.getString(R.string.grade_year_proposed_format_2, grade.name)
Grade.TYPE_YEAR_FINAL -> app.getString(R.string.grade_year_final_format_2, grade.name)
else -> grade.name
}
val text = app.getString(
R.string.notification_grade_format,
gradeName,
grade.subjectLongName
)
notifications += Notification(
id = Notification.buildId(grade.profileId, Notification.TYPE_NEW_GRADE, grade.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_GRADE),
text = text,
type = Notification.TYPE_NEW_GRADE,
profileId = grade.profileId,
profileName = profiles.singleOrNull { it.id == grade.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_GRADES,
addedDate = grade.addedDate
).addExtra("gradeId", grade.id).addExtra("gradesSubjectId", grade.subjectId)
}
}
private fun behaviourNotifications() {
for (notice in app.db.noticeDao().notNotifiedNow) {
val noticeTypeStr = when (notice.type) {
Notice.TYPE_POSITIVE -> app.getString(R.string.notification_notice_praise)
Notice.TYPE_NEGATIVE -> app.getString(R.string.notification_notice_warning)
else -> app.getString(R.string.notification_notice_new)
}
val text = app.getString(
R.string.notification_notice_format,
noticeTypeStr,
notice.teacherFullName,
Date.fromMillis(notice.addedDate).formattedString
)
notifications += Notification(
id = Notification.buildId(notice.profileId, Notification.TYPE_NEW_NOTICE, notice.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_NOTICE),
text = text,
type = Notification.TYPE_NEW_NOTICE,
profileId = notice.profileId,
profileName = profiles.singleOrNull { it.id == notice.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_BEHAVIOUR,
addedDate = notice.addedDate
).addExtra("noticeId", notice.id)
}
}
private fun attendanceNotifications() {
for (attendance in app.db.attendanceDao().notNotifiedNow) {
val attendanceTypeStr = when (attendance.type) {
Attendance.TYPE_ABSENT -> app.getString(R.string.notification_absence)
Attendance.TYPE_ABSENT_EXCUSED -> app.getString(R.string.notification_absence_excused)
Attendance.TYPE_BELATED -> app.getString(R.string.notification_belated)
Attendance.TYPE_BELATED_EXCUSED -> app.getString(R.string.notification_belated_excused)
Attendance.TYPE_RELEASED -> app.getString(R.string.notification_release)
Attendance.TYPE_DAY_FREE -> app.getString(R.string.notification_day_free)
else -> app.getString(R.string.notification_type_attendance)
}
val text = app.getString(
if (attendance.subjectLongName.isNullOrEmpty())
R.string.notification_attendance_no_lesson_format
else
R.string.notification_attendance_format,
attendanceTypeStr,
attendance.subjectLongName,
attendance.lessonDate.formattedString
)
notifications += Notification(
id = Notification.buildId(attendance.profileId, Notification.TYPE_NEW_ATTENDANCE, attendance.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ATTENDANCE),
text = text,
type = Notification.TYPE_NEW_ATTENDANCE,
profileId = attendance.profileId,
profileName = profiles.singleOrNull { it.id == attendance.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_ATTENDANCE,
addedDate = attendance.addedDate
).addExtra("attendanceId", attendance.id).addExtra("attendanceSubjectId", attendance.subjectId)
}
}
private fun announcementNotifications() {
for (announcement in app.db.announcementDao().notNotifiedNow) {
val text = app.getString(
R.string.notification_announcement_format,
announcement.teacherFullName,
announcement.subject
)
notifications += Notification(
id = Notification.buildId(announcement.profileId, Notification.TYPE_NEW_ANNOUNCEMENT, announcement.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_ANNOUNCEMENT),
text = text,
type = Notification.TYPE_NEW_ANNOUNCEMENT,
profileId = announcement.profileId,
profileName = profiles.singleOrNull { it.id == announcement.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_ANNOUNCEMENTS,
addedDate = announcement.addedDate
).addExtra("announcementId", announcement.id)
}
}
private fun messageNotifications() {
for (message in app.db.messageDao().receivedNotNotifiedNow) {
val text = app.getString(
R.string.notification_message_format,
message.senderFullName,
message.subject
)
notifications += Notification(
id = Notification.buildId(message.profileId, Notification.TYPE_NEW_MESSAGE, message.id),
title = app.getNotificationTitle(Notification.TYPE_NEW_MESSAGE),
text = text,
type = Notification.TYPE_NEW_MESSAGE,
profileId = message.profileId,
profileName = profiles.singleOrNull { it.id == message.profileId }?.name,
viewId = MainActivity.DRAWER_ITEM_MESSAGES,
addedDate = message.addedDate
).addExtra("messageType", Message.TYPE_RECEIVED.toLong()).addExtra("messageId", message.id)
}
}
private fun luckyNumberNotifications() {
val luckyNumbers = app.db.luckyNumberDao().notNotifiedNow
luckyNumbers?.removeAll { it.date < today }
luckyNumbers?.forEach { luckyNumber ->
val profile = profiles.singleOrNull { it.id == luckyNumber.profileId } ?: return@forEach
val text = when (profile.studentNumber != -1 && profile.studentNumber == luckyNumber.number) {
true -> when (luckyNumber.date.value) {
todayValue -> R.string.notification_lucky_number_yours_format
todayValue + 1 -> R.string.notification_lucky_number_yours_tomorrow_format
else -> R.string.notification_lucky_number_yours_later_format
}
else -> when (luckyNumber.date.value) {
todayValue -> R.string.notification_lucky_number_format
todayValue + 1 -> R.string.notification_lucky_number_tomorrow_format
else -> R.string.notification_lucky_number_later_format
}
}
notifications += Notification(
id = Notification.buildId(luckyNumber.profileId, Notification.TYPE_LUCKY_NUMBER, luckyNumber.date.value.toLong()),
title = app.getNotificationTitle(Notification.TYPE_LUCKY_NUMBER),
text = app.getString(text, luckyNumber.date.formattedString, luckyNumber.number),
type = Notification.TYPE_LUCKY_NUMBER,
profileId = luckyNumber.profileId,
profileName = profile.name,
viewId = MainActivity.DRAWER_ITEM_HOME,
addedDate = luckyNumber.addedDate
)
}
}
}

View File

@ -1,90 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.task
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.Notifier.ID_NOTIFICATIONS
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.getNotificationTitle
import pl.szczodrzynski.edziennik.utils.models.Notification
import kotlin.math.min
class NotifyTask : IApiTask(-1) {
override fun prepare(app: App) {
taskName = app.getString(R.string.edziennik_notification_api_notify_title)
}
override fun cancel() {
}
fun run(app: App, taskCallback: EdziennikCallback) {
val list = app.db.notificationDao().getNotPostedNow()
val notificationList = list.subList(0, min(10, list.size))
val unreadCount = list.size
for (notification in notificationList) {
val intent = Intent(app, MainActivity::class.java)
notification.fillIntent(intent)
val pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0)
val notificationBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
// title, text, type, date
.setContentTitle(notification.profileName)
.setContentText(notification.text)
.setSubText(app.getNotificationTitle(notification.type))
.setWhen(notification.addedDate)
.setTicker(app.getString(R.string.notification_ticker_format, Notification.stringType(app, notification.type)))
// icon, color, lights, priority
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
// channel, group, style
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setStyle(NotificationCompat.BigTextStyle().bigText(notification.text))
// intent, auto cancel
.setContentIntent(pendingIntent)
.setAutoCancel(true)
if (!app.notifier.shouldBeQuiet()) {
notificationBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(notification.id, notificationBuilder.build())
}
if (notificationList.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val intent = Intent(app, MainActivity::class.java)
intent.action = "android.intent.action.MAIN"
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS)
val pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
intent, 0)
val groupBuilder = NotificationCompat.Builder(app, app.notifier.notificationGroup)
.setSmallIcon(R.drawable.ic_notification)
.setColor(app.notifier.notificationColor)
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
.setGroupSummary(true)
.setAutoCancel(true)
.setChannelId(app.notifier.notificationGroup)
.setGroup(app.notifier.notificationGroup)
.setLights(-0xff0001, 2000, 2000)
.setPriority(app.notifier.notificationPriority)
.setContentIntent(pendingIntent)
.setStyle(NotificationCompat.BigTextStyle())
if (!app.notifier.shouldBeQuiet()) {
groupBuilder.setDefaults(app.notifier.notificationDefaults)
}
app.notifier.notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build())
}
app.db.notificationDao().setAllPosted()
taskCallback.onCompleted()
}
}

View File

@ -0,0 +1,172 @@
package pl.szczodrzynski.edziennik.data.api.task
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.os.Build
import android.util.SparseIntArray
import androidx.core.app.NotificationCompat
import androidx.core.util.forEach
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification as AppNotification
class PostNotifications(val app: App, nList: List<AppNotification>) {
companion object {
private const val TAG = "PostNotifications"
}
/*public boolean shouldBeQuiet() {
long now = Time.getNow().getInMillis();
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");
}
if (start > now) {
now += 1000 * 60 * 60 * 24;
//Log.d(TAG, "Now is smaller");
}
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
return start > 0 && now >= start && now <= end;
}*/
fun shouldBeQuiet() = false
private fun buildSummaryText(summaryCounts: SparseIntArray): CharSequence {
val summaryTexts = mutableListOf<String>()
summaryCounts.forEach { key, value ->
if (value <= 0)
return@forEach
val pluralRes = when (key) {
AppNotification.TYPE_TIMETABLE_LESSON_CHANGE -> R.plurals.notification_new_timetable_change_format
AppNotification.TYPE_NEW_GRADE -> R.plurals.notification_new_grades_format
AppNotification.TYPE_NEW_EVENT -> R.plurals.notification_new_events_format
AppNotification.TYPE_NEW_HOMEWORK -> R.plurals.notification_new_homework_format
AppNotification.TYPE_NEW_SHARED_EVENT -> R.plurals.notification_new_shared_events_format
AppNotification.TYPE_NEW_SHARED_HOMEWORK -> R.plurals.notification_new_shared_homework_format
AppNotification.TYPE_NEW_MESSAGE -> R.plurals.notification_new_messages_format
AppNotification.TYPE_NEW_NOTICE -> R.plurals.notification_new_notices_format
AppNotification.TYPE_NEW_ATTENDANCE -> R.plurals.notification_new_attendance_format
AppNotification.TYPE_LUCKY_NUMBER -> R.plurals.notification_new_lucky_number_format
AppNotification.TYPE_NEW_ANNOUNCEMENT -> R.plurals.notification_new_announcements_format
else -> R.plurals.notification_other_format
}
summaryTexts += app.resources.getQuantityString(pluralRes, value, value)
}
return summaryTexts.concat(", ")
}
init { run {
val count = nList.size
if (count == 0)
return@run
val notificationManager = app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val summaryCounts = SparseIntArray()
val newNotificationsText = app.resources.getQuantityString(R.plurals.notification_count_format, count, count)
val newNotificationsShortText = app.resources.getQuantityString(R.plurals.notification_count_short_format, count, count)
val intent = Intent(
app,
MainActivity::class.java,
"fragmentId" to MainActivity.DRAWER_ITEM_NOTIFICATIONS
)
val summaryIntent = PendingIntent.getActivity(app, app.notifications.dataId, intent, PendingIntent.FLAG_ONE_SHOT)
// On Nougat or newer - show maximum 8 notifications
// On Marshmallow or older - show maximum 4 notifications
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && count > 4 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && count > 8) {
val summaryList = mutableListOf<CharSequence>()
nList.forEach {
summaryCounts[it.type]++
summaryList += listOf(
it.profileName.asBoldSpannable(),
it.text
).concat(": ")
}
// Create a summary to show *instead* of notifications
val combined = NotificationCompat.Builder(app, app.notifications.dataKey)
.setContentTitle(app.getString(R.string.app_name))
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.InboxStyle()
.also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
it.setBigContentTitle(app.getString(R.string.app_name))
it.setSummaryText(newNotificationsShortText)
}
else {
it.setBigContentTitle(newNotificationsText)
it.setSummaryText(app.getString(R.string.notification_click_to_see_all))
}
summaryList.forEach { line ->
it.addLine(line)
}
})
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.dataKey)
.setContentIntent(summaryIntent)
.setAutoCancel(true)
.build()
notificationManager.notify(System.currentTimeMillis().toInt(), combined)
}
else {
// Less than 8 notifications
val notifications = nList.map {
summaryCounts[it.type]++
NotificationCompat.Builder(app, app.notifications.dataKey)
.setContentTitle(it.profileName ?: app.getString(R.string.app_name))
.setContentText(it.text)
.setSubText(if (it.type == TYPE_SERVER_MESSAGE) null else it.title)
.setTicker("${it.profileName}: ${it.title}")
.setSmallIcon(R.drawable.ic_notification)
.setStyle(NotificationCompat.BigTextStyle()
.bigText(it.text))
.setWhen(it.addedDate)
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.dataKey)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
.setContentIntent(it.getPendingIntent(app))
.setAutoCancel(true)
.build()
}
val time = System.currentTimeMillis()
notificationManager.apply {
notifications.forEachIndexed { index, it ->
notificationManager.notify((time + index).toInt(), it)
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val summary = NotificationCompat.Builder(app, app.notifications.dataKey)
.setContentTitle(newNotificationsText)
.setContentText(buildSummaryText(summaryCounts))
.setTicker(newNotificationsText)
.setSmallIcon(R.drawable.ic_notification)
.setColor(0xff2196f3.toInt())
.setLights(0xff2196f3.toInt(), 2000, 2000)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setGroup(app.notifications.dataKey)
.setGroupSummary(true)
.setContentIntent(summaryIntent)
.setAutoCancel(true)
.build()
notificationManager.notify(app.notifications.dataId, summary)
}
}
}}
}

View File

@ -7,39 +7,39 @@ package pl.szczodrzynski.edziennik.data.api.task
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.data.api.szkolny.Szkolny
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.db.entity.Notification
import pl.szczodrzynski.edziennik.data.db.entity.Profile
import pl.szczodrzynski.edziennik.utils.Utils.d
class SzkolnyTask(val request: Any) : IApiTask(-1) {
class SzkolnyTask(val app: App, val syncingProfiles: List<Profile>) : IApiTask(-1) {
companion object {
private const val TAG = "SzkolnyTask"
fun sync(profiles: List<Profile>) = SzkolnyTask(SyncRequest(profiles))
/*fun shareEvent(event: EventFull) = SzkolnyTask(ShareEventRequest(event))
fun unshareEvent(event: EventFull) = SzkolnyTask(UnshareEventRequest(event))*/
}
private val api by lazy { SzkolnyApi(app) }
private val profiles by lazy { app.db.profileDao().allNow }
override fun prepare(app: App) { taskName = app.getString(R.string.edziennik_szkolny_creating_notifications) }
override fun cancel() {}
private lateinit var szkolny: Szkolny
private val notificationList = mutableListOf<Notification>()
override fun prepare(app: App) {
taskName = app.getString(R.string.edziennik_szkolny_api_sync_title)
internal fun run(taskCallback: EdziennikCallback) {
val startTime = System.currentTimeMillis()
val notifications = Notifications(app, notificationList, profiles)
// create all e-register data notifications
notifications.run()
// send notifications to web push, get shared events
AppSync(app, notificationList, profiles, api).run()
// create notifications for shared events (not present before app sync)
notifications.sharedEventNotifications()
d(TAG, "Created ${notificationList.count()} notifications.")
// update the database
app.db.metadataDao().setAllNotified(true)
app.db.notificationDao().addAll(notificationList)
app.db.profileDao().setAllNotEmpty()
// post all notifications
PostNotifications(app, notificationList)
d(TAG, "SzkolnyTask: finished in ${System.currentTimeMillis()-startTime} ms.")
taskCallback.onCompleted()
}
override fun cancel() {
// TODO
}
internal fun run(app: App, taskCallback: EdziennikCallback) {
szkolny = Szkolny(app, taskCallback)
when (request) {
is SyncRequest -> szkolny.sync(request.profiles)
/*is ShareEventRequest -> szkolny.shareEvent(request.event)
is UnshareEventRequest -> szkolny.unshareEvent(request.event)*/
}
}
data class SyncRequest(val profiles: List<Profile>)
/*data class ShareEventRequest(val event: EventFull)
data class UnshareEventRequest(val event: EventFull)*/
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,930 @@
package pl.szczodrzynski.edziennik.data.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
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.crc32
import pl.szczodrzynski.edziennik.data.db.converter.*
import pl.szczodrzynski.edziennik.data.db.dao.*
import pl.szczodrzynski.edziennik.data.db.entity.*
import pl.szczodrzynski.edziennik.utils.models.Date
@Database(entities = [
Grade::class,
Teacher::class,
TeacherAbsence::class,
TeacherAbsenceType::class,
Subject::class,
Notice::class,
Team::class,
Attendance::class,
Event::class,
EventType::class,
LoginStore::class,
Profile::class,
LuckyNumber::class,
Announcement::class,
GradeCategory::class,
FeedbackMessage::class,
Message::class,
MessageRecipient::class,
DebugLog::class,
EndpointTimer::class,
LessonRange::class,
Notification::class,
Classroom::class,
NoticeType::class,
AttendanceType::class,
Lesson::class,
ConfigEntry::class,
LibrusLesson::class,
Metadata::class
], version = 75)
@TypeConverters(
ConverterTime::class,
ConverterDate::class,
ConverterJsonObject::class,
ConverterListLong::class,
ConverterListString::class,
ConverterDateInt::class
)
abstract class AppDb : RoomDatabase() {
abstract fun gradeDao(): GradeDao
abstract fun teacherDao(): TeacherDao
abstract fun teacherAbsenceDao(): TeacherAbsenceDao
abstract fun teacherAbsenceTypeDao(): TeacherAbsenceTypeDao
abstract fun subjectDao(): SubjectDao
abstract fun noticeDao(): NoticeDao
abstract fun teamDao(): TeamDao
abstract fun attendanceDao(): AttendanceDao
abstract fun eventDao(): EventDao
abstract fun eventTypeDao(): EventTypeDao
abstract fun loginStoreDao(): LoginStoreDao
abstract fun profileDao(): ProfileDao
abstract fun luckyNumberDao(): LuckyNumberDao
abstract fun announcementDao(): AnnouncementDao
abstract fun gradeCategoryDao(): GradeCategoryDao
abstract fun feedbackMessageDao(): FeedbackMessageDao
abstract fun messageDao(): MessageDao
abstract fun messageRecipientDao(): MessageRecipientDao
abstract fun debugLogDao(): DebugLogDao
abstract fun endpointTimerDao(): EndpointTimerDao
abstract fun lessonRangeDao(): LessonRangeDao
abstract fun notificationDao(): NotificationDao
abstract fun classroomDao(): ClassroomDao
abstract fun noticeTypeDao(): NoticeTypeDao
abstract fun attendanceTypeDao(): AttendanceTypeDao
abstract fun timetableDao(): TimetableDao
abstract fun configDao(): ConfigDao
abstract fun librusLessonDao(): LibrusLessonDao
abstract fun metadataDao(): MetadataDao
companion object {
@Volatile private var instance: AppDb? = null
private val LOCK = Any()
private val MIGRATION_11_12: Migration = object : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("PRAGMA foreign_keys=off;")
database.execSQL("ALTER TABLE teams RENAME TO _teams_old;")
database.execSQL("CREATE TABLE teams (profileId INTEGER NOT NULL, teamId INTEGER NOT NULL, teamType INTEGER NOT NULL, teamName TEXT, teamTeacherId INTEGER NOT NULL, PRIMARY KEY(profileId, teamId));")
database.execSQL("INSERT INTO teams (profileId, teamId, teamType, teamName, teamTeacherId) SELECT profileId, teamId, teamType, teamName, teacherId FROM _teams_old;")
database.execSQL("DROP TABLE _teams_old;")
database.execSQL("PRAGMA foreign_keys=on;")
}
}
private val MIGRATION_12_13: Migration = object : Migration(12, 13) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE lessonChanges ADD lessonChangeWeekDay INTEGER NOT NULL DEFAULT -1;")
}
}
private val MIGRATION_13_14: Migration = object : Migration(13, 14) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE loginStores (loginStoreId INTEGER NOT NULL, loginStoreType INTEGER NOT NULL, loginStoreData TEXT, PRIMARY KEY(loginStoreId));")
}
}
private val MIGRATION_14_15: Migration = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades RENAME TO _grades_old;")
database.execSQL("CREATE TABLE `grades` (\n" +
"\t`profileId`\tINTEGER NOT NULL,\n" +
"\t`gradeId`\tINTEGER NOT NULL,\n" +
"\t`gradeDescription`\tTEXT,\n" +
"\t`gradeName`\tTEXT,\n" +
"\t`gradeValue`\tREAL NOT NULL,\n" +
"\t`gradeWeight`\tREAL NOT NULL,\n" +
"\t`gradeSemester`\tINTEGER NOT NULL,\n" +
"\t`gradeType`\tINTEGER NOT NULL,\n" +
"\t`teacherId`\tINTEGER NOT NULL,\n" +
"\t`categoryId`\tINTEGER NOT NULL,\n" +
"\t`subjectId`\tINTEGER NOT NULL,\n" +
"\tPRIMARY KEY(`profileId`,`gradeId`)\n" +
");")
database.execSQL("INSERT INTO grades\n" +
" SELECT *\n" +
" FROM _grades_old;")
database.execSQL("DROP TABLE _grades_old;")
database.execSQL("CREATE INDEX index_grades_profileId ON grades (profileId);")
}
}
private val MIGRATION_15_16: Migration = object : Migration(15, 16) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE profiles (" +
"profileId INTEGER NOT NULL, " +
"name TEXT, " +
"subname TEXT, " +
"image TEXT, " +
"syncEnabled INTEGER NOT NULL, " +
"syncNotifications INTEGER NOT NULL, " +
"enableSharedEvents INTEGER NOT NULL, " +
"countInSeconds INTEGER NOT NULL, " +
"loggedIn INTEGER NOT NULL, " +
"empty INTEGER NOT NULL, " +
"studentNameLong TEXT, " +
"studentNameShort TEXT, " +
"studentNumber INTEGER NOT NULL, " +
"studentData TEXT, " +
"registration INTEGER NOT NULL, " +
"gradeColorMode INTEGER NOT NULL, " +
"agendaViewType INTEGER NOT NULL, " +
"currentSemester INTEGER NOT NULL, " +
"attendancePercentage REAL NOT NULL, " +
"dateSemester1Start TEXT, " +
"dateSemester2Start TEXT, " +
"dateYearEnd TEXT, " +
"luckyNumberEnabled INTEGER NOT NULL, " +
"luckyNumber INTEGER NOT NULL, " +
"luckyNumberDate TEXT, " +
"loginStoreId INTEGER NOT NULL, " +
"PRIMARY KEY(profileId));")
}
}
private val MIGRATION_16_17: Migration = object : Migration(16, 17) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE profiles ADD archived INTEGER NOT NULL DEFAULT 0;")
database.execSQL("ALTER TABLE teams ADD teamCode TEXT;")
}
}
private val MIGRATION_17_18: Migration = object : Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE events ADD eventBlacklisted INTEGER NOT NULL DEFAULT 0;")
}
}
private val MIGRATION_18_19: Migration = object : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades ADD gradeClassAverage REAL NOT NULL DEFAULT -1;")
}
}
private val MIGRATION_19_20: Migration = object : Migration(19, 20) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE luckyNumbers (" +
"profileId INTEGER NOT NULL, " +
"luckyNumberDate TEXT NOT NULL, " +
"luckyNumber INTEGER NOT NULL, " +
"PRIMARY KEY(profileId, luckyNumberDate));")
}
}
private val MIGRATION_20_21: Migration = object : Migration(20, 21) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades ADD gradeCategory TEXT")
database.execSQL("ALTER TABLE grades ADD gradeColor INTEGER NOT NULL DEFAULT -1")
database.execSQL("DROP TABLE gradeCategories")
}
}
private val MIGRATION_21_22: Migration = object : Migration(21, 22) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE eventTypes (" +
"profileId INTEGER NOT NULL, " +
"eventType INTEGER NOT NULL, " +
"eventTypeName TEXT, " +
"eventTypeColor INTEGER NOT NULL, " +
"PRIMARY KEY(profileId, eventType));")
}
}
private val MIGRATION_22_23: Migration = object : Migration(22, 23) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades RENAME TO _grades_old;")
database.execSQL("CREATE TABLE `grades` (\n" +
"\t`profileId`\tINTEGER NOT NULL,\n" +
"\t`gradeId`\tINTEGER NOT NULL,\n" +
"\t`gradeCategory`\tTEXT,\n" +
"\t`gradeColor`\tINTEGER NOT NULL DEFAULT -1,\n" +
"\t`gradeDescription`\tTEXT,\n" +
"\t`gradeName`\tTEXT,\n" +
"\t`gradeValue`\tREAL NOT NULL,\n" +
"\t`gradeWeight`\tREAL NOT NULL,\n" +
"\t`gradeSemester`\tINTEGER NOT NULL,\n" +
"\t`gradeClassAverage`\tREAL NOT NULL DEFAULT -1,\n" +
"\t`gradeType`\tINTEGER NOT NULL,\n" +
"\t`teacherId`\tINTEGER NOT NULL,\n" +
"\t`subjectId`\tINTEGER NOT NULL,\n" +
"\tPRIMARY KEY(`profileId`,`gradeId`)\n" +
");")
database.execSQL("DROP INDEX index_grades_profileId")
database.execSQL("CREATE INDEX `index_grades_profileId` ON `grades` (\n" +
"\t`profileId`\n" +
");")
database.execSQL("INSERT INTO grades (profileId, gradeId, gradeDescription, gradeName, gradeValue, gradeWeight, gradeSemester, gradeType, teacherId, subjectId, gradeClassAverage, gradeCategory, gradeColor) SELECT profileId, gradeId, gradeDescription, gradeName, gradeValue, gradeWeight, gradeSemester, gradeType, teacherId, subjectId, gradeClassAverage, gradeCategory, gradeColor FROM _grades_old;")
database.execSQL("DROP TABLE _grades_old;")
database.execSQL("ALTER TABLE attendances RENAME TO _attendances_old;")
database.execSQL("CREATE TABLE `attendances` (\n" +
"\t`profileId`\tINTEGER NOT NULL,\n" +
"\t`attendanceId`\tINTEGER NOT NULL,\n" +
"\t`attendanceLessonDate`\tTEXT NOT NULL,\n" +
"\t`attendanceStartTime`\tTEXT NOT NULL,\n" +
"\t`attendanceLessonTopic`\tTEXT,\n" +
"\t`attendanceSemester`\tINTEGER NOT NULL,\n" +
"\t`attendanceType`\tINTEGER NOT NULL,\n" +
"\t`teacherId`\tINTEGER NOT NULL,\n" +
"\t`subjectId`\tINTEGER NOT NULL,\n" +
"\tPRIMARY KEY(`profileId`,`attendanceId`,`attendanceLessonDate`,`attendanceStartTime`)\n" +
");")
database.execSQL("DROP INDEX index_attendances_profileId")
database.execSQL("CREATE INDEX `index_attendances_profileId` ON `attendances` (\n" +
"\t`profileId`\n" +
");")
database.execSQL("INSERT INTO attendances SELECT * FROM _attendances_old;")
database.execSQL("DROP TABLE _attendances_old;")
}
}
private val MIGRATION_23_24: Migration = object : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE profiles ADD yearAverageMode INTEGER NOT NULL DEFAULT 0")
}
}
private val MIGRATION_24_25: Migration = object : Migration(24, 25) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE announcements (" +
"profileId INTEGER NOT NULL, " +
"announcementId INTEGER NOT NULL, " +
"announcementSubject TEXT, " +
"announcementText TEXT, " +
"announcementStartDate TEXT, " +
"announcementEndDate TEXT, " +
"teacherId INTEGER NOT NULL, " +
"PRIMARY KEY(profileId, announcementId));")
//database.execSQL("DROP INDEX index_announcements_profileId");
database.execSQL("CREATE INDEX index_announcements_profileId ON announcements (" +
"profileId" +
");")
}
}
private val MIGRATION_25_26: Migration = object : Migration(25, 26) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades ADD gradePointGrade INTEGER NOT NULL DEFAULT 0;")
}
}
private val MIGRATION_26_27: Migration = object : Migration(26, 27) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades ADD gradeValueMax REAL NOT NULL DEFAULT 0;")
}
}
private val MIGRATION_27_28: Migration = object : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE gradeCategories (profileId INTEGER NOT NULL, categoryId INTEGER NOT NULL, weight REAL NOT NULL, color INTEGER NOT NULL, `text` TEXT, columns TEXT, valueFrom REAL NOT NULL, valueTo REAL NOT NULL, PRIMARY KEY(profileId, categoryId));")
database.execSQL("ALTER TABLE grades ADD gradeComment TEXT;")
}
}
private val MIGRATION_28_29: Migration = object : Migration(28, 29) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE feedbackMessages (messageId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, received INTEGER NOT NULL DEFAULT 0, sentTime INTEGER NOT NULL, `text` TEXT)")
}
}
private val MIGRATION_29_30: Migration = object : Migration(29, 30) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE feedbackMessages ADD fromUser TEXT DEFAULT NULL")
database.execSQL("ALTER TABLE feedbackMessages ADD fromUserName TEXT DEFAULT NULL")
}
}
private val MIGRATION_30_31: Migration = object : Migration(30, 31) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE messages (" +
"profileId INTEGER NOT NULL, " +
"messageId INTEGER NOT NULL, " +
"messageSubject TEXT, " +
"messageBody TEXT DEFAULT NULL, " +
"messageType INTEGER NOT NULL DEFAULT 0, " +
"senderId INTEGER NOT NULL DEFAULT -1, " +
"senderReplyId INTEGER NOT NULL DEFAULT -1, " +
"recipientIds TEXT DEFAULT NULL, " +
"recipientReplyIds TEXT DEFAULT NULL, " +
"readByRecipientDates TEXT DEFAULT NULL, " +
"overrideHasAttachments INTEGER NOT NULL DEFAULT 0, " +
"attachmentIds TEXT DEFAULT NULL, " +
"attachmentNames TEXT DEFAULT NULL, " +
"PRIMARY KEY(profileId, messageId));")
//database.execSQL("DROP INDEX index_announcements_profileId");
database.execSQL("CREATE INDEX index_messages_profileId ON messages (" +
"profileId" +
");")
}
}
private val MIGRATION_31_32: Migration = object : Migration(31, 32) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE messages ADD attachmentSizes TEXT DEFAULT NULL")
}
}
private val MIGRATION_32_33: Migration = object : Migration(32, 33) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE messages")
database.execSQL("CREATE TABLE messages (" +
"profileId INTEGER NOT NULL, " +
"messageId INTEGER NOT NULL, " +
"messageSubject TEXT, " +
"messageBody TEXT DEFAULT NULL, " +
"messageType INTEGER NOT NULL DEFAULT 0, " +
"senderId INTEGER NOT NULL DEFAULT -1, " +
"senderReplyId INTEGER NOT NULL DEFAULT -1, " +
"overrideHasAttachments INTEGER NOT NULL DEFAULT 0, " +
"attachmentIds TEXT DEFAULT NULL, " +
"attachmentNames TEXT DEFAULT NULL, " +
"attachmentSizes TEXT DEFAULT NULL, " +
"PRIMARY KEY(profileId, messageId));")
//database.execSQL("DROP INDEX index_announcements_profileId");
database.execSQL("CREATE INDEX index_messages_profileId ON messages (" +
"profileId" +
");")
database.execSQL("CREATE TABLE messageRecipients (" +
"profileId INTEGER NOT NULL, " +
"messageRecipientId INTEGER NOT NULL DEFAULT -1, " +
"messageRecipientReplyId INTEGER NOT NULL DEFAULT -1, " +
"messageRecipientReadDate INTEGER NOT NULL DEFAULT -1, " +
"messageId INTEGER NOT NULL, " +
"PRIMARY KEY(profileId, messageRecipientId));")
}
}
private val MIGRATION_33_34: Migration = object : Migration(33, 34) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE messageRecipients")
database.execSQL("CREATE TABLE messageRecipients (" +
"profileId INTEGER NOT NULL, " +
"messageRecipientId INTEGER NOT NULL DEFAULT -1, " +
"messageRecipientReplyId INTEGER NOT NULL DEFAULT -1, " +
"messageRecipientReadDate INTEGER NOT NULL DEFAULT -1, " +
"messageId INTEGER NOT NULL, " +
"PRIMARY KEY(profileId, messageRecipientId, messageId));")
}
}
private val MIGRATION_34_35: Migration = object : Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE teachers ADD teacherLoginId TEXT DEFAULT NULL;")
}
}
private val MIGRATION_35_36: Migration = object : Migration(35, 36) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET yearAverageMode = 4 WHERE yearAverageMode = 0")
}
}
private val MIGRATION_36_37: Migration = object : Migration(36, 37) {
override fun migrate(database: SupportSQLiteDatabase) {}
}
private val MIGRATION_37_38: Migration = object : Migration(37, 38) {
override fun migrate(database: SupportSQLiteDatabase) {
val today = Date.getToday()
val schoolYearStart = if (today.month < 9) today.year - 1 else today.year
database.execSQL("UPDATE profiles SET dateSemester1Start = '$schoolYearStart-09-01' WHERE dateSemester1Start IS NULL;")
database.execSQL("UPDATE profiles SET dateSemester2Start = '" + (schoolYearStart + 1) + "-02-01' WHERE dateSemester2Start IS NULL;")
database.execSQL("UPDATE profiles SET dateYearEnd = '" + (schoolYearStart + 1) + "-06-30' WHERE dateYearEnd IS NULL;")
}
}
private val MIGRATION_38_39: Migration = object : Migration(38, 39) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE debugLogs (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `text` TEXT);")
}
}
private val MIGRATION_39_40: Migration = object : Migration(39, 40) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE profiles ADD changedEndpoints TEXT DEFAULT NULL;")
}
}
private val MIGRATION_40_41: Migration = object : Migration(40, 41) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET empty = 1;")
}
}
private val MIGRATION_41_42: Migration = object : Migration(41, 42) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET empty = 1;")
}
}
private val MIGRATION_42_43: Migration = object : Migration(42, 43) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE profiles ADD lastFullSync INTEGER NOT NULL DEFAULT 0;")
}
}
private val MIGRATION_43_44: Migration = object : Migration(43, 44) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET empty = 1;")
database.execSQL("UPDATE profiles SET currentSemester = 1;")
}
}
private val MIGRATION_44_45: Migration = object : Migration(44, 45) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET empty = 1;")
}
}
private val MIGRATION_45_46: Migration = object : Migration(45, 46) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET lastFullSync = 0")
}
}
private val MIGRATION_46_47: Migration = object : Migration(46, 47) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE profiles SET lastFullSync = 0")
}
}
private val MIGRATION_47_48: Migration = object : Migration(47, 48) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE notices ADD points REAL NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE notices ADD category TEXT DEFAULT NULL")
}
}
private val MIGRATION_48_49: Migration = object : Migration(48, 49) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades ADD gradeParentId INTEGER NOT NULL DEFAULT -1")
}
}
private val MIGRATION_49_50: Migration = object : Migration(49, 50) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE grades ADD gradeIsImprovement INTEGER NOT NULL DEFAULT 0")
database.execSQL("DELETE FROM attendances WHERE attendances.profileId IN (SELECT profiles.profileId FROM profiles JOIN loginStores USING(loginStoreId) WHERE loginStores.loginStoreType = 1)")
database.execSQL("UPDATE profiles SET empty = 1 WHERE profileId IN (SELECT profiles.profileId FROM profiles JOIN loginStores USING(loginStoreId) WHERE loginStores.loginStoreType = 4)")
}
}
private val MIGRATION_50_51: Migration = object : Migration(50, 51) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE profiles ADD lastReceiversSync INTEGER NOT NULL DEFAULT 0")
database.execSQL("ALTER TABLE teachers ADD teacherType INTEGER NOT NULL DEFAULT 0")
}
}
private val MIGRATION_51_52: Migration = object : Migration(51, 52) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE teachers ADD teacherTypeDescription TEXT DEFAULT NULL")
}
}
private val MIGRATION_52_53: Migration = object : Migration(52, 53) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS teacherAbsence (" +
"profileId INTEGER NOT NULL," +
"teacherAbsenceId INTEGER NOT NULL," +
"teacherId INTEGER NOT NULL," +
"teacherAbsenceType INTEGER NOT NULL," +
"teacherAbsenceDateFrom TEXT NOT NULL," +
"teacherAbsenceDateTo TEXT NOT NULL," +
"PRIMARY KEY(profileId, teacherAbsenceId)" +
")")
}
}
private val MIGRATION_53_54: Migration = object : Migration(53, 54) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE teacherAbsence ADD teacherAbsenceTimeFrom TEXT DEFAULT NULL")
database.execSQL("ALTER TABLE teacherAbsence ADD teacherAbsenceTimeTo TEXT DEFAULT NULL")
}
}
private val MIGRATION_54_55: Migration = object : Migration(54, 55) {
override fun migrate(database: SupportSQLiteDatabase) {
// 2019-10-21 for merge compatibility between 3.1.1 and api-v2
// moved to Migration 55->56
}
}
private val MIGRATION_55_56: Migration = object : Migration(55, 56) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS endpointTimers (" +
"profileId INTEGER NOT NULL," +
"endpointId INTEGER NOT NULL," +
"endpointLastSync INTEGER DEFAULT NULL," +
"endpointNextSync INTEGER NOT NULL DEFAULT 1," +
"endpointViewId INTEGER DEFAULT NULL," +
"PRIMARY KEY(profileId, endpointId)" +
")")
database.execSQL("CREATE TABLE IF NOT EXISTS lessonRanges (" +
"profileId INTEGER NOT NULL," +
"lessonRangeNumber INTEGER NOT NULL," +
"lessonRangeStart TEXT NOT NULL," +
"lessonRangeEnd TEXT NOT NULL," +
"PRIMARY KEY(profileId, lessonRangeNumber)" +
")")
}
}
private val MIGRATION_56_57: Migration = object : Migration(56, 57) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE gradeCategories ADD type INTEGER NOT NULL DEFAULT 0")
}
}
private val MIGRATION_57_58: Migration = object : Migration(57, 58) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE metadata RENAME TO _metadata_old;")
database.execSQL("DROP INDEX index_metadata_profileId_thingType_thingId;")
database.execSQL("UPDATE _metadata_old SET thingType = " + Metadata.TYPE_HOMEWORK + " WHERE thingType = " + Metadata.TYPE_EVENT + " AND thingId IN (SELECT eventId FROM events WHERE eventType = -1);")
database.execSQL("CREATE TABLE metadata (\n" +
"profileId INTEGER NOT NULL,\n" +
"metadataId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" +
"thingType INTEGER NOT NULL,\n" +
"thingId INTEGER NOT NULL,\n" +
"seen INTEGER NOT NULL,\n" +
"notified INTEGER NOT NULL,\n" +
"addedDate INTEGER NOT NULL);")
database.execSQL("INSERT INTO metadata SELECT * FROM (SELECT * FROM _metadata_old ORDER BY addedDate DESC) GROUP BY thingId;")
database.execSQL("DROP TABLE _metadata_old;")
database.execSQL("CREATE UNIQUE INDEX index_metadata_profileId_thingType_thingId ON metadata (profileId, thingType, thingId);")
}
}
private val MIGRATION_58_59: Migration = object : Migration(58, 59) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("UPDATE metadata SET addedDate = addedDate*1000 WHERE addedDate < 10000000000;")
database.execSQL("INSERT INTO metadata (profileId, thingType, thingId, seen, notified, addedDate)\n" +
"SELECT profileId,\n" +
"10 AS thingType,\n" +
"luckyNumberDate*10000+substr(luckyNumberDate, 6)*100+substr(luckyNumberDate, 9) AS thingId,\n" +
"1 AS seen,\n" +
"1 AS notified,\n" +
"CAST(strftime('%s', luckyNumberDate) AS INT)*1000 AS addedDate\n" +
"FROM luckyNumbers;")
database.execSQL("ALTER TABLE luckyNumbers RENAME TO _old_luckyNumbers;")
database.execSQL("CREATE TABLE luckyNumbers (\n" +
"profileId INTEGER NOT NULL,\n" +
"luckyNumberDate INTEGER NOT NULL, \n" +
"luckyNumber INTEGER NOT NULL, \n" +
"PRIMARY KEY(profileId, luckyNumberDate))")
database.execSQL("INSERT INTO luckyNumbers (profileId, luckyNumberDate, luckyNumber)\n" +
"SELECT profileId,\n" +
"luckyNumberDate*10000+substr(luckyNumberDate, 6)*100+substr(luckyNumberDate, 9) AS luckyNumberDate,\n" +
"luckyNumber\n" +
"FROM _old_luckyNumbers;")
database.execSQL("DROP TABLE _old_luckyNumbers;")
}
}
private val MIGRATION_59_60: Migration = object : Migration(59, 60) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE notifications (\n" +
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" +
" title TEXT NOT NULL,\n" +
" `text` TEXT NOT NULL,\n" +
" `type` INTEGER NOT NULL,\n" +
" profileId INTEGER DEFAULT NULL,\n" +
" profileName TEXT DEFAULT NULL,\n" +
" posted INTEGER NOT NULL DEFAULT 0,\n" +
" viewId INTEGER DEFAULT NULL,\n" +
" extras TEXT DEFAULT NULL,\n" +
" addedDate INTEGER NOT NULL\n" +
");")
database.execSQL("ALTER TABLE profiles ADD COLUMN disabledNotifications TEXT DEFAULT NULL")
}
}
private val MIGRATION_60_61: Migration = object : Migration(60, 61) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS teacherAbsenceTypes (" +
"profileId INTEGER NOT NULL," +
"teacherAbsenceTypeId INTEGER NOT NULL," +
"teacherAbsenceTypeName TEXT NOT NULL," +
"PRIMARY KEY(profileId, teacherAbsenceTypeId))")
database.execSQL("ALTER TABLE teacherAbsence ADD COLUMN teacherAbsenceName TEXT DEFAULT NULL")
}
}
private val MIGRATION_61_62: Migration = object : Migration(61, 62) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS classrooms (\n" +
" profileId INTEGER NOT NULL,\n" +
" id INTEGER NOT NULL,\n" +
" name TEXT NOT NULL,\n" +
" PRIMARY KEY(profileId, id)\n" +
")")
database.execSQL("CREATE TABLE IF NOT EXISTS noticeTypes (\n" +
" profileId INTEGER NOT NULL,\n" +
" id INTEGER NOT NULL,\n" +
" name TEXT NOT NULL,\n" +
" PRIMARY KEY(profileId, id)\n" +
")")
database.execSQL("CREATE TABLE IF NOT EXISTS attendanceTypes (\n" +
" profileId INTEGER NOT NULL,\n" +
" id INTEGER NOT NULL,\n" +
" name TEXT NOT NULL,\n" +
" type INTEGER NOT NULL,\n" +
" color INTEGER NOT NULL,\n" +
" PRIMARY KEY(profileId, id)\n" +
")")
}
}
private val MIGRATION_62_63: Migration = object : Migration(62, 63) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE profiles ADD COLUMN accountNameLong TEXT DEFAULT NULL")
database.execSQL("ALTER TABLE profiles ADD COLUMN studentClassName TEXT DEFAULT NULL")
database.execSQL("ALTER TABLE profiles ADD COLUMN studentSchoolYear TEXT DEFAULT NULL")
}
}
private val MIGRATION_63_64: Migration = object : Migration(63, 64) {
override fun migrate(database: SupportSQLiteDatabase) {
//database.execSQL("ALTER TABLE lessons RENAME TO lessonsOld;");
database.execSQL("CREATE TABLE timetable (" +
"profileId INTEGER NOT NULL," +
"id INTEGER NOT NULL," +
"type INTEGER NOT NULL," +
"date TEXT DEFAULT NULL," +
"lessonNumber INTEGER DEFAULT NULL," +
"startTime TEXT DEFAULT NULL," +
"endTime TEXT DEFAULT NULL," +
"subjectId INTEGER DEFAULT NULL," +
"teacherId 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," +
"oldSubjectId INTEGER DEFAULT NULL," +
"oldTeacherId INTEGER DEFAULT NULL," +
"oldTeamId INTEGER DEFAULT NULL," +
"oldClassroom TEXT DEFAULT NULL," +
"PRIMARY KEY(id));")
database.execSQL("CREATE INDEX index_lessons_profileId_type_date ON timetable (profileId, type, date);")
database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);")
}
}
private val MIGRATION_64_65: Migration = object : Migration(64, 65) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE config (" +
"profileId INTEGER NOT NULL DEFAULT -1," +
"`key` TEXT NOT NULL," +
"value TEXT NOT NULL," +
"PRIMARY KEY(profileId, `key`));")
}
}
private val MIGRATION_65_66: Migration = object : Migration(65, 66) {
override fun migrate(database: SupportSQLiteDatabase) {
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`));")
}
}
private val MIGRATION_66_67: Migration = object : Migration(66, 67) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM grades WHERE (gradeId=-1 OR gradeId=-2) AND gradeType=20")
database.execSQL("DELETE FROM metadata WHERE (thingId=-1 OR thingId=-2) AND thingType=1")
database.execSQL("ALTER TABLE gradeCategories RENAME TO _gradeCategories")
database.execSQL("CREATE TABLE gradeCategories (" +
"profileId INTEGER NOT NULL," +
"categoryId INTEGER NOT NULL," +
"weight REAL NOT NULL," +
"color INTEGER NOT NULL," +
"`text` TEXT," +
"columns TEXT," +
"valueFrom REAL NOT NULL," +
"valueTo REAL NOT NULL," +
"type INTEGER NOT NULL," +
"PRIMARY KEY(profileId, categoryId, type))")
database.execSQL("INSERT INTO gradeCategories (profileId, categoryId, weight, color," +
"`text`, columns, valueFrom, valueTo, type) " +
"SELECT profileId, categoryId, weight, color, `text`, columns, valueFrom," +
"valueTo, type FROM _gradeCategories")
database.execSQL("DROP TABLE _gradeCategories")
}
}
private val MIGRATION_67_68: Migration = object : Migration(67, 68) {
override fun migrate(database: SupportSQLiteDatabase) {
/* Migration from crc16 to crc32 id */
database.execSQL("DELETE FROM announcements")
database.execSQL("DELETE FROM metadata WHERE thingType=7")
}
}
private val MIGRATION_68_69: Migration = object : Migration(68, 69) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE loginStores ADD COLUMN loginStoreMode INTEGER NOT NULL DEFAULT 0")
}
}
private val MIGRATION_69_70: Migration = object : Migration(69, 70) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE announcements ADD COLUMN announcementIdString TEXT DEFAULT NULL")
database.execSQL("DELETE FROM announcements")
}
}
private val MIGRATION_70_71: Migration = object : Migration(70, 71) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM messages WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0);")
database.execSQL("DELETE FROM messageRecipients WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0);")
database.execSQL("DELETE FROM teachers WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0);")
database.execSQL("DELETE FROM endpointTimers WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0);")
database.execSQL("DELETE FROM metadata WHERE profileId IN (SELECT profileId FROM profiles WHERE archived = 0) AND thingType = 8;")
database.execSQL("UPDATE profiles SET empty = 1 WHERE archived = 0;")
database.execSQL("UPDATE profiles SET lastReceiversSync = 0 WHERE archived = 0;")
database.execSQL("INSERT INTO config (profileId, `key`, value) VALUES (-1, \"runSync\", \"true\");")
}
}
private val MIGRATION_71_72: Migration = object : Migration(71, 72) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE loginStores RENAME to _loginStores;")
database.execSQL("CREATE TABLE loginStores(" +
"loginStoreId INTEGER NOT NULL," +
"loginStoreType INTEGER NOT NULL," +
"loginStoreMode INTEGER NOT NULL," +
"loginStoreData TEXT NOT NULL," +
"PRIMARY KEY(loginStoreId));")
database.execSQL("INSERT INTO loginStores " +
"(loginStoreId, loginStoreType, loginStoreMode, loginStoreData) " +
"SELECT loginStoreId, loginStoreType, loginStoreMode, loginStoreData " +
"FROM _loginStores;")
database.execSQL("DROP TABLE _loginStores;")
database.execSQL("ALTER TABLE profiles RENAME TO _profiles_old;")
database.execSQL("CREATE TABLE profiles (\n" +
"profileId INTEGER NOT NULL, name TEXT NOT NULL, subname TEXT, image TEXT DEFAULT NULL, \n" +
"studentNameLong TEXT NOT NULL, studentNameShort TEXT NOT NULL, accountName TEXT, \n" +
"studentData TEXT NOT NULL, empty INTEGER NOT NULL DEFAULT 1, archived INTEGER NOT NULL DEFAULT 0, \n" +
"syncEnabled INTEGER NOT NULL DEFAULT 1, enableSharedEvents INTEGER NOT NULL DEFAULT 1, registration INTEGER NOT NULL DEFAULT 0, \n" +
"userCode TEXT NOT NULL DEFAULT \"\", studentNumber INTEGER NOT NULL DEFAULT -1, studentClassName TEXT DEFAULT NULL, \n" +
"studentSchoolYearStart INTEGER NOT NULL, dateSemester1Start TEXT NOT NULL, dateSemester2Start TEXT NOT NULL, \n" +
"dateYearEnd TEXT NOT NULL, disabledNotifications TEXT DEFAULT NULL, lastReceiversSync INTEGER NOT NULL DEFAULT 0, \n" +
"loginStoreId INTEGER NOT NULL, loginStoreType INTEGER NOT NULL, PRIMARY KEY(profileId));")
database.execSQL("INSERT INTO profiles (profileId, name, subname, image, studentNameLong, studentNameShort, accountName, \n" +
"userCode, studentData, empty, archived, syncEnabled, enableSharedEvents, registration, studentNumber, studentSchoolYearStart, \n" +
"dateSemester1Start, dateSemester2Start, dateYearEnd, lastReceiversSync, loginStoreId, loginStoreType \n" +
") SELECT profileId, name, subname, image, studentNameLong, studentNameShort, accountNameLong, \"\" AS userCode, studentData, \n" +
"empty, archived, syncEnabled, enableSharedEvents, registration, studentNumber, SUBSTR(dateSemester1Start, 0, 5) AS studentSchoolYearStart, \n" +
"dateSemester1Start, dateSemester2Start, dateYearEnd, lastReceiversSync, _profiles_old.loginStoreId, loginStoreType FROM _profiles_old \n" +
"JOIN loginStores ON loginStores.loginStoreId = _profiles_old.loginStoreId \n" +
"WHERE profileId >= 0;")
database.execSQL("DROP TABLE _profiles_old;")
// MIGRACJA userCode - mobidziennik
database.execSQL("DROP TABLE IF EXISTS _userCodes;")
database.execSQL("CREATE TABLE _userCodes (profileId INTEGER, loginData TEXT, studentData TEXT, userCode TEXT, serverName TEXT, username TEXT, studentId TEXT);")
database.execSQL("DELETE FROM _userCodes;")
database.execSQL("INSERT INTO _userCodes SELECT profileId, loginStores.loginStoreData, studentData, \"\", \"\", \"\", \"\" FROM profiles JOIN loginStores ON loginStores.loginStoreId = profiles.loginStoreId WHERE profiles.loginStoreType = 1;")
database.execSQL("UPDATE _userCodes SET serverName = SUBSTR(loginData, instr(loginData, '\"serverName\":\"')+14);")
database.execSQL("UPDATE _userCodes SET serverName = SUBSTR(serverName, 0, instr(serverName, '\",')+instr(serverName, '\"}')-(instr(serverName, '\"}')*min(instr(serverName, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET username = SUBSTR(loginData, instr(loginData, '\"username\":\"')+12);")
database.execSQL("UPDATE _userCodes SET username = SUBSTR(username, 0, instr(username, '\",')+instr(username, '\"}')-(instr(username, '\"}')*min(instr(username, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET studentId = SUBSTR(studentData, instr(studentData, '\"studentId\":')+12);")
database.execSQL("UPDATE _userCodes SET studentId = SUBSTR(studentId, 0, instr(studentId, ',')+instr(studentId, '}')-(instr(studentId, '}')*min(instr(studentId, ','), 1)));")
database.execSQL("UPDATE _userCodes SET userCode = serverName||\":\"||username||\":\"||studentId;")
database.execSQL("UPDATE profiles SET userCode = (SELECT userCode FROM _userCodes WHERE profileId = profiles.profileId) WHERE profileId IN (SELECT profileId FROM _userCodes);")
// MIGRACJA userCode - librus
database.execSQL("DROP TABLE IF EXISTS _userCodes;")
database.execSQL("CREATE TABLE _userCodes (profileId INTEGER, loginData TEXT, studentData TEXT, userCode TEXT, schoolName TEXT, accountLogin TEXT);")
database.execSQL("DELETE FROM _userCodes;")
database.execSQL("INSERT INTO _userCodes SELECT profileId, loginStores.loginStoreData, studentData, \"\", \"\", \"\" FROM profiles JOIN loginStores ON loginStores.loginStoreId = profiles.loginStoreId WHERE profiles.loginStoreType = 2;")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(studentData, instr(studentData, '\"schoolName\":\"')+14);")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(schoolName, 0, instr(schoolName, '\",')+instr(schoolName, '\"}')-(instr(schoolName, '\"}')*min(instr(schoolName, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET accountLogin = SUBSTR(studentData, instr(studentData, '\"accountLogin\":\"')+16);")
database.execSQL("UPDATE _userCodes SET accountLogin = SUBSTR(accountLogin, 0, instr(accountLogin, '\",')+instr(accountLogin, '\"}')-(instr(accountLogin, '\"}')*min(instr(accountLogin, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET userCode = schoolName||\":\"||accountLogin;")
database.execSQL("UPDATE profiles SET userCode = (SELECT userCode FROM _userCodes WHERE profileId = profiles.profileId) WHERE profileId IN (SELECT profileId FROM _userCodes);")
// MIGRACJA userCode - iuczniowie
database.execSQL("DROP TABLE IF EXISTS _userCodes;")
database.execSQL("CREATE TABLE _userCodes (profileId INTEGER, loginData TEXT, studentData TEXT, userCode TEXT, schoolName TEXT, username TEXT, registerId TEXT);")
database.execSQL("DELETE FROM _userCodes;")
database.execSQL("INSERT INTO _userCodes SELECT profileId, loginStores.loginStoreData, studentData, \"\", \"\", \"\", \"\" FROM profiles JOIN loginStores ON loginStores.loginStoreId = profiles.loginStoreId WHERE profiles.loginStoreType = 3;")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(loginData, instr(loginData, '\"schoolName\":\"')+14);")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(schoolName, 0, instr(schoolName, '\",')+instr(schoolName, '\"}')-(instr(schoolName, '\"}')*min(instr(schoolName, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET username = SUBSTR(loginData, instr(loginData, '\"username\":\"')+12);")
database.execSQL("UPDATE _userCodes SET username = SUBSTR(username, 0, instr(username, '\",')+instr(username, '\"}')-(instr(username, '\"}')*min(instr(username, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET registerId = SUBSTR(studentData, instr(studentData, '\"registerId\":')+13);")
database.execSQL("UPDATE _userCodes SET registerId = SUBSTR(registerId, 0, instr(registerId, ',')+instr(registerId, '}')-(instr(registerId, '}')*min(instr(registerId, ','), 1)));")
database.execSQL("UPDATE _userCodes SET userCode = schoolName||\":\"||username||\":\"||registerId;")
database.execSQL("UPDATE profiles SET userCode = (SELECT userCode FROM _userCodes WHERE profileId = profiles.profileId) WHERE profileId IN (SELECT profileId FROM _userCodes);")
// MIGRACJA userCode - vulcan
database.execSQL("DROP TABLE IF EXISTS _userCodes;")
database.execSQL("CREATE TABLE _userCodes (profileId INTEGER, loginData TEXT, studentData TEXT, userCode TEXT, schoolName TEXT, studentId TEXT);")
database.execSQL("DELETE FROM _userCodes;")
database.execSQL("INSERT INTO _userCodes SELECT profileId, loginStores.loginStoreData, studentData, \"\", \"\", \"\" FROM profiles JOIN loginStores ON loginStores.loginStoreId = profiles.loginStoreId WHERE profiles.loginStoreType = 4;")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(studentData, instr(studentData, '\"schoolName\":\"')+14);")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(schoolName, 0, instr(schoolName, '\",')+instr(schoolName, '\"}')-(instr(schoolName, '\"}')*min(instr(schoolName, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET studentId = SUBSTR(studentData, instr(studentData, '\"studentId\":')+12);")
database.execSQL("UPDATE _userCodes SET studentId = SUBSTR(studentId, 0, instr(studentId, ',')+instr(studentId, '}')-(instr(studentId, '}')*min(instr(studentId, ','), 1)));")
database.execSQL("UPDATE _userCodes SET userCode = schoolName||\":\"||studentId;")
database.execSQL("UPDATE profiles SET userCode = (SELECT userCode FROM _userCodes WHERE profileId = profiles.profileId) WHERE profileId IN (SELECT profileId FROM _userCodes);")
// MIGRACJA userCode - edudziennik
database.execSQL("DROP TABLE IF EXISTS _userCodes;")
database.execSQL("CREATE TABLE _userCodes (profileId INTEGER, loginData TEXT, studentData TEXT, userCode TEXT, schoolName TEXT, email TEXT, studentId TEXT);")
database.execSQL("DELETE FROM _userCodes;")
database.execSQL("INSERT INTO _userCodes SELECT profileId, loginStores.loginStoreData, studentData, \"\", \"\", \"\", \"\" FROM profiles JOIN loginStores ON loginStores.loginStoreId = profiles.loginStoreId WHERE profiles.loginStoreType = 5;")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(studentData, instr(studentData, '\"schoolName\":\"')+14);")
database.execSQL("UPDATE _userCodes SET schoolName = SUBSTR(schoolName, 0, instr(schoolName, '\",')+instr(schoolName, '\"}')-(instr(schoolName, '\"}')*min(instr(schoolName, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET email = SUBSTR(loginData, instr(loginData, '\"email\":\"')+9);")
database.execSQL("UPDATE _userCodes SET email = SUBSTR(email, 0, instr(email, '\",')+instr(email, '\"}')-(instr(email, '\"}')*min(instr(email, '\",'), 1)));")
database.execSQL("UPDATE _userCodes SET studentId = SUBSTR(studentData, instr(studentData, '\"studentId\":\"')+13);")
database.execSQL("UPDATE _userCodes SET studentId = SUBSTR(studentId, 0, instr(studentId, '\",')+instr(studentId, '\"}')-(instr(studentId, '\"}')*min(instr(studentId, '\",'), 1)));")
database.query("SELECT profileId, studentId FROM _userCodes;").use { cursor ->
while (cursor.moveToNext()) {
val profileId = cursor.getInt(0)
val crc = cursor.getString(1).crc32()
database.execSQL("UPDATE _userCodes SET studentId = $crc WHERE profileId = $profileId")
}
}
database.execSQL("UPDATE _userCodes SET userCode = schoolName||\":\"||email||\":\"||studentId;")
database.execSQL("UPDATE profiles SET userCode = (SELECT userCode FROM _userCodes WHERE profileId = profiles.profileId) WHERE profileId IN (SELECT profileId FROM _userCodes);")
database.execSQL("DROP TABLE _userCodes;")
}
}
private val MIGRATION_72_73: Migration = object : Migration(72, 73) {
override fun migrate(database: SupportSQLiteDatabase) { // Mark as seen all lucky number metadata.
database.execSQL("UPDATE metadata SET seen=1 WHERE thingType=10")
database.execSQL("DROP TABLE lessons")
database.execSQL("DROP TABLE lessonChanges")
}
}
private val MIGRATION_73_74: Migration = object : Migration(73, 74) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE librusLessons (" +
"profileId INTEGER NOT NULL," +
"lessonId INTEGER NOT NULL," +
"teacherId INTEGER NOT NULL," +
"subjectId INTEGER NOT NULL," +
"teamId INTEGER," +
"PRIMARY KEY(profileId, lessonId));")
database.execSQL("CREATE INDEX index_librusLessons_profileId ON librusLessons (profileId);")
}
}
private val MIGRATION_74_75: Migration = object : Migration(74, 75) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE timetable RENAME TO _timetable;")
database.execSQL("CREATE TABLE timetable (profileId INTEGER NOT NULL,id INTEGER NOT NULL,type INTEGER NOT NULL,date TEXT DEFAULT NULL,lessonNumber INTEGER DEFAULT NULL,startTime TEXT DEFAULT NULL,endTime TEXT DEFAULT NULL,subjectId INTEGER DEFAULT NULL,teacherId 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,oldSubjectId INTEGER DEFAULT NULL,oldTeacherId INTEGER DEFAULT NULL,oldTeamId INTEGER DEFAULT NULL,oldClassroom TEXT DEFAULT NULL,PRIMARY KEY(profileId, id));")
database.execSQL("INSERT INTO timetable (profileId, id, type, date, lessonNumber, startTime, endTime, subjectId, teacherId, teamId, classroom, oldDate, oldLessonNumber, oldStartTime, oldEndTime, oldSubjectId, oldTeacherId, oldTeamId, oldClassroom) SELECT profileId, id, type, date, lessonNumber, startTime, endTime, subjectId, teacherId, teamId, classroom, oldDate, oldLessonNumber, oldStartTime, oldEndTime, oldSubjectId, oldTeacherId, oldTeamId, oldClassroom FROM _timetable;")
database.execSQL("DROP TABLE _timetable;")
database.execSQL("CREATE INDEX index_lessons_profileId_type_date ON timetable (profileId, type, date);")
database.execSQL("CREATE INDEX index_lessons_profileId_type_oldDate ON timetable (profileId, type, oldDate);")
}
}
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: buildDatabase(context).also { instance = it }
}
private fun buildDatabase(context: Context) = Room.databaseBuilder(
context.applicationContext,
AppDb::class.java,
"edziennik.db"
).addMigrations(
MIGRATION_11_12,
MIGRATION_12_13,
MIGRATION_13_14,
MIGRATION_14_15,
MIGRATION_15_16,
MIGRATION_16_17,
MIGRATION_17_18,
MIGRATION_18_19,
MIGRATION_19_20,
MIGRATION_20_21,
MIGRATION_21_22,
MIGRATION_22_23,
MIGRATION_23_24,
MIGRATION_24_25,
MIGRATION_25_26,
MIGRATION_26_27,
MIGRATION_27_28,
MIGRATION_28_29,
MIGRATION_29_30,
MIGRATION_30_31,
MIGRATION_31_32,
MIGRATION_32_33,
MIGRATION_33_34,
MIGRATION_34_35,
MIGRATION_35_36,
MIGRATION_36_37,
MIGRATION_37_38,
MIGRATION_38_39,
MIGRATION_39_40,
MIGRATION_40_41,
MIGRATION_41_42,
MIGRATION_42_43,
MIGRATION_43_44,
MIGRATION_44_45,
MIGRATION_45_46,
MIGRATION_46_47,
MIGRATION_47_48,
MIGRATION_48_49,
MIGRATION_49_50,
MIGRATION_50_51,
MIGRATION_51_52,
MIGRATION_52_53,
MIGRATION_53_54,
MIGRATION_54_55,
MIGRATION_55_56,
MIGRATION_56_57,
MIGRATION_57_58,
MIGRATION_58_59,
MIGRATION_59_60,
MIGRATION_60_61,
MIGRATION_61_62,
MIGRATION_62_63,
MIGRATION_63_64,
MIGRATION_64_65,
MIGRATION_65_66,
MIGRATION_66_67,
MIGRATION_67_68,
MIGRATION_68_69,
MIGRATION_69_70,
MIGRATION_70_71,
MIGRATION_71_72,
MIGRATION_72_73,
MIGRATION_73_74,
MIGRATION_74_75
).allowMainThreadQueries().build()
}
}

View File

@ -16,8 +16,8 @@ import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Announcement;
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ANNOUNCEMENT;
@ -69,4 +69,14 @@ public abstract class AnnouncementDao {
public List<AnnouncementFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT " +
"*, " +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " +
"FROM announcements " +
"LEFT JOIN teachers USING(profileId, teacherId) " +
"LEFT JOIN metadata ON announcementId = thingId AND thingType = "+TYPE_ANNOUNCEMENT+" AND metadata.profileId = announcements.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<AnnouncementFull> getNotNotifiedNow();
}

View File

@ -5,20 +5,20 @@
package pl.szczodrzynski.edziennik.data.db.dao;
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.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Attendance;
import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import java.util.List;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_ATTENDANCE;
@Dao
@ -72,6 +72,13 @@ public abstract class AttendanceDao {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT * FROM attendances " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN metadata ON attendanceId = thingId AND thingType = " + TYPE_ATTENDANCE + " AND metadata.profileId = attendances.profileId " +
"WHERE notified = 0 " +
"ORDER BY attendanceLessonDate DESC, attendanceStartTime DESC")
public abstract List<AttendanceFull> getNotNotifiedNow();
// only absent and absent_excused count as absences
// all the other types are counted as being present
@Query("SELECT \n" +

View File

@ -34,7 +34,7 @@ public abstract class EventDao {
public abstract long add(Event event);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void addAll(List<Event> eventList);
public abstract long[] addAll(List<Event> eventList);
@Query("DELETE FROM events WHERE profileId = :profileId")
public abstract void clear(int profileId);
@ -44,7 +44,7 @@ public abstract class EventDao {
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
public abstract void removeMetadata(int profileId, int thingType, long thingId);
@Transaction
public void remove(int profileId, int type, long id) {
public void remove(int profileId, long type, long id) {
remove(profileId, id);
removeMetadata(profileId, type == Event.TYPE_HOMEWORK ? TYPE_HOMEWORK : TYPE_EVENT, id);
}
@ -88,7 +88,7 @@ public abstract class EventDao {
public LiveData<List<EventFull>> getAllWhere(int profileId, String filter) {
return getAll(profileId, filter);
}
public LiveData<List<EventFull>> getAllByType(int profileId, int type, String filter) {
public LiveData<List<EventFull>> getAllByType(int profileId, long type, String filter) {
return getAll(profileId, "eventType = "+type+" AND "+filter);
}
public LiveData<List<EventFull>> getAllByDate(int profileId, @NonNull Date date) {
@ -125,6 +125,24 @@ public abstract class EventDao {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds(int profileId);
@Query("SELECT eventId FROM events WHERE eventBlacklisted = 1")
public abstract List<Long> getBlacklistedIds();
@Query("SELECT " +
"*, " +
"eventTypes.eventTypeName AS typeName, " +
"eventTypes.eventTypeColor AS typeColor " +
"FROM events " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN eventTypes USING(profileId, eventType) " +
"LEFT JOIN metadata ON eventId = thingId AND (thingType = " + TYPE_EVENT + " OR thingType = " + TYPE_HOMEWORK + ") AND metadata.profileId = events.profileId " +
"WHERE events.eventBlacklisted = 0 AND notified = 0 " +
"GROUP BY eventId " +
"ORDER BY addedDate ASC")
public abstract List<EventFull> getNotNotifiedNow();
public EventFull getByIdNow(int profileId, long eventId) {
List<EventFull> eventList = getAllNow(profileId, "eventId = "+eventId);
return eventList.size() == 0 ? null : eventList.get(0);
@ -146,13 +164,13 @@ public abstract class EventDao {
}
@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);
public abstract void removeFutureWithType(int profileId, Date todayDate, long 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);
public abstract void removeFutureExceptType(int profileId, Date todayDate, long exceptType);
@Transaction
public void removeFutureExceptTypes(int profileId, Date todayDate, List<Integer> exceptTypes) {
public void removeFutureExceptTypes(int profileId, Date todayDate, List<Long> exceptTypes) {
removeFuture(profileId, todayDate, "eventType NOT IN " + exceptTypes.toString().replace('[', '(').replace(']', ')'));
}

View File

@ -4,14 +4,14 @@
package pl.szczodrzynski.edziennik.data.db.dao;
import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.EventType;
@Dao
@ -26,11 +26,14 @@ public interface EventTypeDao {
void clear(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId AND eventType = :typeId")
EventType getByIdNow(int profileId, int typeId);
EventType getByIdNow(int profileId, long typeId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
LiveData<List<EventType>> getAll(int profileId);
@Query("SELECT * FROM eventTypes WHERE profileId = :profileId")
List<EventType> getAllNow(int profileId);
@Query("SELECT * FROM eventTypes")
List<EventType> getAllNow();
}

View File

@ -83,6 +83,13 @@ public abstract class GradeDao {
return getAllNow(profileId, "gradeParentId = "+parentId);
}
@Query("SELECT * FROM grades " +
"LEFT JOIN subjects USING(profileId, subjectId) " +
"LEFT JOIN metadata ON gradeId = thingId AND thingType = " + TYPE_GRADE + " AND metadata.profileId = grades.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<GradeFull> getNotNotifiedNow();
@RawQuery
abstract GradeFull getNow(SupportSQLiteQuery query);
public GradeFull getNow(int profileId, String filter) {

View File

@ -20,4 +20,7 @@ interface LibrusLessonDao {
@Query("SELECT * FROM librusLessons WHERE profileId = :profileId")
fun getAllNow(profileId: Int): List<LibrusLesson>
@Query("DELETE FROM librusLessons WHERE profileId = :profileId")
fun clear(profileId: Int)
}

View File

@ -17,9 +17,9 @@ import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber;
import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
import pl.szczodrzynski.edziennik.data.db.entity.Notice;
import pl.szczodrzynski.edziennik.data.db.full.LuckyNumberFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import static pl.szczodrzynski.edziennik.data.db.entity.Metadata.TYPE_LUCKY_NUMBER;
@ -78,4 +78,10 @@ public abstract class LuckyNumberDao {
public List<LuckyNumberFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT * FROM luckyNumbers\n" +
"LEFT JOIN metadata ON luckyNumberDate = thingId AND thingType = "+TYPE_LUCKY_NUMBER+" AND metadata.profileId = luckyNumbers.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<LuckyNumberFull> getNotNotifiedNow();
}

View File

@ -17,8 +17,8 @@ import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.entity.Message;
import pl.szczodrzynski.edziennik.data.db.full.MessageFull;
import pl.szczodrzynski.edziennik.data.db.entity.Metadata;
import pl.szczodrzynski.edziennik.data.db.full.MessageFull;
import static pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_DELETED;
import static pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED;
@ -101,4 +101,14 @@ public abstract class MessageDao {
public List<MessageFull> getReceivedNotNotifiedNow(int profileId) {
return getReceivedNow(profileId, "notified = 0");
}
@Query("SELECT " +
"*, " +
"teachers.teacherName || ' ' || teachers.teacherSurname AS senderFullName " +
"FROM messages " +
"LEFT JOIN teachers ON teachers.profileId = messages.profileId AND teacherId = senderId " +
"LEFT JOIN metadata ON messageId = thingId AND thingType = "+TYPE_MESSAGE+" AND metadata.profileId = messages.profileId " +
"WHERE messageType = 0 AND notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<MessageFull> getReceivedNotNotifiedNow();
}

View File

@ -40,6 +40,9 @@ public abstract class MetadataDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void addAllIgnore(List<Metadata> metadataList);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract void addAllReplace(List<Metadata> metadataList);
@Query("UPDATE metadata SET seen = :seen WHERE thingId = :thingId AND thingType = :thingType AND profileId = :profileId")
abstract void updateSeen(int profileId, int thingType, long thingId, boolean seen);
@ -165,6 +168,9 @@ public abstract class MetadataDao {
@Query("UPDATE metadata SET notified = :notified WHERE profileId = :profileId")
public abstract void setAllNotified(int profileId, boolean notified);
@Query("UPDATE metadata SET notified = :notified")
public abstract void setAllNotified(boolean notified);
@Query("SELECT count() FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND seen = 0")

View File

@ -5,13 +5,13 @@
package pl.szczodrzynski.edziennik.data.db.dao;
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.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import java.util.List;
@ -69,4 +69,14 @@ public abstract class NoticeDao {
public List<NoticeFull> getNotNotifiedNow(int profileId) {
return getAllNow(profileId, "notified = 0");
}
@Query("SELECT " +
"*, " +
"teachers.teacherName || ' ' || teachers.teacherSurname AS teacherFullName " +
"FROM notices " +
"LEFT JOIN teachers USING(profileId, teacherId) " +
"LEFT JOIN metadata ON noticeId = thingId AND thingType = "+TYPE_NOTICE+" AND metadata.profileId = notices.profileId " +
"WHERE notified = 0 " +
"ORDER BY addedDate DESC")
public abstract List<NoticeFull> getNotNotifiedNow();
}

View File

@ -52,9 +52,12 @@ interface ProfileDao {
@Query("SELECT profiles.* FROM teams JOIN profiles USING(profileId) WHERE teamCode = :teamCode AND registration = " + Profile.REGISTRATION_ENABLED + " AND enableSharedEvents = 1")
fun getByTeamCodeNowWithRegistration(teamCode: String?): List<Profile>
@get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId ASC LIMIT 1")
@get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId ASC LIMIT 1")
val firstId: Int?
@get:Query("SELECT profileId FROM profiles WHERE profileId >= 0 ORDER BY profileId DESC LIMIT 1")
@get:Query("SELECT profileId FROM profiles WHERE profileId > 0 ORDER BY profileId DESC LIMIT 1")
val lastId: Int?
@Query("UPDATE profiles SET empty = 0")
fun setAllNotEmpty()
}

View File

@ -107,4 +107,18 @@ interface TimetableDao {
WHERE timetable.profileId = :profileId AND timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0
""")
fun getNotNotifiedNow(profileId: Int): List<LessonFull>
@Query("""
SELECT
timetable.*,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
metadata.seen, metadata.notified, metadata.addedDate
FROM timetable
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
WHERE timetable.type NOT IN (${Lesson.TYPE_NORMAL}, ${Lesson.TYPE_NO_LESSONS}, ${Lesson.TYPE_SHIFTED_SOURCE}) AND metadata.notified = 0
""")
fun getNotNotifiedNow(): List<LessonFull>
}

View File

@ -32,19 +32,19 @@ public class Event {
public String topic;
@ColumnInfo(name = "eventColor")
public int color = -1;
public static final int TYPE_UNDEFINED = -2;
public static final int TYPE_HOMEWORK = -1;
public static final int TYPE_DEFAULT = 0;
public static final int TYPE_EXAM = 1;
public static final int TYPE_SHORT_QUIZ = 2;
public static final int TYPE_ESSAY = 3;
public static final int TYPE_PROJECT = 4;
public static final int TYPE_PT_MEETING = 5;
public static final int TYPE_EXCURSION = 6;
public static final int TYPE_READING = 7;
public static final int TYPE_CLASS_EVENT = 8;
public static final int TYPE_INFORMATION = 9;
public static final int TYPE_TEACHER_ABSENCE = 10;
public static final long TYPE_UNDEFINED = -2;
public static final long TYPE_HOMEWORK = -1;
public static final long TYPE_DEFAULT = 0;
public static final long TYPE_EXAM = 1;
public static final long TYPE_SHORT_QUIZ = 2;
public static final long TYPE_ESSAY = 3;
public static final long TYPE_PROJECT = 4;
public static final long TYPE_PT_MEETING = 5;
public static final long TYPE_EXCURSION = 6;
public static final long TYPE_READING = 7;
public static final long TYPE_CLASS_EVENT = 8;
public static final long TYPE_INFORMATION = 9;
public static final long TYPE_TEACHER_ABSENCE = 10;
public static final int COLOR_HOMEWORK = 0xff795548;
public static final int COLOR_DEFAULT = 0xffffc107;
public static final int COLOR_EXAM = 0xfff44336;
@ -58,7 +58,7 @@ public class Event {
public static final int COLOR_INFORMATION = 0xff039be5;
public static final int COLOR_TEACHER_ABSENCE = 0xff039be5;
@ColumnInfo(name = "eventType")
public int type = TYPE_DEFAULT;
public long type = TYPE_DEFAULT;
@ColumnInfo(name = "eventAddedManually")
public boolean addedManually;
@ColumnInfo(name = "eventSharedBy")
@ -76,7 +76,7 @@ public class Event {
@Ignore
public Event() {}
public Event(int profileId, long id, Date eventDate, @Nullable Time startTime, String topic, int color, int type, boolean addedManually, long teacherId, long subjectId, long teamId)
public Event(int profileId, long id, Date eventDate, @Nullable Time startTime, String topic, int color, long type, boolean addedManually, long teacherId, long subjectId, long teamId)
{
this.profileId = profileId;
this.id = id;
@ -97,8 +97,8 @@ public class Event {
}
@Override
public Event clone() throws CloneNotSupportedException {
return new Event(
public Event clone() {
Event event = new Event(
profileId,
id,
eventDate.clone(),
@ -111,6 +111,10 @@ public class Event {
teacherId,
teamId
);
event.sharedBy = sharedBy;
event.sharedByName = sharedByName;
event.blacklisted = blacklisted;
return event;
}
@Override

View File

@ -6,7 +6,6 @@ package pl.szczodrzynski.edziennik.data.db.entity
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
@ -14,8 +13,9 @@ import pl.szczodrzynski.edziennik.utils.models.Time
indices = [
Index(value = ["profileId", "type", "date"]),
Index(value = ["profileId", "type", "oldDate"])
])
open class Lesson(val profileId: Int, @PrimaryKey var id: Long) {
],
primaryKeys = ["profileId", "id"])
open class Lesson(val profileId: Int, var id: Long) {
companion object {
const val TYPE_NO_LESSONS = -1
const val TYPE_NORMAL = 0

View File

@ -4,34 +4,18 @@
package pl.szczodrzynski.edziennik.data.db.entity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_AUTO_ARCHIVING
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_ERROR
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_FEEDBACK_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_GENERAL
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_LUCKY_NUMBER
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ANNOUNCEMENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_ATTENDANCE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_GRADE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_NOTICE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_NEW_SHARED_EVENT
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_SERVER_MESSAGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_TIMETABLE_CHANGED
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_TIMETABLE_LESSON_CHANGE
import pl.szczodrzynski.edziennik.data.db.entity.Notification.Companion.TYPE_UPDATE
import pl.szczodrzynski.edziennik.MainActivity
@Entity(tableName = "notifications")
data class Notification(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val id: Long = 0,
val title: String,
val text: String,
@ -41,7 +25,7 @@ data class Notification(
val profileId: Int?,
val profileName: String?,
var posted: Boolean = false,
var posted: Boolean = true,
var viewId: Int? = null,
var extras: JsonObject? = null,
@ -59,6 +43,7 @@ data class Notification(
const val TYPE_NEW_HOMEWORK = 10
const val TYPE_NEW_SHARED_EVENT = 7
const val TYPE_NEW_SHARED_HOMEWORK = 12
const val TYPE_REMOVED_SHARED_EVENT = 18
const val TYPE_NEW_MESSAGE = 8
const val TYPE_NEW_NOTICE = 9
const val TYPE_NEW_ATTENDANCE = 13
@ -67,6 +52,10 @@ data class Notification(
const val TYPE_NEW_ANNOUNCEMENT = 15
const val TYPE_FEEDBACK_MESSAGE = 16
const val TYPE_AUTO_ARCHIVING = 17
fun buildId(profileId: Int, type: Int, itemId: Long): Long {
return 1000000000000 + profileId*10000000000 + type*100000000 + itemId;
}
}
fun addExtra(key: String, value: Long?): Notification {
@ -100,4 +89,10 @@ data class Notification(
e.printStackTrace()
}
}
fun getPendingIntent(context: Context): PendingIntent {
val intent = Intent(context, MainActivity::class.java)
fillIntent(intent)
return PendingIntent.getActivity(context, id.toInt(), intent, PendingIntent.FLAG_ONE_SHOT)
}
}

View File

@ -30,21 +30,21 @@ open class Profile(
val loginStoreId: Int,
val loginStoreType: Int,
override var name: String,
override var subname: String?,
override var name: String = "",
override var subname: String? = null,
/**
* The name of the student.
* This doesn't change, no matter if it's a parent or student account.
*/
var studentNameLong: String,
var studentNameShort: String,
var studentNameLong: String = "",
var studentNameShort: String = "",
/**
* A full name of the account owner.
* If null, then it's a student account.
* If not null, then it's a parent account with this name.
*/
var accountName: String?,
var accountName: String? = null,
val studentData: JsonObject = JsonObject()

View File

@ -45,6 +45,35 @@ public class EventFull extends Event {
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
}
public EventFull(EventFull event) {
super(
event.profileId,
event.id,
event.eventDate.clone(),
event.startTime == null ? null : event.startTime.clone(),
event.topic,
event.color,
event.type,
event.addedManually,
event.teacherId,
event.subjectId,
event.teamId
);
this.sharedBy = event.sharedBy;
this.sharedByName = event.sharedByName;
this.blacklisted = event.blacklisted;
this.typeName = event.typeName;
this.typeColor = event.typeColor;
this.teacherFullName = event.teacherFullName;
this.subjectLongName = event.subjectLongName;
this.subjectShortName = event.subjectShortName;
this.teamName = event.teamName;
this.teamCode = event.teamCode;
this.seen = event.seen;
this.notified = event.notified;
this.addedDate = event.addedDate;
}
public EventFull(Event event, Metadata metadata) {
this(event);

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-11.
*/
package pl.szczodrzynski.edziennik.data.firebase
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.legacy.content.WakefulBroadcastReceiver
import com.google.gson.JsonObject
class FirebaseBroadcastReceiver : WakefulBroadcastReceiver() {
companion object {
private const val TAG = "FirebaseBroadcast"
}
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
val json = JsonObject()
extras?.keySet()?.forEach { key ->
extras.get(key)?.let {
when (it) {
is String -> json.addProperty(key, it)
is Int -> json.addProperty(key, it)
is Long -> json.addProperty(key, it)
is Float -> json.addProperty(key, it)
is Boolean -> json.addProperty(key, it)
else -> json.addProperty(key, it.toString())
}
}
}
Log.d(TAG, "Intent(action=${intent?.action}, extras=$json)")
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-11.
*/
package pl.szczodrzynski.edziennik.data.firebase
class FirebaseSendException(reason: String?) : Exception(reason) {
companion object {
const val ERROR_UNKNOWN = 0
const val ERROR_INVALID_PARAMETERS = 1
const val ERROR_SIZE = 2
const val ERROR_TTL_EXCEEDED = 3
const val ERROR_TOO_MANY_MESSAGES = 4
}
val errorCode = when (reason) {
"service_not_available" -> ERROR_TTL_EXCEEDED
"toomanymessages" -> ERROR_TOO_MANY_MESSAGES
"invalid_parameters" -> ERROR_INVALID_PARAMETERS
"messagetoobig" -> ERROR_SIZE
"missing_to" -> ERROR_INVALID_PARAMETERS
else -> ERROR_UNKNOWN
}
}

View File

@ -0,0 +1,193 @@
/*
* Copyright (c) Kuba Szczodrzyński 2020-1-11.
*/
package pl.szczodrzynski.edziennik.data.firebase
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.PendingIntent.CanceledException
import android.content.Intent
import android.util.Log
import com.google.firebase.iid.zzaq
import com.google.firebase.iid.zzv
import com.google.firebase.messaging.zzc
import com.google.gson.JsonObject
import pl.szczodrzynski.edziennik.*
import java.util.*
import com.google.firebase.messaging.zzo.zza as logNotificationOpen
import com.google.firebase.messaging.zzo.zza as logNotificationReceived
import com.google.firebase.messaging.zzo.zzb as logNotificationDismiss
import com.google.firebase.messaging.zzo.zzd as shouldUploadMetrics
@SuppressLint("Registered")
open class FirebaseService : zzc() {
companion object {
private const val TAG = "FirebaseService"
}
private val messageQueue = ArrayDeque<String>(10)
open fun onMessageReceived(message: Message) = Unit
open fun onDeletedMessages() = Unit
open fun onMessageSent(messageId: String?) = Unit
open fun onSendError(messageId: String?, exception: Exception) = Unit
open fun onNewToken(token: String?) = Unit
// apparently this gets the correct intent from some
// kind of queue inside Firebase's InstanceID Receiver
final override fun zza(intent: Intent?) = zzaq.zza()?.zzb()
final override fun zzb(intent: Intent?): Boolean {
val action = intent?.action
if (action == "com.google.firebase.messaging.NOTIFICATION_OPEN") {
intent.getParcelableExtra<PendingIntent>("pending_intent")?.let {
try {
it.send()
} catch (e: CanceledException) {
Log.e(TAG, "Notification pending intent canceled")
}
}
if (shouldUploadMetrics(intent)) {
logNotificationOpen(intent)
}
return true
}
return false
}
final override fun zzc(intent: Intent?) {
val action = intent?.action
val json = intent?.toJsonObject()
Log.d(TAG, "zzc Intent(action=$action, extras=$json)")
if (action == null || json == null)
return
when (action) {
"com.google.firebase.messaging.NOTIFICATION_DISMISS" -> {
if (shouldUploadMetrics(intent)) {
logNotificationDismiss(intent)
}
}
"com.google.firebase.messaging.NEW_TOKEN" -> {
onNewToken(json.getString("token"))
}
"com.google.android.c2dm.intent.RECEIVE",
"com.google.firebase.messaging.RECEIVE_DIRECT_BOOT" -> {
val messageId = json.getString("google.message_id")
if (messageId != null) {
// send back an acknowledgement to Google Play Services
val ackBundle = Bundle(
"google.message_id" to messageId
)
zzv.zza(this).zza(2, ackBundle)
}
// check for duplicate message
// and add it to queue
if (messageId.isNotNullNorEmpty()) {
if (messageQueue.contains(messageId)) {
Log.d(TAG, "Received duplicate message: $messageId")
return
}
if (messageQueue.size >= 10)
messageQueue.remove()
messageQueue += messageId
}
// process the received message
processMessage(messageId, json, intent)
}
else -> {
Log.d(TAG, "Unknown intent action: $action")
}
}
}
private fun processMessage(messageId: String?, json: JsonObject, intent: Intent) {
// remove something that the original FMS removes
json.remove("androidx.contentpager.content.wakelockid")
// get the message type
when (val it = json.getString("message_type") ?: "gcm") {
"gcm" -> { // 0
if (shouldUploadMetrics(intent)) {
logNotificationReceived(intent, null)
}
onMessageReceived(Message(messageId, json))
}
"deleted_messages" -> { // 1
onDeletedMessages()
}
"send_event" -> { // 2
onMessageSent(messageId)
}
"send_error" -> { // 3
onSendError(
messageId ?: json.getString("message_id"),
FirebaseSendException(json.getString("error"))
)
}
else -> {
Log.w(TAG, "Received message with unknown type: $it")
return
}
}
}
data class Message(val messageId: String?, private val json: JsonObject) {
val data = json.deepCopy()
val from by lazy { s("test.from") ?: s("from") ?: "" }
val to by lazy { s("google.to") }
val messageType by lazy { s("message_type") }
val collapseKey by lazy { s("collapse_key") }
val sentTime by lazy { l("google.sent_time") }
val ttl by lazy { i("google.ttl") }
val originalPriority by lazy { getPriority(s("google.original_priority") ?: s("priority")) }
val priority by lazy { getPriority(
s("google.delivered_priority") ?: if (i("google.priority_reduced") == 1)
"normal"
else s("google.priority")
) }
val isNotificationMessage by lazy { isNotificationMessage(json) }
val notificationTitle by lazy { s("gcm.notification.title") }
val notificationText by lazy { s("gcm.notification.body") }
init {
data.also {
val toRemove = mutableListOf<String>()
it.keySet().forEach { key ->
if (key.startsWith("google.")
|| key.startsWith("gcm.")
|| key == "from"
|| key == "message_type"
|| key == "collapse_key")
toRemove += key
}
toRemove.forEach { key ->
it.remove(key)
}
}
}
private fun s(key: String): String? = json.getString(key)
private fun l(key: String): Long = json.getLong(key) ?: 0L
private fun i(key: String): Int = json.getInt(key) ?: 0
private fun isNotificationMessage(json: JsonObject): Boolean {
return json.getInt("gcm.n.e") == 1
|| json.getInt("gcm.notification.e") == 1
|| json.getString("gcm.n.icon") != null
|| json.getString("gcm.notification.icon") != null
}
private fun getPriority(str: String?): Int {
return when (str) {
"high" -> 1
"normal" -> 2
else -> 0
}
}
override fun toString(): String {
return "Message(messageId=$messageId, from=$from, data=$data)"
}
}
}

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