Compare commits

..

34 commits

Author SHA1 Message Date
Kuba Szczodrzyński
9b7aca745a [3.9.9-dev] 2019-11-21 19:18:04 +01:00
Kuba Szczodrzyński
82852389fa [UI] Update indentation, again. Fix manual event color selecting. 2019-11-21 18:43:01 +01:00
Kuba Szczodrzyński
ce06084e6f [Timetable] Fix lessons removing, again. 2019-11-21 18:24:02 +01:00
Kacper Ziubryniewicz
3ca051983f [APIv2/Timetable] Add searching for the next lesson by team id 2019-11-20 22:43:48 +01:00
Kacper Ziubryniewicz
cd379e4175 [APIv2/Librus] Add getting grade comments 2019-11-20 21:16:18 +01:00
Kuba Szczodrzyński
62fdfa2b6f [UI] Update manual event dialog. Fix timetable errors. 2019-11-20 21:13:43 +01:00
Kuba Szczodrzyński
9866017f7e [APIv2/Vulcan] Fix timetable teams issue. Fix missing login data error. 2019-11-20 19:51:50 +01:00
Kuba Szczodrzyński
67f98b08c6 [Update] Update update code to allow update from direct download. 2019-11-20 17:11:08 +01:00
Kuba Szczodrzyński
fdb5f7ec02 [APIv2/Mobidziennik] Implement getting message details. 2019-11-18 22:57:23 +01:00
Kuba Szczodrzyński
04c3c7ca6e [APIv2] Handle Librus Portal maintenance. 2019-11-18 18:55:52 +01:00
Kuba Szczodrzyński
f424315d97 [3.9.8-dev] 2019-11-17 23:17:37 +01:00
Kuba Szczodrzyński
c907a8df37 [APIv2] Librus: better error handling. Timetable: fix widget crashing with NPE. 2019-11-17 23:16:13 +01:00
Kuba Szczodrzyński
37ea65e3fc [Timetable] Add SwipeToRefresh. Select start&end hours based on lesson ranges. 2019-11-16 21:16:18 +01:00
Kuba Szczodrzyński
a3e5f824c8 [UI] Fix messages & homework refresh layout sensitivity. 2019-11-16 18:46:01 +01:00
Kuba Szczodrzyński
e0c850a455 [Gradle] Update Tachyon to support creating DayView programmatically. 2019-11-16 17:07:07 +01:00
Kuba Szczodrzyński
1c6815f708 [3.9.7-dev] 2019-11-15 22:00:18 +01:00
Kuba Szczodrzyński
9a20511935 [UI] Set pull-to-refresh colors. 2019-11-15 21:16:58 +01:00
Kuba Szczodrzyński
965f5e73d9 [Bugs] Update gradle. Fix crashes in the timetable widget. 2019-11-15 19:55:46 +01:00
Kuba Szczodrzyński
13b58a1d56 [3.9.6-dev] Widgets. Timetables. RIP APIv1. 2019-11-15 00:02:00 +01:00
Kuba Szczodrzyński
0a3261b8b3 [APIv2/Mobidziennik] Fix incorrect profile ID in teams & timetable. 2019-11-14 23:46:53 +01:00
Kuba Szczodrzyński
dbdfc7fdd8 [Widget] Add new Timetable widget with APIv2. 2019-11-14 23:33:13 +01:00
Kuba Szczodrzyński
56062f5bfa [Timetable] Fix day fragment crashing on restoring saved instance 2019-11-14 22:13:32 +01:00
Kuba Szczodrzyński
0cbba2eb45 [Models] Make Time comparable. Implement faster Date.stepForward 2019-11-14 19:31:50 +01:00
Kacper Ziubryniewicz
aa84356dd6 [APIv2/Vulcan] Add getting timetable with lesson changes 2019-11-14 00:41:34 +01:00
Kuba Szczodrzyński
efaad0a4dd [APIv1] Remove remaining APIv1 components. [*] 2019-11-13 22:37:30 +01:00
Kuba Szczodrzyński
71015c0137 [APIv1] Remove most APIv1 components. 2019-11-13 22:26:12 +01:00
Kuba Szczodrzyński
85b5667a7e [Sync] New manual sync dialog. Remove most APIv1 dependencies. 2019-11-13 21:57:47 +01:00
Kacper Ziubryniewicz
dfdc6817a1 [APIv2/Vulcan] Fix getting sent messages with unknown recipient 2019-11-13 21:01:09 +01:00
Kuba Szczodrzyński
058345b9c9 [Error] Add user friendly error strings. Add error snackbar to activity & login. 2019-11-13 19:44:08 +01:00
Kuba Szczodrzyński
831b7876b4 [APIv2] Fix duplicated error code 2019-11-13 18:23:21 +01:00
Kuba Szczodrzyński
729cf6f08e [Settings] New Profile removal dialog. 2019-11-13 17:19:25 +01:00
Kuba Szczodrzyński
860a16b32d [3.9.5-dev] Messages & manual events 2019-11-13 17:19:25 +01:00
Kacper Ziubryniewicz
9f871c077b [APIv2/Librus] Fix getting messages with unknown recipient 2019-11-13 00:00:28 +01:00
Kacper Ziubryniewicz
a8f89abf7d [APIv2/Vulcan] Add changing message status 2019-11-12 23:59:48 +01:00
109 changed files with 3163 additions and 13121 deletions

View file

@ -169,7 +169,7 @@ dependencies {
implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584' implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584'
implementation 'com.linkedin.android.tachyon:tachyon:1.0.2' implementation 'com.github.kuba2k2:Tachyon:551943a6b5'
} }
repositories { repositories {
mavenCentral() mavenCentral()

View file

@ -105,6 +105,11 @@
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:noHistory="true" android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" /> android:theme="@style/AppTheme.NoDisplay" />
<activity android:name=".widgets.timetable.LessonDialogActivity"
android:configChanges="orientation|keyboardHidden"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@style/AppTheme.NoDisplay" />
<activity <activity
android:name=".ui.modules.settings.SettingsLicenseActivity" android:name=".ui.modules.settings.SettingsLicenseActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"

View file

@ -68,11 +68,6 @@ import me.leolin.shortcutbadger.ShortcutBadger;
import okhttp3.ConnectionSpec; import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.TlsVersion; import okhttp3.TlsVersion;
import pl.szczodrzynski.edziennik.data.api.Edziennik;
import pl.szczodrzynski.edziennik.data.api.Iuczniowie;
import pl.szczodrzynski.edziennik.data.api.Librus;
import pl.szczodrzynski.edziennik.data.api.Mobidziennik;
import pl.szczodrzynski.edziennik.data.api.Vulcan;
import pl.szczodrzynski.edziennik.data.db.AppDb; import pl.szczodrzynski.edziennik.data.db.AppDb;
import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog; import pl.szczodrzynski.edziennik.data.db.modules.debuglog.DebugLog;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore; import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
@ -141,12 +136,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
public PersistentCookieJar cookieJar; public PersistentCookieJar cookieJar;
public OkHttpClient http; public OkHttpClient http;
public OkHttpClient httpLazy; public OkHttpClient httpLazy;
//public Jakdojade apiJakdojade;
public Edziennik apiEdziennik;
public Mobidziennik apiMobidziennik;
public Iuczniowie apiIuczniowie;
public Librus apiLibrus;
public Vulcan apiVulcan;
public SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE public SharedPreferences appSharedPrefs; // sharedPreferences for APPCONFIG + JOBS STORE
public AppConfig appConfig; // APPCONFIG: common for all profiles public AppConfig appConfig; // APPCONFIG: common for all profiles
@ -204,12 +193,6 @@ public class App extends androidx.multidex.MultiDexApplication implements Config
db = AppDb.getDatabase(this); db = AppDb.getDatabase(this);
gson = new Gson(); gson = new Gson();
networkUtils = new NetworkUtils(this); networkUtils = new NetworkUtils(this);
apiEdziennik = new Edziennik(this);
//apiJakdojade = new Jakdojade(this);
apiMobidziennik = new Mobidziennik(this);
apiIuczniowie = new Iuczniowie(this);
apiLibrus = new Librus(this);
apiVulcan = new Vulcan(this);
Iconics.init(getApplicationContext()); Iconics.init(getApplicationContext());
Iconics.registerFont(SzkolnyFont.INSTANCE); Iconics.registerFont(SzkolnyFont.INSTANCE);

View file

@ -4,7 +4,9 @@ import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.* import android.text.*
@ -13,9 +15,10 @@ import android.text.style.StrikethroughSpan
import android.text.style.StyleSpan import android.text.style.StyleSpan
import android.util.LongSparseArray import android.util.LongSparseArray
import android.util.SparseArray import android.util.SparseArray
import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes import androidx.annotation.*
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.util.forEach import androidx.core.util.forEach
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -95,6 +98,15 @@ fun String.swapFirstLastName(): String {
} }
} }
fun String.getFirstLastName(): Pair<String, String>? {
return this.split(" ").let {
if (it.size >= 2) Pair(it[0], it[1])
else null
}
}
fun String.getLastFirstName() = this.getFirstLastName()
fun changeStringCase(s: String): String { fun changeStringCase(s: String): String {
val delimiters = " '-/" val delimiters = " '-/"
val sb = StringBuilder() val sb = StringBuilder()
@ -153,8 +165,9 @@ fun colorFromName(context: Context, name: String?): Int {
return context.getColorFromRes(color) return context.getColorFromRes(color)
} }
fun MutableList<out Profile>.filterOutArchived() { fun MutableList<Profile>.filterOutArchived(): MutableList<Profile> {
this.removeAll { it.archived } this.removeAll { it.archived }
return this
} }
fun Activity.isStoragePermissionGranted(): Boolean { fun Activity.isStoragePermissionGranted(): Boolean {
@ -436,3 +449,49 @@ fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
} }
}) })
} }
/**
* Convert a value in dp to pixels.
*/
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
/**
* Convert a value in pixels to dp.
*/
val Int.px: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
@ColorInt
fun @receiver:AttrRes Int.resolveAttr(context: Context?): Int {
val typedValue = TypedValue()
context?.theme?.resolveAttribute(this, typedValue, true)
return typedValue.data
}
@ColorInt
fun @receiver:ColorRes Int.resolveColor(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getColor(this, context.theme)
}
else {
context.resources.getColor(this)
}
}
fun @receiver:DrawableRes Int.resolveDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
context.resources.getDrawable(this, context.theme)
}
else {
context.resources.getDrawable(this)
}
}
fun View.findParentById(targetId: Int): View? {
if (id == targetId) {
return this
}
val viewParent = this.parent ?: return null
if (viewParent is View) {
return viewParent.findParentById(targetId)
}
return null
}

View file

@ -38,18 +38,20 @@ import pl.droidsonroids.gif.GifDrawable
import pl.szczodrzynski.edziennik.App.APP_URL import pl.szczodrzynski.edziennik.App.APP_URL
import pl.szczodrzynski.edziennik.api.v2.events.* import pl.szczodrzynski.edziennik.api.v2.events.*
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface.*
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.* import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata.*
import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding
import pl.szczodrzynski.edziennik.network.ServerRequest import pl.szczodrzynski.edziennik.network.ServerRequest
import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent
import pl.szczodrzynski.edziennik.sync.SyncWorker import pl.szczodrzynski.edziennik.sync.SyncWorker
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog
import pl.szczodrzynski.edziennik.ui.dialogs.sync.SyncViewListDialog
import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment import pl.szczodrzynski.edziennik.ui.modules.agenda.AgendaFragment
import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment import pl.szczodrzynski.edziennik.ui.modules.announcements.AnnouncementsFragment
import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment
import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment
import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment import pl.szczodrzynski.edziennik.ui.modules.behaviour.BehaviourFragment
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.FeedbackFragment
import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment import pl.szczodrzynski.edziennik.ui.modules.feedback.HelpFragment
import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment
@ -214,6 +216,7 @@ class MainActivity : AppCompatActivity() {
val navView: NavView by lazy { b.navView } val navView: NavView by lazy { b.navView }
val drawer: NavDrawer by lazy { navView.drawer } val drawer: NavDrawer by lazy { navView.drawer }
val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet } val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet }
val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout }
@ -246,6 +249,8 @@ class MainActivity : AppCompatActivity() {
setContentView(b.root) setContentView(b.root)
errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar)
navLoading = true navLoading = true
b.navView.apply { b.navView.apply {
@ -371,6 +376,11 @@ class MainActivity : AppCompatActivity() {
b.swipeRefreshLayout.isEnabled = true b.swipeRefreshLayout.isEnabled = true
b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() } b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() }
b.swipeRefreshLayout.setColorSchemeResources(
R.color.md_blue_500,
R.color.md_amber_500,
R.color.md_green_500
)
isStoragePermissionGranted() isStoragePermissionGranted()
@ -464,7 +474,7 @@ class MainActivity : AppCompatActivity() {
.withIcon(CommunityMaterial.Icon2.cmd_sync) .withIcon(CommunityMaterial.Icon2.cmd_sync)
.withOnClickListener(View.OnClickListener { .withOnClickListener(View.OnClickListener {
bottomSheet.close() bottomSheet.close()
app.apiEdziennik.guiSyncFeature(app, this, App.profileId, R.string.sync_dialog_title, R.string.sync_dialog_text, R.string.sync_done, fragmentToFeature(navTargetId)) SyncViewListDialog(this, navTargetId)
}), }),
BottomSheetSeparatorItem(false), BottomSheetSeparatorItem(false),
BottomSheetPrimaryItem(false) BottomSheetPrimaryItem(false)
@ -573,7 +583,12 @@ class MainActivity : AppCompatActivity() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncErrorEvent(event: ApiTaskErrorEvent) { fun onSyncErrorEvent(event: ApiTaskErrorEvent) {
navView.toolbar.apply {
subtitleFormat = R.string.toolbar_subtitle
subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread
subtitle = "Gotowe"
}
errorSnackbar.addError(event.error).show()
} }
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true) @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) { fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) {
@ -605,22 +620,7 @@ class MainActivity : AppCompatActivity() {
.setCancelable(false) .setCancelable(false)
.show() .show()
} }
private fun fragmentToFeature(currentFragment: Int): Int {
return when (currentFragment) {
DRAWER_ITEM_TIMETABLE -> FEATURE_TIMETABLE
DRAWER_ITEM_AGENDA -> FEATURE_AGENDA
DRAWER_ITEM_GRADES -> FEATURE_GRADES
DRAWER_ITEM_HOMEWORK -> FEATURE_HOMEWORK
DRAWER_ITEM_BEHAVIOUR -> FEATURE_NOTICES
DRAWER_ITEM_ATTENDANCE -> FEATURE_ATTENDANCE
DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) {
1 -> FEATURE_MESSAGES_OUTBOX
else -> FEATURE_MESSAGES_INBOX
}
DRAWER_ITEM_ANNOUNCEMENTS -> FEATURE_ANNOUNCEMENTS
else -> FEATURE_ALL
}
}
private fun fragmentToSyncName(currentFragment: Int): Int { private fun fragmentToSyncName(currentFragment: Int): Int {
return when (currentFragment) { return when (currentFragment) {
DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable
@ -1043,7 +1043,7 @@ class MainActivity : AppCompatActivity() {
} }
loadTarget(DRAWER_ITEM_SETTINGS, null) loadTarget(DRAWER_ITEM_SETTINGS, null)
} else if (item.itemId == 2) { } else if (item.itemId == 2) {
app.apiEdziennik.guiRemoveProfile(this@MainActivity, profileId, profile.name?.getText(this).toString()) ProfileRemoveDialog(this, profileId, profile.name?.getText(this)?.toString() ?: "?")
} }
true true
} }

View file

@ -311,13 +311,14 @@ public class Notifier {
\____/| .__/ \__,_|\__,_|\__\___||___/ \____/| .__/ \__,_|\__,_|\__\___||___/
| | | |
|*/ |*/
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) { public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename, boolean updateDirect) {
if (!app.appConfig.notifyAboutUpdates) if (!app.appConfig.notifyAboutUpdates)
return; return;
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class) Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
.putExtra("update_version", updateVersion) .putExtra("update_version", updateVersion)
.putExtra("update_url", updateUrl) .putExtra("update_url", updateUrl)
.putExtra("update_filename", updateFilename); .putExtra("update_filename", updateFilename)
.putExtra("update_direct", updateDirect);
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);

View file

@ -1,450 +0,0 @@
package pl.szczodrzynski.edziennik;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.util.SparseArray;
import android.view.View;
import android.widget.RemoteViews;
import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonChange;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.models.Week;
import pl.szczodrzynski.edziennik.widgets.WidgetConfig;
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDetailsActivity;
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService;
import static pl.szczodrzynski.edziennik.ExtensionsKt.filterOutArchived;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.utils.Utils.bs;
public class WidgetTimetable extends AppWidgetProvider {
public static final String ACTION_SYNC_DATA = "ACTION_SYNC_DATA";
private static final String TAG = "WidgetTimetable";
private static int modeInt = 0;
public WidgetTimetable() {
// Start the worker thread
//HandlerThread sWorkerThread = new HandlerThread("WidgetTimetable-worker");
//sWorkerThread.start();
//Handler sWorkerQueue = new Handler(sWorkerThread.getLooper());
}
public static SparseArray<List<ItemWidgetTimetableModel>> timetables = null;
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_SYNC_DATA.equals(intent.getAction())) {
EdziennikTask.Companion.sync().enqueue(context);
}
super.onReceive(context, intent);
}
public static PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, WidgetTimetable.class);
intent.setAction(action);
return getPendingSelfIntent(context, intent);
}
public static PendingIntent getPendingSelfIntent(Context context, Intent intent) {
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
ComponentName thisWidget = new ComponentName(context, WidgetTimetable.class);
timetables = new SparseArray<>();
//timetables.clear();
App app = (App)context.getApplicationContext();
int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);
// There may be multiple widgets active, so update all of them
for (int appWidgetId : allWidgetIds) {
//d(TAG, "thr "+Thread.currentThread().getName());
WidgetConfig widgetConfig = app.appConfig.widgetTimetableConfigs.get(appWidgetId);
if (widgetConfig == null) {
widgetConfig = new WidgetConfig(app.profileFirstId());
app.appConfig.widgetTimetableConfigs.put(appWidgetId, widgetConfig);
app.appConfig.savePending = true;
}
RemoteViews views;
if (widgetConfig.bigStyle) {
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark_big : R.layout.widget_timetable_big);
}
else {
views = new RemoteViews(context.getPackageName(), widgetConfig.darkTheme ? R.layout.widget_timetable_dark : R.layout.widget_timetable);
}
PorterDuff.Mode mode = PorterDuff.Mode.DST_IN;
/*if (widgetConfig.darkTheme) {
switch (modeInt) {
case 0:
mode = PorterDuff.Mode.ADD;
d(TAG, "ADD");
break;
case 1:
mode = PorterDuff.Mode.DST_ATOP;
d(TAG, "DST_ATOP");
break;
case 2:
mode = PorterDuff.Mode.DST_IN;
d(TAG, "DST_IN");
break;
case 3:
mode = PorterDuff.Mode.DST_OUT;
d(TAG, "DST_OUT");
break;
case 4:
mode = PorterDuff.Mode.DST_OVER;
d(TAG, "DST_OVER");
break;
case 5:
mode = PorterDuff.Mode.LIGHTEN;
d(TAG, "LIGHTEN");
break;
case 6:
mode = PorterDuff.Mode.MULTIPLY;
d(TAG, "MULTIPLY");
break;
case 7:
mode = PorterDuff.Mode.OVERLAY;
d(TAG, "OVERLAY");
break;
case 8:
mode = PorterDuff.Mode.SCREEN;
d(TAG, "SCREEN");
break;
case 9:
mode = PorterDuff.Mode.SRC_ATOP;
d(TAG, "SRC_ATOP");
break;
case 10:
mode = PorterDuff.Mode.SRC_IN;
d(TAG, "SRC_IN");
break;
case 11:
mode = PorterDuff.Mode.SRC_OUT;
d(TAG, "SRC_OUT");
break;
case 12:
mode = PorterDuff.Mode.SRC_OVER;
d(TAG, "SRC_OVER");
break;
case 13:
mode = PorterDuff.Mode.XOR;
d(TAG, "XOR");
break;
default:
modeInt = 0;
mode = PorterDuff.Mode.ADD;
d(TAG, "ADD");
break;
}
}*/
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// this code seems to crash the launcher on >= P
float transparency = widgetConfig.opacity; //0...1
long colorFilter = 0x01000000L * (long) (255f * transparency);
try {
final Method[] declaredMethods = Class.forName("android.widget.RemoteViews").getDeclaredMethods();
final int len = declaredMethods.length;
if (len > 0) {
for (int m = 0; m < len; m++) {
final Method method = declaredMethods[m];
if (method.getName().equals("setDrawableParameters")) {
method.setAccessible(true);
method.invoke(views, R.id.widgetTimetableListView, true, -1, (int) colorFilter, mode, -1);
method.invoke(views, R.id.widgetTimetableHeader, true, -1, (int) colorFilter, mode, -1);
break;
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
Intent refreshIntent = new Intent(context, WidgetTimetable.class);
refreshIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
PendingIntent pendingRefreshIntent = PendingIntent.getBroadcast(context,
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent);
views.setOnClickPendingIntent(R.id.widgetTimetableSync, WidgetTimetable.getPendingSelfIntent(context, ACTION_SYNC_DATA));
views.setImageViewBitmap(R.id.widgetTimetableRefresh, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
views.setImageViewBitmap(R.id.widgetTimetableSync, new IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync)
.color(IconicsColor.colorInt(Color.WHITE))
.size(IconicsSize.dp(widgetConfig.bigStyle ? 24 : 16)).toBitmap());
boolean unified = widgetConfig.profileId == -1;
List<Profile> profileList = new ArrayList<>();
if (unified) {
profileList = app.db.profileDao().getAllNow();
filterOutArchived(profileList);
}
else {
Profile profile = app.db.profileDao().getFullByIdNow(widgetConfig.profileId);
if (profile != null) {
profileList.add(profile);
}
}
//d(TAG, "Profiles: "+ Arrays.toString(profileList.toArray()));
if (profileList == null || profileList.size() == 0) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist));
}
else {
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
//Register profile;
long bellSyncDiffMillis = 0;
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000;
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier;
bellSyncDiffMillis *= -1;
}
List<ItemWidgetTimetableModel> lessonList = new ArrayList<>();
Time syncedNow = Time.fromMillis(Time.getNow().getInMillis() + bellSyncDiffMillis);
Date today = Date.getToday();
int openProfileId = -1;
Date displayingDate = null;
int displayingWeekDay = 0;
if (unified) {
views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.widget_timetable_title_unified));
}
else {
views.setTextViewText(R.id.widgetTimetableSubtitle, profileList.get(0).getName());
openProfileId = profileList.get(0).getId();
}
List<LessonFull> lessons = app.db.lessonDao().getAllWeekNow(unified ? -1 : openProfileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today);
int scrollPos = 0;
for (Profile profile: profileList) {
Date profileDisplayingDate = HomeFragment.findDateWithLessons(profile.getId(), lessons, syncedNow, 1);
int profileDisplayingWeekDay = profileDisplayingDate.getWeekDay();
int dayDiff = Date.diffDays(profileDisplayingDate, Date.getToday());
//d(TAG, "For profile "+profile.name+" displayingDate is "+profileDisplayingDate.getStringY_m_d());
if (displayingDate == null || profileDisplayingDate.getValue() < displayingDate.getValue()) {
displayingDate = profileDisplayingDate;
displayingWeekDay = profileDisplayingWeekDay;
//d(TAG, "Setting as global dd");
if (dayDiff == 0) {
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_today_format, Week.getFullDayName(displayingWeekDay)));
} else if (dayDiff == 1) {
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(displayingWeekDay)));
} else {
views.setTextViewText(R.id.widgetTimetableTitle, Week.getFullDayName(displayingWeekDay) + " " + profileDisplayingDate.getStringDm());
}
}
}
for (Profile profile: profileList) {
int pos = 0;
List<EventFull> events = app.db.eventDao().getAllByDateNow(profile.getId(), displayingDate);
if (events == null)
events = new ArrayList<>();
if (unified) {
ItemWidgetTimetableModel separator = new ItemWidgetTimetableModel();
separator.profileId = profile.getId();
separator.bigStyle = widgetConfig.bigStyle;
separator.darkTheme = widgetConfig.darkTheme;
separator.separatorProfileName = profile.getName();
lessonList.add(separator);
}
for (LessonFull lesson : lessons) {
//d(TAG, "Profile "+profile.id+" Lesson profileId "+lesson.profileId+" weekDay "+lesson.weekDay+", "+lesson);
if (profile.getId() != lesson.profileId || displayingWeekDay != lesson.weekDay)
continue;
//d(TAG, "Not skipped");
ItemWidgetTimetableModel model = new ItemWidgetTimetableModel();
model.bigStyle = widgetConfig.bigStyle;
model.darkTheme = widgetConfig.darkTheme;
model.profileId = profile.getId();
model.lessonDate = displayingDate;
model.startTime = lesson.startTime;
model.endTime = lesson.endTime;
model.lessonPassed = (syncedNow.getValue() > lesson.endTime.getValue()) && displayingWeekDay == Week.getTodayWeekDay();
model.lessonCurrent = (Time.inRange(lesson.startTime, lesson.endTime, syncedNow)) && displayingWeekDay == Week.getTodayWeekDay();
if (model.lessonCurrent) {
scrollPos = pos;
} else if (model.lessonPassed) {
scrollPos = pos + 1;
}
pos++;
model.subjectName = bs(lesson.subjectLongName);
model.classroomName = lesson.classroomName;
model.bellSyncDiffMillis = bellSyncDiffMillis;
if (lesson.changeId != 0) {
if (lesson.changeType == LessonChange.TYPE_CHANGE) {
model.lessonChange = true;
if (lesson.changedClassroomName()) {
model.newClassroomName = lesson.changeClassroomName;
}
if (lesson.changedSubjectLongName()) {
model.newSubjectName = lesson.changeSubjectLongName;
}
}
if (lesson.changeType == LessonChange.TYPE_CANCELLED) {
model.lessonCancelled = true;
}
}
for (EventFull event : events) {
if (event.startTime == null)
continue;
if (event.eventDate.getValue() == displayingDate.getValue()
&& event.startTime.getValue() == lesson.startTime.getValue()) {
model.eventColors.add(event.type == TYPE_HOMEWORK ? ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK : event.getColor());
}
}
lessonList.add(model);
}
}
if (lessonList.size() == 0) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE);
views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons));
appWidgetManager.updateAppWidget(appWidgetId, views);
}
else {
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE);
timetables.put(appWidgetId, lessonList);
//WidgetTimetableListProvider.widgetsLessons.put(appWidgetId, lessons);
//views.setRemoteAdapter(R.id.widgetTimetableListView, new Intent());
Intent listIntent = new Intent(context, WidgetTimetableService.class);
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
listIntent.setData(Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)));
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent);
// template to handle the click listener for each item
Intent intentTemplate = new Intent(context, LessonDetailsActivity.class);
// Old activities shouldn't be in the history stack
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntentTimetable = PendingIntent.getActivity(context,
0,
intentTemplate,
0);
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable);
Intent openIntent = new Intent(context, MainActivity.class);
openIntent.setAction("android.intent.action.MAIN");
if (!unified) {
openIntent.putExtra("profileId", openProfileId);
openIntent.putExtra("timetableDate", displayingDate.getValue());
}
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE);
PendingIntent pendingOpenIntent = PendingIntent.getActivity(context,
appWidgetId, openIntent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent);
if (!unified)
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos);
}
}
appWidgetManager.updateAppWidget(appWidgetId, views);
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView);
}
//modeInt++;
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
App app = (App) context.getApplicationContext();
for (int appWidgetId: appWidgetIds) {
app.appConfig.widgetTimetableConfigs.remove(appWidgetId);
}
app.saveConfig("widgetTimetableConfigs");
}
}

View file

@ -0,0 +1,371 @@
package pl.szczodrzynski.edziennik
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.util.SparseArray
import android.view.View
import android.widget.RemoteViews
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.ItemWidgetTimetableModel
import pl.szczodrzynski.edziennik.utils.models.Time
import pl.szczodrzynski.edziennik.utils.models.Week
import pl.szczodrzynski.edziennik.widgets.WidgetConfig
import pl.szczodrzynski.edziennik.widgets.timetable.LessonDialogActivity
import pl.szczodrzynski.edziennik.widgets.timetable.WidgetTimetableService
import java.lang.reflect.InvocationTargetException
class WidgetTimetable : AppWidgetProvider() {
override fun onReceive(context: Context, intent: Intent) {
if (ACTION_SYNC_DATA == intent.action) {
EdziennikTask.sync().enqueue(context)
}
super.onReceive(context, intent)
}
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
val thisWidget = ComponentName(context, WidgetTimetable::class.java)
timetables = SparseArray()
//timetables.clear();
val app = context.applicationContext as App
var bellSyncDiffMillis: Long = 0
if (app.appConfig.bellSyncDiff != null) {
bellSyncDiffMillis = (app.appConfig.bellSyncDiff.hour * 60 * 60 * 1000 + app.appConfig.bellSyncDiff.minute * 60 * 1000 + app.appConfig.bellSyncDiff.second * 1000).toLong()
bellSyncDiffMillis *= app.appConfig.bellSyncMultiplier.toLong()
bellSyncDiffMillis *= -1
}
val allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget)
allWidgetIds?.forEach { appWidgetId ->
var widgetConfig = app.appConfig.widgetTimetableConfigs[appWidgetId]
if (widgetConfig == null) {
widgetConfig = WidgetConfig(app.profileFirstId())
app.appConfig.widgetTimetableConfigs[appWidgetId] = widgetConfig
app.appConfig.savePending = true
}
val views = if (widgetConfig.bigStyle) {
RemoteViews(context.packageName, if (widgetConfig.darkTheme) R.layout.widget_timetable_dark_big else R.layout.widget_timetable_big)
} else {
RemoteViews(context.packageName, if (widgetConfig.darkTheme) R.layout.widget_timetable_dark else R.layout.widget_timetable)
}
val refreshIntent = Intent(app, WidgetTimetable::class.java)
refreshIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
refreshIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
val pendingRefreshIntent = PendingIntent.getBroadcast(context,
0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT)
views.setOnClickPendingIntent(R.id.widgetTimetableRefresh, pendingRefreshIntent)
views.setOnClickPendingIntent(R.id.widgetTimetableSync, getPendingSelfIntent(context, ACTION_SYNC_DATA))
views.setImageViewBitmap(R.id.widgetTimetableRefresh, IconicsDrawable(context, CommunityMaterial.Icon2.cmd_refresh)
.colorInt(Color.WHITE)
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
views.setImageViewBitmap(R.id.widgetTimetableSync, IconicsDrawable(context, CommunityMaterial.Icon2.cmd_sync)
.colorInt(Color.WHITE)
.sizeDp(if (widgetConfig.bigStyle) 24 else 16).toBitmap())
prepareAppWidget(app, appWidgetId, views, widgetConfig, bellSyncDiffMillis)
appWidgetManager.updateAppWidget(appWidgetId, views)
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widgetTimetableListView)
}
}
private fun prepareAppWidget(
app: App,
appWidgetId: Int,
views: RemoteViews,
widgetConfig: WidgetConfig,
bellSyncDiffMillis: Long
) {
// get the current bell-synced time
val now = Time.fromMillis(Time.getNow().inMillis + bellSyncDiffMillis)
// set the widget transparency
val mode = PorterDuff.Mode.DST_IN
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// this code seems to crash the launcher on >= P
val transparency = widgetConfig.opacity //0...1
val colorFilter = 0x01000000L * (255f * transparency).toLong()
try {
val declaredMethods = Class.forName("android.widget.RemoteViews").declaredMethods
val len = declaredMethods.size
if (len > 0) {
for (m in 0 until len) {
val method = declaredMethods[m]
if (method.name == "setDrawableParameters") {
method.isAccessible = true
method.invoke(views, R.id.widgetTimetableListView, true, -1, colorFilter.toInt(), mode, -1)
method.invoke(views, R.id.widgetTimetableHeader, true, -1, colorFilter.toInt(), mode, -1)
break
}
}
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
} catch (e: InvocationTargetException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
} catch (e: IllegalArgumentException) {
e.printStackTrace()
}
}
val unified = widgetConfig.profileId == -1
// get all profiles or one profile with the specified id
val profileList = if (unified)
app.db.profileDao().allNow.filterOutArchived()
else
listOfNotNull(app.db.profileDao().getByIdNow(widgetConfig.profileId))
// no profile was found
if (profileList.isEmpty()) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_profile_doesnt_exist))
return
}
views.setViewVisibility(R.id.widgetTimetableLoading, View.GONE)
// set lesson search bounds
val today = Date.getToday()
val searchEnd = today.clone().stepForward(0, 0, 7)
var scrollPos = 0
var profileId: Int? = null
var displayingDate: Date? = null
val models = mutableListOf<ItemWidgetTimetableModel>()
// get all lessons within the search bounds
val lessonList = app.db.timetableDao().getBetweenDatesNow(today, searchEnd)
for (profile in profileList) {
// add a profile separator with its name
if (unified) {
val separator = ItemWidgetTimetableModel()
separator.profileId = profile.id
separator.bigStyle = widgetConfig.bigStyle
separator.darkTheme = widgetConfig.darkTheme
separator.separatorProfileName = profile.name
models.add(separator)
}
// search for lessons to display
val timetableDate = Date.getToday()
var checkedDays = 0
var lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
while ((lessons.isEmpty() || lessons.none {
it.displayDate != today || (it.displayDate == today && it.displayEndTime != null && it.displayEndTime!! >= now)
}) && checkedDays < 7) {
timetableDate.stepForward(0, 0, 1)
lessons = lessonList.filter { it.profileId == profile.id && it.displayDate == timetableDate && it.type != Lesson.TYPE_NO_LESSONS }
checkedDays++
}
// set the displayingDate to show in the header
if (!unified) {
if (lessons.isNotEmpty())
displayingDate = timetableDate
profileId = profile.id
}
// get all events for the current date
val events = app.db.eventDao().getAllByDateNow(profile.id, timetableDate)?.filterNotNull() ?: emptyList()
lessons.forEachIndexed { pos, lesson ->
val model = ItemWidgetTimetableModel()
model.bigStyle = widgetConfig.bigStyle
model.darkTheme = widgetConfig.darkTheme
model.profileId = profile.id
model.lessonId = lesson.id
model.lessonDate = timetableDate
model.startTime = lesson.displayStartTime
model.endTime = lesson.displayEndTime
// check if the lesson has already passed or it's currently in progress
if (lesson.displayDate == today) {
lesson.displayEndTime?.let { endTime ->
model.lessonPassed = now > endTime
lesson.displayStartTime?.let { startTime ->
model.lessonCurrent = now in startTime..endTime
}
}
}
// set where should the list view scroll to
if (model.lessonCurrent) {
scrollPos = pos
} else if (model.lessonPassed) {
scrollPos = pos + 1
}
// set the subject and classroom name
model.subjectName = lesson.displaySubjectName
model.classroomName = lesson.displayClassroom
// set the bell sync to calculate progress in ListProvider
model.bellSyncDiffMillis = bellSyncDiffMillis
// make the model aware of the lesson type
when (lesson.type) {
Lesson.TYPE_CANCELLED -> {
model.lessonCancelled = true
}
Lesson.TYPE_CHANGE,
Lesson.TYPE_SHIFTED_SOURCE,
Lesson.TYPE_SHIFTED_TARGET -> {
model.lessonChange = true
}
}
// add every event on this lesson
for (event in events) {
if (event.startTime != null && event.startTime != lesson.displayStartTime)
continue
model.eventColors.add(if (event.type == TYPE_HOMEWORK) ItemWidgetTimetableModel.EVENT_COLOR_HOMEWORK else event.getColor())
}
models += model
}
}
if (unified) {
// set the title for an unified widget
views.setTextViewText(R.id.widgetTimetableTitle, app.getString(R.string.widget_timetable_title_unified))
views.setViewVisibility(R.id.widgetTimetableSubtitle, View.GONE)
} else {
// set the title to present the widget's profile
views.setTextViewText(R.id.widgetTimetableTitle, profileList[0].name)
views.setViewVisibility(R.id.widgetTimetableTitle, View.VISIBLE)
// make the subtitle show current date for these lessons
displayingDate?.let {
when (Date.diffDays(it, Date.getToday())) {
0 -> views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.day_today_format, Week.getFullDayName(it.weekDay)))
1 -> views.setTextViewText(R.id.widgetTimetableSubtitle, app.getString(R.string.day_tomorrow_format, Week.getFullDayName(it.weekDay)))
else -> views.setTextViewText(R.id.widgetTimetableSubtitle, Week.getFullDayName(it.weekDay) + " " + it.formattedString)
}
}
}
// intent running when the header is clicked
val openIntent = Intent(app, MainActivity::class.java)
openIntent.action = "android.intent.action.MAIN"
if (!unified) {
// per-profile widget should redirect to it + correct day
profileId?.let {
openIntent.putExtra("profileId", it)
}
displayingDate?.let {
openIntent.putExtra("timetableDate", it.value)
}
}
openIntent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
val pendingOpenIntent = PendingIntent.getActivity(app, appWidgetId, openIntent, 0)
views.setOnClickPendingIntent(R.id.widgetTimetableHeader, pendingOpenIntent)
if (lessonList.isEmpty()) {
views.setViewVisibility(R.id.widgetTimetableLoading, View.VISIBLE)
views.setRemoteAdapter(R.id.widgetTimetableListView, Intent())
views.setTextViewText(R.id.widgetTimetableLoading, app.getString(R.string.widget_timetable_no_lessons))
return
}
timetables!!.put(appWidgetId, models)
// apply the list service to the list view
val listIntent = Intent(app, WidgetTimetableService::class.java)
listIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
listIntent.data = Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME))
views.setRemoteAdapter(R.id.widgetTimetableListView, listIntent)
// create an intent used to display the lesson details dialog
val intentTemplate = Intent(app, LessonDialogActivity::class.java)
intentTemplate.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
val pendingIntentTimetable = PendingIntent.getActivity(app, appWidgetId, intentTemplate, 0)
views.setPendingIntentTemplate(R.id.widgetTimetableListView, pendingIntentTimetable)
if (!unified)
views.setScrollPosition(R.id.widgetTimetableListView, scrollPos)
}
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
val app = context.applicationContext as App
for (appWidgetId in appWidgetIds) {
app.appConfig.widgetTimetableConfigs.remove(appWidgetId)
}
app.saveConfig("widgetTimetableConfigs")
}
companion object {
val ACTION_SYNC_DATA = "ACTION_SYNC_DATA"
private val TAG = "WidgetTimetable"
private val modeInt = 0
var timetables: SparseArray<List<ItemWidgetTimetableModel>>? = null
fun getPendingSelfIntent(context: Context, action: String): PendingIntent {
val intent = Intent(context, WidgetTimetable::class.java)
intent.action = action
return getPendingSelfIntent(context, intent)
}
fun getPendingSelfIntent(context: Context, intent: Intent): PendingIntent {
return PendingIntent.getBroadcast(context, 0, intent, 0)
}
fun drawableToBitmap(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
}
}

View file

@ -39,9 +39,11 @@ const val ERROR_REQUEST_HTTP_404 = 54
const val ERROR_REQUEST_HTTP_405 = 55 const val ERROR_REQUEST_HTTP_405 = 55
const val ERROR_REQUEST_HTTP_410 = 56 const val ERROR_REQUEST_HTTP_410 = 56
const val ERROR_REQUEST_HTTP_500 = 57 const val ERROR_REQUEST_HTTP_500 = 57
const val ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND = 60
const val ERROR_REQUEST_FAILURE_TIMEOUT = 61
const val ERROR_REQUEST_FAILURE_NO_INTERNET = 62
const val ERROR_RESPONSE_EMPTY = 100 const val ERROR_RESPONSE_EMPTY = 100
const val ERROR_LOGIN_DATA_MISSING = 101 const val ERROR_LOGIN_DATA_MISSING = 101
const val ERROR_LOGIN_DATA_INVALID = 102
const val ERROR_PROFILE_MISSING = 105 const val ERROR_PROFILE_MISSING = 105
const val ERROR_INVALID_LOGIN_MODE = 110 const val ERROR_INVALID_LOGIN_MODE = 110
const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111
@ -99,6 +101,13 @@ const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172
const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173 const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173
const val ERROR_LIBRUS_SYNERGIA_OTHER = 174 const val ERROR_LIBRUS_SYNERGIA_OTHER = 174
const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175 const val ERROR_LIBRUS_SYNERGIA_MAINTENANCE = 175
const val ERROR_LIBRUS_MESSAGES_MAINTENANCE = 176
const val ERROR_LIBRUS_MESSAGES_ERROR = 177
const val ERROR_LIBRUS_MESSAGES_OTHER = 178
const val ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN = 179
const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN = 180
const val ERROR_LIBRUS_API_MAINTENANCE = 181
const val ERROR_LIBRUS_PORTAL_MAINTENANCE = 182
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202
@ -109,7 +118,7 @@ const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS = 206
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210
const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211 const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212 const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212
const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 212 const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216
const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213 const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213
const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214 const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214
const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215 const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215
@ -163,3 +172,5 @@ const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911
const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912 const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912
const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913 const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913
const val EXCEPTION_IDZIENNIK_API_REQUEST = 914 const val EXCEPTION_IDZIENNIK_API_REQUEST = 914
const val LOGIN_NO_ARGUMENTS = 1201

View file

@ -7,39 +7,36 @@ package pl.szczodrzynski.edziennik.api.v2
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA 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_ANNOUNCEMENTS
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE 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_GRADES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME 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_HOMEWORK
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
const val FEATURE_ALL = 0 internal const val FEATURE_TIMETABLE = 1
const val FEATURE_TIMETABLE = 1 internal const val FEATURE_AGENDA = 2
const val FEATURE_AGENDA = 2 internal const val FEATURE_GRADES = 3
const val FEATURE_GRADES = 3 internal const val FEATURE_HOMEWORK = 4
const val FEATURE_HOMEWORK = 4 internal const val FEATURE_BEHAVIOUR = 5
const val FEATURE_BEHAVIOUR = 5 internal const val FEATURE_ATTENDANCE = 6
const val FEATURE_ATTENDANCE = 6 internal const val FEATURE_MESSAGES_INBOX = 7
const val FEATURE_MESSAGES_INBOX = 7 internal const val FEATURE_MESSAGES_SENT = 8
const val FEATURE_MESSAGES_SENT = 8 internal const val FEATURE_ANNOUNCEMENTS = 9
const val FEATURE_ANNOUNCEMENTS = 9
const val FEATURE_ALWAYS_NEEDED = 100 internal const val FEATURE_ALWAYS_NEEDED = 100
const val FEATURE_STUDENT_INFO = 101 internal const val FEATURE_STUDENT_INFO = 101
const val FEATURE_STUDENT_NUMBER = 109 internal const val FEATURE_STUDENT_NUMBER = 109
const val FEATURE_SCHOOL_INFO = 102 internal const val FEATURE_SCHOOL_INFO = 102
const val FEATURE_CLASS_INFO = 103 internal const val FEATURE_CLASS_INFO = 103
const val FEATURE_TEAM_INFO = 104 internal const val FEATURE_TEAM_INFO = 104
const val FEATURE_LUCKY_NUMBER = 105 internal const val FEATURE_LUCKY_NUMBER = 105
const val FEATURE_TEACHERS = 106 internal const val FEATURE_TEACHERS = 106
const val FEATURE_SUBJECTS = 107 internal const val FEATURE_SUBJECTS = 107
const val FEATURE_CLASSROOMS = 108 internal const val FEATURE_CLASSROOMS = 108
const val FEATURE_PUSH_CONFIG = 120 internal const val FEATURE_PUSH_CONFIG = 120
const val FEATURE_MESSAGE_GET = 201
object Features { object Features {
private fun getAllNecessary(): List<Int> = listOf( private fun getAllNecessary(): List<Int> = listOf(

View file

@ -37,6 +37,16 @@ object Regexes {
"""events: (.+),$""".toRegex(RegexOption.MULTILINE) """events: (.+),$""".toRegex(RegexOption.MULTILINE)
} }
val MOBIDZIENNIK_MESSAGE_READ_DATE by lazy {
"""czas przeczytania:.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy {
""".+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy {
"""href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?<small.+?\(([0-9.]+)\s(M|K|G|)B\))*""".toRegex(RegexOption.DOT_MATCHES_ALL)
}
val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy {
@ -60,4 +70,10 @@ object Regexes {
val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy {
"""<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL) """<option.*?value="([0-9]+)"\sdata-id-ucznia="([A-z0-9]+?)".*?>(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)</option>""".toRegex(RegexOption.DOT_MATCHES_ALL)
} }
val VULCAN_SHITFT_ANNOTATION by lazy {
"""\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex()
}
} }

View file

@ -24,6 +24,7 @@ const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GC = 1023
const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024 const val ENDPOINT_LIBRUS_API_TEXT_GC = 1024
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025 const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC = 1025
const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026 const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GC = 1026
const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030
const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031 const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031
const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032 const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032
const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033 const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033
@ -97,6 +98,7 @@ val LibrusFeatures = listOf(
ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_BEHAVIOUR_GC to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_POINT_GRADES to LOGIN_METHOD_LIBRUS_API,
ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API, ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API,

View file

@ -32,6 +32,13 @@ open class LibrusApi(open val data: DataLibrus) {
val callback = object : JsonCallbackHandler() { val callback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(tag, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null && response?.parserErrorBody == null) { if (json == null && response?.parserErrorBody == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -104,6 +111,7 @@ open class LibrusApi(open val data: DataLibrus) {
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_FORBIDDEN) .allowErrorCode(HTTP_FORBIDDEN)
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(callback) .callback(callback)
.build() .build()
.enqueue() .enqueue()

View file

@ -85,6 +85,10 @@ class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_grades) data.startProgress(R.string.edziennik_progress_endpoint_grades)
LibrusApiGrades(data, onSuccess) LibrusApiGrades(data, onSuccess)
} }
ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_comments)
LibrusApiGradeComments(data, onSuccess)
}
ENDPOINT_LIBRUS_API_NORMAL_GC -> { ENDPOINT_LIBRUS_API_NORMAL_GC -> {
data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) data.startProgress(R.string.edziennik_progress_endpoint_grade_categories)
LibrusApiGradeCategories(data, onSuccess) LibrusApiGradeCategories(data, onSuccess)

View file

@ -15,7 +15,6 @@ import org.jsoup.parser.Parser
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
@ -43,19 +42,19 @@ open class LibrusMessages(open val data: DataLibrus) {
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) { override fun onSuccess(text: String?, response: Response?) {
if (text.isNullOrEmpty()) { if (text.isNullOrEmpty()) {
data.error(ApiError(LibrusSynergia.TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
return return
} }
// TODO: Finish error handling when {
text.contains("<message>Niepoprawny login i/lub hasło.</message>") -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
if ("error" in text) { text.contains("stop.png") -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
when ("<type>(.*)</type>".toRegex().find(text)?.get(1)) { text.contains("eAccessDeny") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
"eAccessDeny" -> data.error(ApiError(tag, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED) text.contains("OffLine") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
.withResponse(response) text.contains("<status>error</status>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
.withApiResponse(text)) text.contains("<type>eVarWhitThisNameNotExists</type>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
} text.contains("<error>") -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
} }
try { try {

View file

@ -14,7 +14,7 @@ import pl.szczodrzynski.edziennik.utils.Utils.d
open class LibrusSynergia(open val data: DataLibrus) { open class LibrusSynergia(open val data: DataLibrus) {
companion object { companion object {
const val TAG = "LibrusSynergia" private const val TAG = "LibrusSynergia"
} }
val profileId val profileId

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-20
*/
package pl.szczodrzynski.edziennik.api.v2.librus.data.api
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.asJsonObjectList
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.getJsonArray
import pl.szczodrzynski.edziennik.getLong
import pl.szczodrzynski.edziennik.getString
class LibrusApiGradeComments(override val data: DataLibrus,
val onSuccess: () -> Unit) : LibrusApi(data) {
companion object {
const val TAG = "LibrusApiGradeComments"
}
init {
apiGet(TAG, "Grades/Comments") { json ->
json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment ->
val id = comment.getLong("Id") ?: return@forEach
val text = comment.getString("Text")
val gradeCategoryObject = GradeCategory(
profileId,
id,
-1f,
-1,
text
).apply {
type = GradeCategory.TYPE_COMMENT
}
data.gradeCategories.put(id, gradeCategoryObject)
}
data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS)
onSuccess()
}
}
}

View file

@ -6,6 +6,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade import pl.szczodrzynski.edziennik.data.db.modules.grades.Grade
import pl.szczodrzynski.edziennik.data.db.modules.grades.GradeCategory
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
@ -42,12 +43,21 @@ class LibrusApiGrades(override val data: DataLibrus,
weight = 0f weight = 0f
} }
val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments ->
if (comments.isNotEmpty()) {
data.gradeCategories.singleOrNull {
it.type == GradeCategory.TYPE_COMMENT
&& it.categoryId == comments[0].asJsonObject.getLong("Id")
}?.text
} else null
} ?: ""
val gradeObject = Grade( val gradeObject = Grade(
profileId, profileId,
id, id,
categoryName, categoryName,
color, color,
"", description,
name, name,
value, value,
weight, weight,

View file

@ -44,16 +44,16 @@ class LibrusApiTimetables(override val data: DataLibrus,
val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList() val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList()
if (lessonRange?.isNullOrEmpty() == false) if (lessonRange?.isNullOrEmpty() == false)
lessonsFound = true lessonsFound = true
lessonRange?.forEachIndexed { index, lesson -> lessonRange?.forEach { lesson ->
parseLesson(lessonDate, lesson) parseLesson(lessonDate, lesson)
} }
} }
if (day.isNullOrEmpty() || !lessonsFound) { if (day.isNullOrEmpty() || !lessonsFound) {
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply { data.lessonNewList.add(Lesson(profileId, lessonDate.value.toLong()).apply {
type = Lesson.TYPE_NO_LESSONS type = Lesson.TYPE_NO_LESSONS
date = lessonDate date = lessonDate
} })
} }
} }

View file

@ -13,6 +13,7 @@ import pl.szczodrzynski.edziennik.api.v2.librus.ENDPOINT_LIBRUS_MESSAGES_SENT
import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusMessages
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
@ -20,7 +21,7 @@ import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.Utils import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int = Message.TYPE_RECEIVED, class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int = TYPE_RECEIVED,
archived: Boolean = false, val onSuccess: () -> Unit) : LibrusMessages(data) { archived: Boolean = false, val onSuccess: () -> Unit) : LibrusMessages(data) {
companion object { companion object {
const val TAG = "LibrusMessagesGetList" const val TAG = "LibrusMessagesGetList"
@ -28,7 +29,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
init { init {
val endpoint = when (type) { val endpoint = when (type) {
Message.TYPE_RECEIVED -> "Inbox/action/GetList" TYPE_RECEIVED -> "Inbox/action/GetList"
Message.TYPE_SENT -> "Outbox/action/GetList" Message.TYPE_SENT -> "Outbox/action/GetList"
else -> null else -> null
} }
@ -46,34 +47,38 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
else -> 0 else -> 0
} }
val sentDate = Date.fromIso(element.select("sendDate").text().trim()) val sentDate = Date.fromIso(element.select("sendDate").text().trim())
var senderId: Long = -1
var receiverId: Long = -1
when (type) { val recipientFirstName = element.select(when (type) {
Message.TYPE_RECEIVED -> { TYPE_RECEIVED -> "senderFirstName"
val senderFirstName = element.select("senderFirstName").text().trim() else -> "receiverFirstName"
val senderLastName = element.select("senderLastName").text().trim() }).text().trim()
senderId = data.teacherList.singleOrNull {
it.name == senderFirstName && it.surname == senderLastName
}?.id ?: -1
}
Message.TYPE_SENT -> { val recipientLastName = element.select(when (type) {
val receiverFirstName = element.select("receiverFirstName").text().trim() TYPE_RECEIVED -> "senderLastName"
val receiverLastName = element.select("receiverLastName").text().trim() else -> "receiverLastName"
receiverId = data.teacherList.singleOrNull { }).text().trim()
it.name == receiverFirstName && it.surname == receiverLastName
}?.id ?: { val recipientId = data.teacherList.singleOrNull {
val teacherObject = Teacher( it.name == recipientFirstName && it.surname == recipientLastName
profileId, }?.id ?: {
-1 * Utils.crc16("$receiverFirstName $receiverLastName".toByteArray()).toLong(), val teacherObject = Teacher(
receiverFirstName, profileId,
receiverLastName -1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()).toLong(),
) recipientFirstName,
data.teacherList.put(teacherObject.id, teacherObject) recipientLastName
teacherObject.id )
}.invoke() data.teacherList.put(teacherObject.id, teacherObject)
} teacherObject.id
}.invoke()
val senderId = when (type) {
TYPE_RECEIVED -> recipientId
else -> -1
}
val receiverId = when (type) {
TYPE_RECEIVED -> -1
else -> recipientId
} }
val notified = when (type) { val notified = when (type) {
@ -112,7 +117,7 @@ class LibrusMessagesGetList(override val data: DataLibrus, private val type: Int
} }
when (type) { when (type) {
Message.TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS)
Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES)
} }
onSuccess() onSuccess()

View file

@ -16,8 +16,7 @@ import pl.szczodrzynski.edziennik.getInt
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection.HTTP_BAD_REQUEST import java.net.HttpURLConnection.*
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
class LibrusLoginApi { class LibrusLoginApi {
companion object { companion object {
@ -117,6 +116,13 @@ class LibrusLoginApi {
private val tokenCallback = object : JsonCallbackHandler() { private val tokenCallback = object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response?) { override fun onSuccess(json: JsonObject?, response: Response?) {
if (response?.code() == HTTP_UNAVAILABLE) {
data.error(ApiError(TAG, ERROR_LIBRUS_API_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null) { if (json == null) {
data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY)
.withResponse(response)) .withResponse(response))
@ -176,6 +182,7 @@ class LibrusLoginApi {
.post() .post()
.allowErrorCode(HTTP_BAD_REQUEST) .allowErrorCode(HTTP_BAD_REQUEST)
.allowErrorCode(HTTP_UNAUTHORIZED) .allowErrorCode(HTTP_UNAUTHORIZED)
.allowErrorCode(HTTP_UNAVAILABLE)
.callback(tokenCallback) .callback(tokenCallback)
.build() .build()
.enqueue() .enqueue()

View file

@ -6,20 +6,57 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.body.MediaTypeUtils
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie import okhttp3.Cookie
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus import pl.szczodrzynski.edziennik.api.v2.librus.DataLibrus
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) { class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
companion object { companion object {
private const val TAG = "LoginLibrusMessages" private const val TAG = "LoginLibrusMessages"
} }
private val callback by lazy { object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
when {
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
location?.contains("AutoLogon") == true -> {
saveSessionId(response, text)
onSuccess()
}
text?.contains("<status>ok</status>") == true -> {
saveSessionId(response, text)
onSuccess()
}
text?.contains("<message>Niepoprawny login i/lub hasło.</message>") == true -> data.error(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN, response, text)
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("OffLine") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_MAINTENANCE, response, text)
text?.contains("<status>error</status>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text)
text?.contains("<type>eVarWhitThisNameNotExists</type>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("<error>") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_OTHER, response, text)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}}
init { run { init { run {
if (data.profile == null) { if (data.profile == null) {
data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) data.error(ApiError(TAG, ERROR_PROFILE_MISSING))
@ -41,7 +78,7 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) {
loginWithSynergia() loginWithSynergia()
} }
else if (data.apiLogin != null && data.apiPassword != null && false) { else if (data.apiLogin != null && data.apiPassword != null) {
loginWithCredentials() loginWithCredentials()
} }
else { else {
@ -54,7 +91,44 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
* XML (Flash messages website) login method. Uses a Synergia login and password. * XML (Flash messages website) login method. Uses a Synergia login and password.
*/ */
private fun loginWithCredentials() { private fun loginWithCredentials() {
d(TAG, "Request: Librus/Login/Messages - $LIBRUS_MESSAGES_URL/Login")
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.newDocument()
val serviceElement = doc.createElement("service")
val headerElement = doc.createElement("header")
val dataElement = doc.createElement("data")
val loginElement = doc.createElement("login")
loginElement.appendChild(doc.createTextNode(data.apiLogin))
dataElement.appendChild(loginElement)
val passwordElement = doc.createElement("login")
passwordElement.appendChild(doc.createTextNode(data.apiPassword))
dataElement.appendChild(passwordElement)
val keyStrokeElement = doc.createElement("KeyStroke")
val keysElement = doc.createElement("Keys")
val upElement = doc.createElement("Up")
keysElement.appendChild(upElement)
val downElement = doc.createElement("Down")
keysElement.appendChild(downElement)
keyStrokeElement.appendChild(keysElement)
dataElement.appendChild(keyStrokeElement)
serviceElement.appendChild(headerElement)
serviceElement.appendChild(dataElement)
doc.appendChild(serviceElement)
val transformer = TransformerFactory.newInstance().newTransformer()
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
val stringWriter = StringWriter()
transformer.transform(DOMSource(doc), StreamResult(stringWriter))
val requestXml = stringWriter.toString()
Request.builder()
.url("$LIBRUS_MESSAGES_URL/Login")
.userAgent(SYNERGIA_USER_AGENT)
.setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML)
.post()
.callback(callback)
.build()
.enqueue()
} }
/** /**
@ -63,37 +137,6 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") { private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") {
d(TAG, "Request: Librus/Login/Messages - $url") d(TAG, "Request: Librus/Login/Messages - $url")
val callback = object : TextCallbackHandler() {
override fun onSuccess(text: String?, response: Response?) {
val location = response?.headers()?.get("Location")
when {
location?.contains("MultiDomainLogon") == true -> loginWithSynergia(location)
location?.contains("AutoLogon") == true -> {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "")
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
onSuccess()
}
text?.contains("eAccessDeny") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text)
text?.contains("stop.png") == true -> data.error(TAG, ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED, response, text)
}
}
override fun onFailure(response: Response?, throwable: Throwable?) {
data.error(ApiError(TAG, ERROR_REQUEST_FAILURE)
.withResponse(response)
.withThrowable(throwable))
}
}
Request.builder() Request.builder()
.url(url) .url(url)
.userAgent(SYNERGIA_USER_AGENT) .userAgent(SYNERGIA_USER_AGENT)
@ -103,4 +146,17 @@ class LibrusLoginMessages(val data: DataLibrus, val onSuccess: () -> Unit) {
.build() .build()
.enqueue() .enqueue()
} }
private fun saveSessionId(response: Response?, text: String?) {
var sessionId = data.app.cookieJar.getCookie("wiadomosci.librus.pl", "DZIENNIKSID")
sessionId = sessionId?.replace("-MAINT", "") // dunno what's this
if (sessionId == null) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID)
.withResponse(response)
.withApiResponse(text))
return
}
data.messagesSessionId = sessionId
data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */
}
} }

View file

@ -99,6 +99,14 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
.post() .post()
.callback(object : JsonCallbackHandler() { .callback(object : JsonCallbackHandler() {
override fun onSuccess(json: JsonObject?, response: Response) { override fun onSuccess(json: JsonObject?, response: Response) {
val location = response.headers()?.get("Location")
if (location == "http://localhost/bar?command=close") {
data.error(ApiError(TAG, ERROR_LIBRUS_PORTAL_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (json == null) { if (json == null) {
if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) { if (response.parserErrorBody?.contains("wciąż nieaktywne") == true) {
data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED) data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED)
@ -120,7 +128,7 @@ class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) {
override fun onFailure(response: Response, throwable: Throwable) { override fun onFailure(response: Response, throwable: Throwable) {
if (response.code() == 403 || response.code() == 401) { if (response.code() == 403 || response.code() == 401) {
data.error(ApiError(TAG, ERROR_LOGIN_DATA_INVALID) data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN)
.withResponse(response) .withResponse(response)
.withThrowable(throwable)) .withThrowable(throwable))
return return

View file

@ -7,7 +7,6 @@ package pl.szczodrzynski.edziennik.api.v2.librus.login
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
import im.wangchao.mhttp.callback.JsonCallbackHandler
import im.wangchao.mhttp.callback.TextCallbackHandler import im.wangchao.mhttp.callback.TextCallbackHandler
import okhttp3.Cookie import okhttp3.Cookie
import pl.szczodrzynski.edziennik.api.v2.* import pl.szczodrzynski.edziennik.api.v2.*
@ -16,7 +15,6 @@ import pl.szczodrzynski.edziennik.api.v2.librus.data.LibrusApi
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.getString import pl.szczodrzynski.edziennik.getString
import pl.szczodrzynski.edziennik.getUnixDate import pl.szczodrzynski.edziennik.getUnixDate
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.Utils.d import pl.szczodrzynski.edziennik.utils.Utils.d
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -86,6 +84,13 @@ class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Un
val callback = object : TextCallbackHandler() { val callback = object : TextCallbackHandler() {
override fun onSuccess(json: String?, response: Response?) { override fun onSuccess(json: String?, response: Response?) {
val location = response?.headers()?.get("Location") val location = response?.headers()?.get("Location")
if (location?.endsWith("przerwa_techniczna") == true) {
data.error(ApiError(TAG, ERROR_LIBRUS_SYNERGIA_MAINTENANCE)
.withApiResponse(json)
.withResponse(response))
return
}
if (location?.endsWith("centrum_powiadomien") == true) { if (location?.endsWith("centrum_powiadomien") == true) {
val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID") val sessionId = data.app.cookieJar.getCookie("synergia.librus.pl", "DZIENNIKSID")
if (sessionId == null) { if (sessionId == null) {

View file

@ -10,8 +10,10 @@ import pl.szczodrzynski.edziennik.api.v2.CODE_INTERNAL_LIBRUS_ACCOUNT_410
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikCallback
import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikData
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web.MobidziennikWebGetMessage
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin import pl.szczodrzynski.edziennik.api.v2.mobidziennik.firstlogin.MobidziennikFirstLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLogin
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.login.MobidziennikLoginWeb
import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods import pl.szczodrzynski.edziennik.api.v2.mobidziennikLoginMethods
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
@ -63,7 +65,11 @@ class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginSto
} }
override fun getMessage(message: MessageFull) { override fun getMessage(message: MessageFull) {
MobidziennikLoginWeb(data) {
MobidziennikWebGetMessage(data, message) {
completed()
}
}
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {

View file

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import pl.szczodrzynski.edziennik.App.profileId
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.getById import pl.szczodrzynski.edziennik.getById
@ -25,7 +24,7 @@ class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List<String>?
val teacherId = cols[4].toLongOrNull() ?: -1 val teacherId = cols[4].toLongOrNull() ?: -1
val teamObject = Team( val teamObject = Team(
profileId, data.profileId,
id, id,
name, name,
type, type,

View file

@ -4,7 +4,6 @@
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.api
import pl.szczodrzynski.edziennik.App.profileId
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
@ -93,7 +92,7 @@ class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List<String>) {
for (day in dataDays) { for (day in dataDays) {
val lessonDate = Date.fromValue(day) val lessonDate = Date.fromValue(day)
data.lessonNewList += Lesson(profileId, lessonDate.value.toLong()).apply { data.lessonNewList += Lesson(data.profileId, lessonDate.value.toLong()).apply {
type = Lesson.TYPE_NO_LESSONS type = Lesson.TYPE_NO_LESSONS
date = lessonDate date = lessonDate
} }

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-18.
*/
package pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.web
import org.greenrobot.eventbus.EventBus
import org.jsoup.Jsoup
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.events.MessageGetEvent
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.DataMobidziennik
import pl.szczodrzynski.edziennik.api.v2.mobidziennik.data.MobidziennikWeb
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.get
import pl.szczodrzynski.edziennik.singleOrNull
import pl.szczodrzynski.edziennik.utils.Utils.monthFromName
import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time
class MobidziennikWebGetMessage(
override val data: DataMobidziennik,
private val message: MessageFull,
val onSuccess: () -> Unit) : MobidziennikWeb(data) {
companion object {
private const val TAG = "MobidziennikWebGetMessage"
}
init {
val typeUrl = if (message.type == Message.TYPE_SENT)
"wiadwyslana"
else
"wiadodebrana"
webGet(TAG, "/dziennik/$typeUrl/?id=${message.id}") { text ->
MobidziennikLuckyNumberExtractor(data, text)
val messageRecipientList = mutableListOf<MessageRecipientFull>()
val doc = Jsoup.parse(text)
val content = doc.select("#content").first()
val body = content.select(".wiadomosc_tresc").first()
if (message.type == TYPE_RECEIVED) {
var readDate = System.currentTimeMillis()
Regexes.MOBIDZIENNIK_MESSAGE_READ_DATE.find(body.html())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,
monthFromName(it[2]),
it[1].toIntOrNull() ?: 1
)
val time = Time.fromH_m_s(
it[4] // TODO blank string safety
)
readDate = date.combineWith(time)
}
val recipient = MessageRecipientFull(
profileId,
-1,
-1,
readDate,
message.id
)
recipient.fullName = profile?.accountNameLong ?: profile?.studentNameLong
messageRecipientList.add(recipient)
} else {
message.senderId = -1
message.senderReplyId = -1
content.select("table.spis tr:has(td)")?.forEach { recipientEl ->
val senderEl = recipientEl.select("td:eq(0)").first()
val senderName = senderEl.text()
val teacher = data.teacherList.singleOrNull { it.fullNameLastFirst == senderName }
val receiverId = teacher?.id ?: -1
var readDate = 0L
val isReadEl = recipientEl.select("td:eq(2)").first()
if (isReadEl.ownText() != "NIE") {
val readDateEl = recipientEl.select("td:eq(3) small").first()
Regexes.MOBIDZIENNIK_MESSAGE_SENT_READ_DATE.find(readDateEl.ownText())?.let {
val date = Date(
it[3].toIntOrNull() ?: 2019,
monthFromName(it[2]),
it[1].toIntOrNull() ?: 1
)
val time = Time.fromH_m_s(
it[4] // TODO blank string safety
)
readDate = date.combineWith(time)
}
}
val recipient = MessageRecipientFull(
profileId,
receiverId,
-1,
readDate,
message.id
)
recipient.fullName = teacher?.fullName ?: "?"
messageRecipientList.add(recipient)
}
}
// this line removes the sender and read date details
body.select("div").remove()
// this needs to be at the end
message.apply {
this.body = body.html()
clearAttachments()
content.select("ul li").map { it.select("a").first() }.forEach {
val attachmentName = it.ownText()
Regexes.MOBIDZIENNIK_MESSAGE_ATTACHMENT.find(it.outerHtml())?.let { match ->
val attachmentId = match[1].toLong()
var size = match[2].toFloatOrNull() ?: 0f
when (match[3]) {
"K" -> size *= 1024f
"M" -> size *= 1024f * 1024f
"G" -> size *= 1024f * 1024f * 1024f
}
message.addAttachment(attachmentId, attachmentName, size.toLong())
}
}
}
if (!message.seen) { // TODO discover why this monstrosity instead of MetadataDao.setSeen
data.messageMetadataList.add(Metadata(
message.profileId,
Metadata.TYPE_MESSAGE,
message.id,
true,
true,
message.addedDate
))
}
message.recipients = messageRecipientList
data.messageRecipientList.addAll(messageRecipientList)
data.messageList.add(message)
EventBus.getDefault().postSticky(MessageGetEvent(message))
onSuccess()
}
}
}

View file

@ -4,6 +4,7 @@
package pl.szczodrzynski.edziennik.api.v2.models package pl.szczodrzynski.edziennik.api.v2.models
import android.content.Context
import com.google.gson.JsonObject import com.google.gson.JsonObject
import im.wangchao.mhttp.Request import im.wangchao.mhttp.Request
import im.wangchao.mhttp.Response import im.wangchao.mhttp.Response
@ -52,6 +53,15 @@ class ApiError(val tag: String, val errorCode: Int) {
) )
} }
fun getStringReason(context: Context): String {
return context.resources.getIdentifier("error_${errorCode}_reason", "string", context.packageName).let {
if (it != 0)
context.getString(it)
else
"?"
}
}
override fun toString(): String { override fun toString(): String {
return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)" return "ApiError(tag='$tag', errorCode=$errorCode, profileId=$profileId, throwable=$throwable, apiResponse=$apiResponse, request=$request, response=$response, isCritical=$isCritical)"
} }

View file

@ -7,15 +7,14 @@ package pl.szczodrzynski.edziennik.api.v2.vulcan
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API import pl.szczodrzynski.edziennik.api.v2.LOGIN_METHOD_VULCAN_API
import pl.szczodrzynski.edziennik.api.v2.models.Data import pl.szczodrzynski.edziennik.api.v2.models.Data
import pl.szczodrzynski.edziennik.currentTimeUnix
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
import pl.szczodrzynski.edziennik.isNotNullNorEmpty import pl.szczodrzynski.edziennik.isNotNullNorEmpty
class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { class DataVulcan(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) {
fun isApiLoginValid() = apiCertificateExpiryTime-30 > currentTimeUnix() fun isApiLoginValid() = /*apiCertificateExpiryTime-30 > currentTimeUnix()
&& apiCertificateKey.isNotNullNorEmpty() &&*/ apiCertificateKey.isNotNullNorEmpty()
&& apiCertificatePrivate.isNotNullNorEmpty() && apiCertificatePrivate.isNotNullNorEmpty()
&& symbol.isNotNullNorEmpty() && symbol.isNotNullNorEmpty()

View file

@ -12,8 +12,10 @@ import pl.szczodrzynski.edziennik.api.v2.interfaces.EdziennikInterface
import pl.szczodrzynski.edziennik.api.v2.models.ApiError import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.edziennik.api.v2.prepare import pl.szczodrzynski.edziennik.api.v2.prepare
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanData import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanData
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.api.VulcanApiMessagesChangeStatus
import pl.szczodrzynski.edziennik.api.v2.vulcan.firstlogin.VulcanFirstLogin import pl.szczodrzynski.edziennik.api.v2.vulcan.firstlogin.VulcanFirstLogin
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLogin import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLogin
import pl.szczodrzynski.edziennik.api.v2.vulcan.login.VulcanLoginApi
import pl.szczodrzynski.edziennik.api.v2.vulcanLoginMethods import pl.szczodrzynski.edziennik.api.v2.vulcanLoginMethods
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
@ -63,7 +65,11 @@ class Vulcan(val app: App, val profile: Profile?, val loginStore: LoginStore, va
} }
override fun getMessage(message: MessageFull) { override fun getMessage(message: MessageFull) {
VulcanLoginApi(data) {
VulcanApiMessagesChangeStatus(data, message) {
completed()
}
}
} }
override fun markAllAnnouncementsAsRead() { override fun markAllAnnouncementsAsRead() {

View file

@ -64,6 +64,10 @@ class VulcanData(val data: DataVulcan, val onSuccess: () -> Unit) {
data.startProgress(R.string.edziennik_progress_endpoint_attendance) data.startProgress(R.string.edziennik_progress_endpoint_attendance)
VulcanApiAttendance(data, onSuccess) VulcanApiAttendance(data, onSuccess)
} }
ENDPOINT_VULCAN_API_TIMETABLE -> {
data.startProgress(R.string.edziennik_progress_endpoint_timetable)
VulcanApiTimetable(data, onSuccess)
}
ENDPOINT_VULCAN_API_MESSAGES_INBOX -> { ENDPOINT_VULCAN_API_MESSAGES_INBOX -> {
data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox)
VulcanApiMessagesInbox(data, onSuccess) VulcanApiMessagesInbox(data, onSuccess)

View file

@ -55,7 +55,7 @@ class VulcanApiAttendance(override val data: DataVulcan, val onSuccess: () -> Un
lessonSemester, lessonSemester,
attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" }, attendance.getString("PrzedmiotNazwa") + attendanceCategory.name.let { " - $it" },
lessonDate, lessonDate,
data.lessonRanges.get(attendance.getInt("IdPoraLekcji") ?: 0)?.startTime, data.lessonRanges.get(attendance.getInt("Numer") ?: 0)?.startTime,
type) type)
data.attendanceList.add(attendanceObject) data.attendanceList.add(attendanceObject)

View file

@ -35,7 +35,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
elements?.getJsonArray("KategorieUwag")?.forEach { saveNoticeType(it.asJsonObject) } elements?.getJsonArray("KategorieUwag")?.forEach { saveNoticeType(it.asJsonObject) }
elements?.getJsonArray("KategorieFrekwencji")?.forEach { saveAttendanceType(it.asJsonObject) } elements?.getJsonArray("KategorieFrekwencji")?.forEach { saveAttendanceType(it.asJsonObject) }
data.setSyncNext(ENDPOINT_VULCAN_API_DICTIONARIES, 4*DAY) data.setSyncNext(ENDPOINT_VULCAN_API_DICTIONARIES, 4 * DAY)
onSuccess() onSuccess()
} }
} }
@ -73,7 +73,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
} }
private fun saveLessonRange(lessonRange: JsonObject) { private fun saveLessonRange(lessonRange: JsonObject) {
val lessonNumber = lessonRange.getInt("Id") ?: return val lessonNumber = lessonRange.getInt("Numer") ?: return
val startTime = lessonRange.getString("PoczatekTekst")?.let { Time.fromH_m(it) } ?: return val startTime = lessonRange.getString("PoczatekTekst")?.let { Time.fromH_m(it) } ?: return
val endTime = lessonRange.getString("KoniecTekst")?.let { Time.fromH_m(it) } ?: return val endTime = lessonRange.getString("KoniecTekst")?.let { Time.fromH_m(it) } ?: return
@ -126,8 +126,7 @@ class VulcanApiDictionaries(override val data: DataVulcan, val onSuccess: () ->
Attendance.TYPE_ABSENT_EXCUSED Attendance.TYPE_ABSENT_EXCUSED
else else
Attendance.TYPE_ABSENT Attendance.TYPE_ABSENT
} } else {
else {
val belated = attendanceType.getBoolean("Spoznienie") ?: false val belated = attendanceType.getBoolean("Spoznienie") ?: false
val released = attendanceType.getBoolean("Zwolnienie") ?: false val released = attendanceType.getBoolean("Zwolnienie") ?: false
val present = attendanceType.getBoolean("Obecnosc") ?: true val present = attendanceType.getBoolean("Obecnosc") ?: true

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-12
*/
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
class VulcanApiMessagesChangeStatus(
override val data: DataVulcan,
private val messageObject: MessageFull,
val onSuccess: () -> Unit
) : VulcanApi(data) {
companion object {
const val TAG = "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
)) { _, _ ->
if (!messageObject.seen) {
data.messageMetadataList.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()
}
}
}
}

View file

@ -14,6 +14,8 @@ import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_RECEIVED
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
@ -43,7 +45,22 @@ class VulcanApiMessagesInbox(override val data: DataVulcan, val onSuccess: () ->
val senderLoginId = message.getString("NadawcaId") ?: return@forEach val senderLoginId = message.getString("NadawcaId") ?: return@forEach
val senderId = data.teacherList val senderId = data.teacherList
.singleOrNull { it.loginId == senderLoginId }?.id ?: return@forEach .singleOrNull { it.loginId == senderLoginId }?.id ?: {
val senderName = message.getString("Nadawca") ?: ""
senderName.getLastFirstName()?.let { (senderLastName, senderFirstName) ->
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16(senderName.toByteArray()).toLong(),
senderFirstName,
senderLastName,
senderLoginId
)
data.teacherList.put(teacherObject.id, teacherObject)
teacherObject.id
}
}.invoke() ?: -1
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 }
?: -1 ?: -1

View file

@ -14,6 +14,8 @@ import pl.szczodrzynski.edziennik.data.db.modules.messages.Message
import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT import pl.szczodrzynski.edziennik.data.db.modules.messages.Message.TYPE_SENT
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipient
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher
import pl.szczodrzynski.edziennik.utils.Utils
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) { class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
@ -23,11 +25,11 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
init { init {
data.profile?.also { profile -> data.profile?.also { profile ->
val startDate: String = when (profile.empty) { val startDate: Long = when (profile.empty) {
true -> profile.getSemesterStart(profile.currentSemester).stringY_m_d true -> profile.getSemesterStart(profile.currentSemester).inUnix
else -> Date.getToday().stepForward(0, -1, 0).stringY_m_d else -> Date.getToday().stepForward(0, -1, 0).inUnix
} }
val endDate: String = profile.getSemesterEnd(profile.currentSemester).stringY_m_d val endDate: Long = profile.getSemesterEnd(profile.currentSemester).inUnix
apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_SENT, parameters = mapOf( apiGet(TAG, VULCAN_API_ENDPOINT_MESSAGES_SENT, parameters = mapOf(
"DataPoczatkowa" to startDate, "DataPoczatkowa" to startDate,
@ -43,23 +45,27 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
val unreadBy = message.getInt("Nieprzeczytane") ?: 0 val unreadBy = message.getInt("Nieprzeczytane") ?: 0
val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } ?: -1 val sentDate = message.getLong("DataWyslaniaUnixEpoch")?.let { it * 1000 } ?: -1
val messageObject = Message(
profileId,
id,
subject,
body,
TYPE_SENT,
-1,
-1
)
message.getJsonArray("Adresaci")?.asJsonObjectList() message.getJsonArray("Adresaci")?.asJsonObjectList()
?.forEachIndexed { _, recipient -> ?.onEach { receiver ->
val recipientLoginId = recipient.getString("LoginId") val receiverLoginId = receiver.getString("LoginId")
?: return@forEachIndexed ?: return@onEach
val recipientId = data.teacherList.singleOrNull { it.loginId == recipientLoginId }?.id val receiverId = data.teacherList.singleOrNull { it.loginId == receiverLoginId }?.id
?: return@forEachIndexed ?: {
val receiverName = receiver.getString("Nazwa") ?: ""
receiverName.getLastFirstName()?.let { (receiverLastName, receiverFirstName) ->
val teacherObject = Teacher(
profileId,
-1 * Utils.crc16(receiverName.toByteArray()).toLong(),
receiverFirstName,
receiverLastName,
receiverLoginId
)
data.teacherList.put(teacherObject.id, teacherObject)
teacherObject.id
}
}.invoke() ?: -1
val readDate: Long = when (readBy) { val readDate: Long = when (readBy) {
0 -> 0 0 -> 0
@ -71,7 +77,7 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
val messageRecipientObject = MessageRecipient( val messageRecipientObject = MessageRecipient(
profileId, profileId,
recipientId, receiverId,
-1, -1,
readDate, readDate,
id id
@ -80,6 +86,16 @@ class VulcanApiMessagesSent(override val data: DataVulcan, val onSuccess: () ->
data.messageRecipientList.add(messageRecipientObject) data.messageRecipientList.add(messageRecipientObject)
} }
val messageObject = Message(
profileId,
id,
subject,
body,
TYPE_SENT,
-1,
-1
)
data.messageIgnoreList.add(messageObject) data.messageIgnoreList.add(messageObject)
data.metadataList.add(Metadata( data.metadataList.add(Metadata(
profileId, profileId,

View file

@ -0,0 +1,205 @@
/*
* Copyright (c) Kacper Ziubryniewicz 2019-11-13
*/
package pl.szczodrzynski.edziennik.api.v2.vulcan.data.api
import androidx.core.util.set
import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.api.v2.Regexes
import pl.szczodrzynski.edziennik.api.v2.VULCAN_API_ENDPOINT_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.models.DataRemoveModel
import pl.szczodrzynski.edziennik.api.v2.vulcan.DataVulcan
import pl.szczodrzynski.edziennik.api.v2.vulcan.ENDPOINT_VULCAN_API_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.vulcan.data.VulcanApi
import pl.szczodrzynski.edziennik.data.db.modules.api.SYNC_ALWAYS
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.utils.Utils.crc16
import pl.szczodrzynski.edziennik.utils.Utils.d
import pl.szczodrzynski.edziennik.utils.models.Date
class VulcanApiTimetable(override val data: DataVulcan, val onSuccess: () -> Unit) : VulcanApi(data) {
companion object {
const val TAG = "VulcanApiTimetable"
}
init { data.profile?.also { profile ->
val currentWeekStart = Date.getToday().let { it.stepForward(0, 0, -it.weekDay) }
val getDate = data.arguments?.getString("weekStart") ?: currentWeekStart.stringY_m_d
val weekStart = Date.fromY_m_d(getDate)
val weekEnd = weekStart.clone().stepForward(0, 0, 6)
apiGet(TAG, VULCAN_API_ENDPOINT_TIMETABLE, parameters = mapOf(
"DataPoczatkowa" to weekStart.stringY_m_d,
"DataKoncowa" to weekEnd.stringY_m_d,
"IdUczen" to data.studentId,
"IdOddzial" to data.studentClassId,
"IdOkresKlasyfikacyjny" to data.studentSemesterId
)) { json, _ ->
val dates: MutableSet<Int> = mutableSetOf()
val lessons: MutableList<Lesson> = mutableListOf()
json.getJsonArray("Data")?.asJsonObjectList()?.forEach { lesson ->
if (lesson.getBoolean("PlanUcznia") != true)
return@forEach
val lessonDate = Date.fromY_m_d(lesson.getString("DzienTekst"))
val lessonNumber = lesson.getInt("NumerLekcji")
val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }
val startTime = lessonRange?.startTime
val endTime = lessonRange?.endTime
val teacherId = lesson.getLong("IdPracownik")
val classroom = lesson.getString("Sala")
val oldTeacherId = lesson.getLong("IdPracownikOld")
val changeAnnotation = lesson.getString("AdnotacjaOZmianie") ?: ""
val type = when {
changeAnnotation.startsWith("(przeniesiona z") -> Lesson.TYPE_SHIFTED_TARGET
changeAnnotation.startsWith("(przeniesiona na") -> Lesson.TYPE_SHIFTED_SOURCE
changeAnnotation.startsWith("(zastępstwo") -> Lesson.TYPE_CHANGE
lesson.getBoolean("PrzekreslonaNazwa") == true -> Lesson.TYPE_CANCELLED
else -> Lesson.TYPE_NORMAL
}
val teamId = lesson.getString("PodzialSkrot")?.let { teamName ->
val name = "${data.teamClass?.name} $teamName"
val id = name.crc16().toLong()
var team = data.teamList.singleOrNull { it.name == name }
if (team == null) {
team = Team(
profileId,
id,
name,
Team.TYPE_VIRTUAL,
"${data.schoolName}:$name",
teacherId ?: oldTeacherId ?: -1
)
data.teamList[id] = team
}
team.id
} ?: data.studentClassId.toLong()
val subjectId = lesson.getLong("IdPrzedmiot")?.let {
when (it) {
0L -> {
val subjectName = lesson.getString("PrzedmiotNazwa") ?: ""
data.subjectList.singleOrNull { subject -> subject.longName == subjectName }?.id
?: {
/**
* CREATE A NEW SUBJECT IF IT DOESN'T EXIST
*/
val subjectObject = Subject(
profileId,
-1 * crc16(subjectName.toByteArray()).toLong(),
subjectName,
subjectName
)
data.subjectList.put(subjectObject.id, subjectObject)
subjectObject.id
}.invoke()
}
else -> it
}
}
val id = lessonDate.combineWith(startTime) / 6L * 10L + (lesson.hashCode() and 0xFFFF)
val lessonObject = Lesson(profileId, id).apply {
this.type = type
when (type) {
Lesson.TYPE_NORMAL, Lesson.TYPE_CHANGE, Lesson.TYPE_SHIFTED_TARGET -> {
this.date = lessonDate
this.lessonNumber = lessonNumber
this.startTime = startTime
this.endTime = endTime
this.subjectId = subjectId
this.teacherId = teacherId
this.teamId = teamId
this.classroom = classroom
this.oldTeacherId = oldTeacherId
}
Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
this.oldDate = lessonDate
this.oldLessonNumber = lessonNumber
this.oldStartTime = startTime
this.oldEndTime = endTime
this.oldSubjectId = subjectId
this.oldTeacherId = teacherId
this.oldTeamId = teamId
this.oldClassroom = classroom
}
}
if (type == Lesson.TYPE_SHIFTED_SOURCE || type == Lesson.TYPE_SHIFTED_TARGET) {
val shift = Regexes.VULCAN_SHITFT_ANNOTATION.find(changeAnnotation)
val oldLessonNumber = shift?.get(2)?.toInt()
val oldLessonDate = shift?.get(3)?.let { Date.fromd_m_Y(it) }
val oldLessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == oldLessonNumber }
val oldStartTime = oldLessonRange?.startTime
val oldEndTime = oldLessonRange?.endTime
when (type) {
Lesson.TYPE_SHIFTED_SOURCE -> {
this.lessonNumber = oldLessonNumber
this.date = oldLessonDate
this.startTime = oldStartTime
this.endTime = oldEndTime
}
Lesson.TYPE_SHIFTED_TARGET -> {
this.oldLessonNumber = oldLessonNumber
this.oldDate = oldLessonDate
this.oldStartTime = oldStartTime
this.oldEndTime = oldEndTime
}
}
}
}
if (type != Lesson.TYPE_NORMAL) {
data.metadataList.add(Metadata(
profileId,
Metadata.TYPE_LESSON_CHANGE,
id,
profile.empty,
profile.empty,
System.currentTimeMillis()
))
}
dates.add(lessonDate.value)
lessons.add(lessonObject)
}
val date: Date = weekStart.clone()
while (date <= weekEnd) {
if (!dates.contains(date.value)) {
lessons.add(Lesson(profileId, date.value.toLong()).apply {
this.type = Lesson.TYPE_NO_LESSONS
this.date = date.clone()
})
}
date.stepForward(0, 0, 1)
}
d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate")
data.lessonNewList.addAll(lessons)
data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd))
data.setSyncNext(ENDPOINT_VULCAN_API_TIMETABLE, SYNC_ALWAYS)
onSuccess()
}
}}
}

View file

@ -1,11 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import im.wangchao.mhttp.Request;
/**
* Callback containing a {@link Request.Builder} which has correct headers and body to download a corresponding message attachment when ran.
* {@code onSuccess} has to be ran on the UI thread.
*/
public interface AttachmentGetCallback {
void onSuccess(Request.Builder builder);
}

View file

@ -1,92 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Map;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher;
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeInfo;
import pl.szczodrzynski.edziennik.utils.models.Endpoint;
public interface EdziennikInterface {
/**
* Sync all Edziennik data.
* Ran always on worker thread.
*
* @param activityContext a {@link Context}, used for resource extractions, passed back to {@link SyncCallback}
* @param callback ran on worker thread.
* @param profileId
* @param profile
* @param loginStore
*/
void sync(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore);
void syncMessages(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile);
void syncFeature(@NonNull Context activityContext, @NonNull SyncCallback callback, @NonNull ProfileFull profile, int ... featureList);
int FEATURE_ALL = 0;
int FEATURE_TIMETABLE = 1;
int FEATURE_AGENDA = 2;
int FEATURE_GRADES = 3;
int FEATURE_HOMEWORK = 4;
int FEATURE_NOTICES = 5;
int FEATURE_ATTENDANCE = 6;
int FEATURE_MESSAGES_INBOX = 7;
int FEATURE_MESSAGES_OUTBOX = 8;
int FEATURE_ANNOUNCEMENTS = 9;
/**
* Download a single message or get its recipient list if it's already downloaded.
*
* May be executed on any thread.
*
* @param activityContext
* @param errorCallback used for error reporting. Ran on a background thread.
* @param profile
* @param message a message of which body and recipient list should be downloaded.
* @param messageCallback always executed on UI thread.
*/
void getMessage(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, @NonNull MessageGetCallback messageCallback);
void getAttachment(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, long attachmentId, @NonNull AttachmentGetCallback attachmentCallback);
//void getMessageList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, int type, @NonNull MessageListCallback messageCallback);
/**
* Download a list of available message recipients.
*
* Updates a database-saved {@code teacherList} with {@code loginId}s.
*
* A {@link Teacher} is considered as a recipient when its {@code loginId} is not null.
*
* May be executed on any thread.
*
* @param activityContext
* @param errorCallback used for error reporting. Ran on a background thread.
* @param profile
* @param recipientListGetCallback always executed on UI thread.
*/
void getRecipientList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull RecipientListGetCallback recipientListGetCallback);
MessagesComposeInfo getComposeInfo(@NonNull ProfileFull profile);
/**
*
* @param profile a {@link Profile} containing already changed endpoints
* @return a map of configurable {@link Endpoint}s along with their names, {@code null} when unsupported
*/
Map<String, Endpoint> getConfigurableEndpoints(Profile profile);
/**
* Check if the specified endpoint is enabled for the current profile.
*
* @param profile a {@link Profile} containing already changed endpoints
* @param defaultActive if the endpoint is enabled by default.
* @param name the endpoint's name
* @return {@code true} if the endpoint is enabled, {@code false} when it's not. Return {@code defaultActive} if unsupported.
*/
boolean isEndpointEnabled(Profile profile, boolean defaultActive, String name);
}

View file

@ -1,11 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import android.content.Context;
import androidx.annotation.NonNull;
import pl.szczodrzynski.edziennik.data.api.AppError;
public interface ErrorCallback {
void onError(Context activityContext, @NonNull AppError error);
}

View file

@ -1,5 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
public interface LoginCallback {
void onSuccess();
}

View file

@ -1,11 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
/**
* Callback containing a {@link MessageFull} which already has its {@code body} and {@code recipients}.
* {@code onSuccess} is always ran on the UI thread.
*/
public interface MessageGetCallback {
void onSuccess(MessageFull message);
}

View file

@ -1,9 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
public interface MessageListCallback {
void onSuccess(List<MessageFull> messageList);
}

View file

@ -1,8 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import androidx.annotation.StringRes;
public interface ProgressCallback extends ErrorCallback {
void onProgress(int progressStep);
void onActionStarted(@StringRes int stringResId);
}

View file

@ -1,9 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher;
public interface RecipientListGetCallback {
void onSuccess(List<Teacher> teacherList);
}

View file

@ -1,19 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.interfaces;
import android.content.Context;
import java.util.List;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
/**
* A callback used for error reporting, progress information.
* All the methods are always ran on a worker thread.
*/
public interface SyncCallback extends ProgressCallback {
void onLoginFirst(List<Profile> profileList, LoginStore loginStore);
void onSuccess(Context activityContext, ProfileFull profileFull);
}

View file

@ -1,6 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.v2
import pl.szczodrzynski.edziennik.data.api.AppError
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
data class ApiLoginResult(val loginStore: LoginStore, val error: AppError?)

View file

@ -1,6 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.v2
import pl.szczodrzynski.edziennik.data.api.AppError
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
data class FirstLoginResult(val profileList: ArrayList<Profile>, val error: AppError?)

View file

@ -1,12 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.v2.librus.firstlogin
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
class FirstLoginLibrus(val app: App, val loginStore: LoginStore, val progressCallback: ProgressCallback, val onSuccess: (profileList: List<Profile>) -> Unit) {
init {
}
}

View file

@ -1,12 +0,0 @@
package pl.szczodrzynski.edziennik.data.api.v2.librus.firstlogin
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.interfaces.ProgressCallback
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile
class FirstLoginSynergia(val app: App, val loginStore: LoginStore, val progressCallback: ProgressCallback, val onSuccess: (profileList: List<Profile>) -> Unit) {
init {
}
}

View file

@ -1,10 +1,10 @@
package pl.szczodrzynski.edziennik.data.db.modules.grades; package pl.szczodrzynski.edziennik.data.db.modules.grades;
import androidx.room.Entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.room.Entity;
@Entity(tableName = "gradeCategories", @Entity(tableName = "gradeCategories",
primaryKeys = {"profileId", "categoryId"}) primaryKeys = {"profileId", "categoryId"})
public class GradeCategory { public class GradeCategory {
@ -25,6 +25,9 @@ public class GradeCategory {
*/ */
public int type = 0; public int type = 0;
public static final int TYPE_NORMAL = 0;
public static final int TYPE_COMMENT = 1;
public GradeCategory(int profileId, long categoryId, float weight, int color, String text) { public GradeCategory(int profileId, long categoryId, float weight, int color, String text) {
this.profileId = profileId; this.profileId = profileId;
this.categoryId = categoryId; this.categoryId = categoryId;

View file

@ -10,6 +10,27 @@ import pl.szczodrzynski.edziennik.utils.models.Date
@Dao @Dao
interface TimetableDao { interface TimetableDao {
companion object {
private const val QUERY = """
SELECT
timetable.*,
subjects.subjectLongName AS subjectName,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
teams.teamName AS teamName,
oldS.subjectLongName AS oldSubjectName,
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
oldG.teamName AS oldTeamName,
metadata.seen, metadata.notified, metadata.addedDate
FROM timetable
LEFT JOIN subjects USING(profileId, subjectId)
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teams USING(profileId, teamId)
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
"""
}
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
operator fun plusAssign(lessonList: List<Lesson>) operator fun plusAssign(lessonList: List<Lesson>)
@ -17,57 +38,47 @@ interface TimetableDao {
@Query("DELETE FROM timetable WHERE profileId = :profileId") @Query("DELETE FROM timetable WHERE profileId = :profileId")
fun clear(profileId: Int) fun clear(profileId: Int)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom)") @Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom))")
fun clearFromDate(profileId: Int, dateFrom: Date) fun clearFromDate(profileId: Int, dateFrom: Date)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo)") @Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate <= :dateTo))")
fun clearToDate(profileId: Int, dateTo: Date) fun clearToDate(profileId: Int, dateTo: Date)
@Query("DELETE FROM timetable WHERE profileId = :profileId AND (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)") @Query("DELETE FROM timetable WHERE profileId = :profileId AND ((type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo))")
fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date) fun clearBetweenDates(profileId: Int, dateFrom: Date, dateTo: Date)
@Query(""" @Query("""
SELECT $QUERY
timetable.*, WHERE timetable.profileId = :profileId AND ((type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date))
subjects.subjectLongName AS subjectName,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
teams.teamName AS teamName,
oldS.subjectLongName AS oldSubjectName,
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
oldG.teamName AS oldTeamName,
metadata.seen, metadata.notified, metadata.addedDate
FROM timetable
LEFT JOIN subjects USING(profileId, subjectId)
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teams USING(profileId, teamId)
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
WHERE timetable.profileId = :profileId AND (type != 3 AND date = :date) OR ((type = 3 OR type = 1) AND oldDate = :date)
ORDER BY id, type ORDER BY id, type
""") """)
fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>> fun getForDate(profileId: Int, date: Date) : LiveData<List<LessonFull>>
@Query(""" @Query("""
SELECT $QUERY
timetable.*,
subjects.subjectLongName AS subjectName,
teachers.teacherName ||" "|| teachers.teacherSurname AS teacherName,
teams.teamName AS teamName,
oldS.subjectLongName AS oldSubjectName,
oldT.teacherName ||" "|| oldT.teacherSurname AS oldTeacherName,
oldG.teamName AS oldTeamName,
metadata.seen, metadata.notified, metadata.addedDate
FROM timetable
LEFT JOIN subjects USING(profileId, subjectId)
LEFT JOIN teachers USING(profileId, teacherId)
LEFT JOIN teams USING(profileId, teamId)
LEFT JOIN subjects AS oldS ON timetable.profileId = oldS.profileId AND timetable.oldSubjectId = oldS.subjectId
LEFT JOIN teachers AS oldT ON timetable.profileId = oldT.profileId AND timetable.oldTeacherId = oldT.teacherId
LEFT JOIN teams AS oldG ON timetable.profileId = oldG.profileId AND timetable.oldTeamId = oldG.teamId
LEFT JOIN metadata ON id = thingId AND thingType = ${Metadata.TYPE_LESSON_CHANGE} AND metadata.profileId = timetable.profileId
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId
ORDER BY id, type ORDER BY id, type
LIMIT 1 LIMIT 1
""") """)
fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull> fun getNextWithSubject(profileId: Int, today: Date, subjectId: Long) : LiveData<LessonFull?>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND ((type != 3 AND date > :today) OR ((type = 3 OR type = 1) AND oldDate > :today)) AND timetable.subjectId = :subjectId AND timetable.teamId = :teamId
ORDER BY id, type
LIMIT 1
""")
fun getNextWithSubjectAndTeam(profileId: Int, today: Date, subjectId: Long, teamId: Long): LiveData<LessonFull?>
@Query("""
$QUERY
WHERE (type != 3 AND date >= :dateFrom AND date <= :dateTo) OR ((type = 3 OR type = 1) AND oldDate >= :dateFrom AND oldDate <= :dateTo)
ORDER BY profileId, id, type
""")
fun getBetweenDatesNow(dateFrom: Date, dateTo: Date) : List<LessonFull>
@Query("""
$QUERY
WHERE timetable.profileId = :profileId AND timetable.id = :lessonId
ORDER BY id, type
""")
fun getByIdNow(profileId: Int, lessonId: Long) : LessonFull?
} }

View file

@ -125,19 +125,22 @@ public class BootReceiver extends BroadcastReceiver {
String updateUrl = result.get("update_url").getAsString(); String updateUrl = result.get("update_url").getAsString();
String updateFilename = result.get("update_filename").getAsString(); String updateFilename = result.get("update_filename").getAsString();
boolean updateMandatory = result.get("update_mandatory").getAsBoolean(); boolean updateMandatory = result.get("update_mandatory").getAsBoolean();
boolean updateDirect = result.get("update_direct").getAsBoolean();
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion; app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl; app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename; app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory; app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig(); app.saveConfig();
} }
if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) { if (!UPDATES_ON_PLAY_STORE || intent.getBooleanExtra("UserChecked", false)) {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
updateVersion, updateVersion,
updateUrl, updateUrl,
updateFilename); updateFilename,
updateDirect);
} }
} else { } else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {
@ -259,7 +262,7 @@ public class BootReceiver extends BroadcastReceiver {
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (UPDATES_ON_PLAY_STORE) { if (UPDATES_ON_PLAY_STORE && !intent.getBooleanExtra("update_direct", false)) {
Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik"); Utils.openGooglePlay(this, "pl.szczodrzynski.edziennik");
return; return;
} }
@ -270,7 +273,8 @@ public class BootReceiver extends BroadcastReceiver {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
intent.getStringExtra("update_version"), intent.getStringExtra("update_version"),
intent.getStringExtra("update_url"), intent.getStringExtra("update_url"),
intent.getStringExtra("update_filename")); intent.getStringExtra("update_filename"),
intent.getBooleanExtra("update_direct", false));
return; return;
} }
} }

View file

@ -154,19 +154,22 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
String updateUrl = remoteMessage.getData().get("update_url"); String updateUrl = remoteMessage.getData().get("update_url");
String updateFilename = remoteMessage.getData().get("update_filename"); String updateFilename = remoteMessage.getData().get("update_filename");
boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory")); boolean updateMandatory = Boolean.parseBoolean(remoteMessage.getData().get("update_mandatory"));
boolean updateDirect = Boolean.parseBoolean(remoteMessage.getData().get("update_direct"));
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals(updateVersion)) {
app.appConfig.updateVersion = updateVersion; app.appConfig.updateVersion = updateVersion;
app.appConfig.updateUrl = updateUrl; app.appConfig.updateUrl = updateUrl;
app.appConfig.updateFilename = updateFilename; app.appConfig.updateFilename = updateFilename;
app.appConfig.updateMandatory = updateMandatory; app.appConfig.updateMandatory = updateMandatory;
app.appConfig.updateDirect = updateDirect;
app.saveConfig("updateVersion", "updateUrl", "updateFilename", "updateMandatory"); app.saveConfig("updateVersion", "updateUrl", "updateFilename", "updateMandatory");
} }
if (!remoteMessage.getData().containsKey("update_silent")) { if (!remoteMessage.getData().containsKey("update_silent")) {
app.notifier.notificationUpdatesShow( app.notifier.notificationUpdatesShow(
updateVersion, updateVersion,
updateUrl, updateUrl,
updateFilename); updateFilename,
updateDirect);
} }
} else { } else {
if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) { if (app.appConfig.updateVersion == null || !app.appConfig.updateVersion.equals("")) {

View file

@ -36,7 +36,6 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.data.db.modules.events.Event; import pl.szczodrzynski.edziennik.data.db.modules.events.Event;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull; import pl.szczodrzynski.edziennik.data.db.modules.events.EventFull;
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType; import pl.szczodrzynski.edziennik.data.db.modules.events.EventType;
@ -54,7 +53,6 @@ import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.models.Week; import pl.szczodrzynski.edziennik.utils.models.Week;
import static pl.szczodrzynski.edziennik.App.APP_URL; import static pl.szczodrzynski.edziennik.App.APP_URL;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_OTHER;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.COLOR_DEFAULT; import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.COLOR_DEFAULT;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK; import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_HOMEWORK;
import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_UNDEFINED; import static pl.szczodrzynski.edziennik.data.db.modules.events.Event.TYPE_UNDEFINED;
@ -374,7 +372,8 @@ public class EventManualDialog {
} }
} catch (Exception e) { } catch (Exception e) {
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
app.apiEdziennik.guiShowErrorDialog(activity, new AppError(TAG, 379, CODE_OTHER, null, e), R.string.error_occured); // TODO show error in EventManualDialog
//app.apiEdziennik.guiShowErrorDialog(activity, new AppError(TAG, 379, CODE_OTHER, null, e), R.string.error_occured);
}); });
} }
}); });

View file

@ -4,19 +4,25 @@
package pl.szczodrzynski.edziennik.ui.dialogs.event package pl.szczodrzynski.edziennik.ui.dialogs.event
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import kotlinx.coroutines.* import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.data.db.modules.events.Event import pl.szczodrzynski.edziennik.data.db.modules.events.Event
import pl.szczodrzynski.edziennik.data.db.modules.events.EventType
import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject import pl.szczodrzynski.edziennik.data.db.modules.subjects.Subject
import pl.szczodrzynski.edziennik.data.db.modules.teams.Team import pl.szczodrzynski.edziennik.data.db.modules.teams.Team
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding
import pl.szczodrzynski.edziennik.utils.Anim
import pl.szczodrzynski.edziennik.utils.TextInputDropDown import pl.szczodrzynski.edziennik.utils.TextInputDropDown
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Time import pl.szczodrzynski.edziennik.utils.models.Time
@ -30,7 +36,9 @@ class EventManualV2Dialog(
val defaultDate: Date? = null, val defaultDate: Date? = null,
val defaultTime: Time? = null, val defaultTime: Time? = null,
val defaultType: Int? = null, val defaultType: Int? = null,
val editingEvent: Event? = null val editingEvent: Event? = null,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) : CoroutineScope { ) : CoroutineScope {
companion object { companion object {
@ -44,18 +52,25 @@ class EventManualV2Dialog(
private val app by lazy { activity.application as App } private val app by lazy { activity.application as App }
private lateinit var b: DialogEventManualV2Binding private lateinit var b: DialogEventManualV2Binding
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
private lateinit var event: Event
private var defaultLoaded = false private var defaultLoaded = false
init { run { private lateinit var event: Event
job = Job() private var customColor: Int? = null
init { run {
if (activity.isFinishing)
return@run
job = Job()
onShowListener?.invoke(TAG)
b = DialogEventManualV2Binding.inflate(activity.layoutInflater) b = DialogEventManualV2Binding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_event_manual_title) .setTitle(R.string.dialog_event_manual_title)
.setView(b.root) .setView(b.root)
.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setPositiveButton(R.string.save) { _, _ -> saveEvent() } .setPositiveButton(R.string.save) { _, _ -> saveEvent() }
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show() .show()
event = editingEvent?.clone() ?: Event().also { event -> event = editingEvent?.clone() ?: Event().also { event ->
@ -73,6 +88,17 @@ class EventManualV2Dialog(
}*/ }*/
} }
b.showMore.onClick { // TODO iconics is broken
it.apply {
refreshDrawableState()
if (isChecked)
Anim.expand(b.moreLayout, 200, null)
else
Anim.collapse(b.moreLayout, 200, null)
}
}
loadLists() loadLists()
}} }}
@ -107,18 +133,33 @@ class EventManualV2Dialog(
"" ""
) )
b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) } b.teacherDropdown += teachers.map { TextInputDropDown.Item(it.id, it.fullName, tag = it) }
// get the event type list
val eventTypes = app.db.eventTypeDao().getAllNow(profileId)
b.typeDropdown.clear()
b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) }
} }
deferred.await() deferred.await()
b.teamDropdown.isEnabled = true b.teamDropdown.isEnabled = true
b.subjectDropdown.isEnabled = true b.subjectDropdown.isEnabled = true
b.teacherDropdown.isEnabled = true b.teacherDropdown.isEnabled = true
b.typeDropdown.isEnabled = true
b.typeDropdown.selected?.let { item ->
customColor = (item.tag as EventType).color
}
// copy IDs from event being edited // copy IDs from event being edited
editingEvent?.let { editingEvent?.let {
b.teamDropdown.select(it.teamId) b.teamDropdown.select(it.teamId)
b.subjectDropdown.select(it.subjectId) b.subjectDropdown.select(it.subjectId)
b.teacherDropdown.select(it.teacherId) b.teacherDropdown.select(it.teacherId)
b.typeDropdown.select(it.type)?.let { item ->
customColor = (item.tag as EventType).color
}
if (it.color != -1)
customColor = it.color
} }
// copy IDs from the LessonFull // copy IDs from the LessonFull
@ -128,6 +169,30 @@ class EventManualV2Dialog(
b.teacherDropdown.select(it.displayTeacherId) b.teacherDropdown.select(it.displayTeacherId)
} }
b.typeDropdown.setOnChangeListener {
b.typeColor.background.colorFilter = PorterDuffColorFilter((it.tag as EventType).color, PorterDuff.Mode.SRC_ATOP)
customColor = null
return@setOnChangeListener true
}
(customColor ?: Event.COLOR_DEFAULT).let {
b.typeColor.background.colorFilter = PorterDuffColorFilter(it, PorterDuff.Mode.SRC_ATOP)
}
b.typeColor.onClick {
val currentColor = (b.typeDropdown?.selected?.tag as EventType?)?.color ?: Event.COLOR_DEFAULT
val colorPickerDialog = ColorPickerDialog.newBuilder()
.setColor(currentColor)
.create()
colorPickerDialog.setColorPickerDialogListener(
object : ColorPickerDialogListener {
override fun onDialogDismissed(dialogId: Int) {}
override fun onColorSelected(dialogId: Int, color: Int) {
b.typeColor.background.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
customColor = color
}
})
colorPickerDialog.show(activity.fragmentManager, "color-picker-dialog")
}
loadDates() loadDates()
}} }}
@ -198,12 +263,20 @@ class EventManualV2Dialog(
val dates = deferred.await() val dates = deferred.await()
b.dateDropdown.clear().append(dates) b.dateDropdown.clear().append(dates)
editingEvent?.let { editingEvent?.eventDate?.let {
b.dateDropdown.select(it.eventDate.value.toLong()) b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
} }
defaultLesson?.let { defaultLesson?.displayDate?.let {
b.dateDropdown.select(it.displayDate?.value?.toLong()) b.dateDropdown.select(TextInputDropDown.Item(
it.value.toLong(),
it.formattedString,
tag = it
))
} }
if (b.dateDropdown.selected == null) { if (b.dateDropdown.selected == null) {
@ -216,22 +289,24 @@ class EventManualV2Dialog(
when { when {
// next lesson with specified subject // next lesson with specified subject
item.id < -1 -> { item.id < -1 -> {
app.db.timetableDao().getNextWithSubject(profileId, Date.getToday(), -item.id).observeOnce(activity, Observer { val teamId = defaultLesson?.teamId ?: -1
val selectedLessonDate = defaultLesson?.date ?: Date.getToday()
when (teamId) {
-1L -> app.db.timetableDao().getNextWithSubject(profileId, selectedLessonDate, -item.id)
else -> app.db.timetableDao().getNextWithSubjectAndTeam(profileId, selectedLessonDate, -item.id, teamId)
}.observeOnce(activity, Observer {
val lessonDate = it?.displayDate ?: return@Observer val lessonDate = it?.displayDate ?: return@Observer
b.dateDropdown.selected = TextInputDropDown.Item( b.dateDropdown.select(TextInputDropDown.Item(
lessonDate.value.toLong(), lessonDate.value.toLong(),
lessonDate.formattedString, lessonDate.formattedString,
tag = lessonDate tag = lessonDate
) ))
// TODO load correct hour when selecting next lesson b.teamDropdown.select(it.displayTeamId)
b.dateDropdown.updateText() b.subjectDropdown.select(it.displaySubjectId)
it.let { b.teacherDropdown.select(it.displayTeacherId)
b.teamDropdown.select(it.displayTeamId)
b.subjectDropdown.select(it.displaySubjectId)
b.teacherDropdown.select(it.displayTeacherId)
}
defaultLoaded = false defaultLoaded = false
loadHours() loadHours(it.displayStartTime)
}) })
return@setOnChangeListener false return@setOnChangeListener false
} }
@ -239,17 +314,17 @@ class EventManualV2Dialog(
item.id == -1L -> { item.id == -1L -> {
MaterialDatePicker.Builder MaterialDatePicker.Builder
.datePicker() .datePicker()
.setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: Date.getToday()).inMillis) .setSelection((b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) }
?: Date.getToday()).inMillis)
.build() .build()
.apply { .apply {
addOnPositiveButtonClickListener { addOnPositiveButtonClickListener {
val dateSelected = Date.fromMillis(it) val dateSelected = Date.fromMillis(it)
b.dateDropdown.selected = TextInputDropDown.Item( b.dateDropdown.select(TextInputDropDown.Item(
dateSelected.value.toLong(), dateSelected.value.toLong(),
dateSelected.formattedString, dateSelected.formattedString,
tag = dateSelected tag = dateSelected
) ))
b.dateDropdown.updateText()
loadHours() loadHours()
} }
show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker") show(this@EventManualV2Dialog.activity.supportFragmentManager, "MaterialDatePicker")
@ -269,7 +344,7 @@ class EventManualV2Dialog(
loadHours() loadHours()
}} }}
private fun loadHours() { private fun loadHours(defaultHour: Time? = null) {
b.timeDropdown.isEnabled = false b.timeDropdown.isEnabled = false
// get the selected date // get the selected date
val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return val date = b.dateDropdown.selectedId?.let { Date.fromValue(it.toInt()) } ?: return
@ -295,7 +370,8 @@ class EventManualV2Dialog(
lesson.displayStartTime?.stringHM ?: "", lesson.displayStartTime?.stringHM ?: "",
lesson.displaySubjectName?.let { lesson.displaySubjectName?.let {
when { when {
lesson.type == Lesson.TYPE_CANCELLED -> it.asStrikethroughSpannable() lesson.type == Lesson.TYPE_CANCELLED
|| lesson.type == Lesson.TYPE_SHIFTED_SOURCE -> it.asStrikethroughSpannable()
lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable() lesson.type != Lesson.TYPE_NORMAL -> it.asItalicSpannable()
else -> it else -> it
} }
@ -331,6 +407,10 @@ class EventManualV2Dialog(
defaultLesson?.let { defaultLesson?.let {
b.timeDropdown.select(it.displayStartTime?.value?.toLong()) b.timeDropdown.select(it.displayStartTime?.value?.toLong())
} }
defaultHour?.let {
b.timeDropdown.select(it.value.toLong())
}
} }
defaultLoaded = true defaultLoaded = true
b.timeDropdown.isEnabled = true b.timeDropdown.isEnabled = true
@ -338,16 +418,16 @@ class EventManualV2Dialog(
// attach a listener to time dropdown // attach a listener to time dropdown
b.timeDropdown.setOnChangeListener { item -> b.timeDropdown.setOnChangeListener { item ->
when { when {
// custom start hour
item.id == -1L -> {
return@setOnChangeListener false
}
// no lessons this day // no lessons this day
item.id == -2L -> { item.id == -2L -> {
b.timeDropdown.deselect() b.timeDropdown.deselect()
return@setOnChangeListener false return@setOnChangeListener false
} }
// custom start hour
item.id == -1L -> {
return@setOnChangeListener false
}
// selected a specific lesson // selected a specific lesson
else -> { else -> {
if (item.tag is LessonFull) { if (item.tag is LessonFull) {

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.settings
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.utils.models.Notification
import java.util.*
import kotlin.coroutines.CoroutineContext
class ProfileRemoveDialog(
val activity: MainActivity,
val profileId: Int,
val profileName: String
) : CoroutineScope {
companion object {
private const val TAG = "ProfileRemoveDialog"
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val app by lazy { activity.application as App }
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
init { run {
job = Job()
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.profile_menu_remove_confirm)
.setMessage(activity.getString(R.string.profile_menu_remove_confirm_text_format, profileName, profileName))
.setPositiveButton(R.string.remove) { _, _ ->
removeProfile()
}
.setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
.setCancelable(false)
.show()
}}
private fun removeProfile() { launch {
val deferred = async(Dispatchers.Default) {
val profileObject = app.db.profileDao().getByIdNow(profileId) ?: return@async
app.db.announcementDao().clear(profileId)
app.db.attendanceDao().clear(profileId)
app.db.eventDao().clear(profileId)
app.db.eventTypeDao().clear(profileId)
app.db.gradeDao().clear(profileId)
app.db.gradeCategoryDao().clear(profileId)
app.db.lessonDao().clear(profileId)
app.db.lessonChangeDao().clear(profileId)
app.db.luckyNumberDao().clear(profileId)
app.db.noticeDao().clear(profileId)
app.db.subjectDao().clear(profileId)
app.db.teacherDao().clear(profileId)
app.db.teamDao().clear(profileId)
app.db.messageRecipientDao().clear(profileId)
app.db.messageDao().clear(profileId)
app.db.endpointTimerDao().clear(profileId)
app.db.attendanceTypeDao().clear(profileId)
app.db.classroomDao().clear(profileId)
app.db.lessonRangeDao().clear(profileId)
app.db.noticeTypeDao().clear(profileId)
app.db.teacherAbsenceDao().clear(profileId)
app.db.teacherAbsenceTypeDao().clear(profileId)
app.db.timetableDao().clear(profileId)
val loginStoreId = profileObject.loginStoreId
val profilesUsingLoginStore = app.db.profileDao().getIdsByLoginStoreIdNow(loginStoreId)
if (profilesUsingLoginStore.size == 1) {
app.db.loginStoreDao().remove(loginStoreId)
}
app.db.profileDao().remove(profileId)
app.db.metadataDao().deleteAll(profileId)
val toRemove = ArrayList<Notification>()
for (notification in app.appConfig.notifications) {
if (notification.profileId == profileId) {
toRemove.add(notification)
}
}
app.appConfig.notifications.removeAll(toRemove)
app.profile = null
App.profileId = -1
app.profileLoadById(app.profileLastId())
}
deferred.await()
dialog.dismiss()
activity.reloadTarget()
Toast.makeText(activity, R.string.dialog_profile_remove_success, Toast.LENGTH_LONG).show()
}}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
*/
package pl.szczodrzynski.edziennik.ui.dialogs.sync
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import kotlin.coroutines.CoroutineContext
class SyncViewListDialog(
val activity: MainActivity,
val currentViewId: Int? = null
) : CoroutineScope {
companion object {
private const val TAG = "SyncViewListDialog"
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val app by lazy { activity.application as App }
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
init { run {
job = Job()
val viewIds = arrayOf(
MainActivity.DRAWER_ITEM_TIMETABLE,
MainActivity.DRAWER_ITEM_AGENDA,
MainActivity.DRAWER_ITEM_GRADES,
MainActivity.DRAWER_ITEM_HOMEWORK,
MainActivity.DRAWER_ITEM_BEHAVIOUR,
MainActivity.DRAWER_ITEM_ATTENDANCE,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_MESSAGES,
MainActivity.DRAWER_ITEM_ANNOUNCEMENTS
)
val items = arrayOf<String>(
app.getString(R.string.menu_timetable),
app.getString(R.string.menu_agenda),
app.getString(R.string.menu_grades),
app.getString(R.string.menu_homework),
app.getString(R.string.menu_notices),
app.getString(R.string.menu_attendance),
app.getString(R.string.title_messages_inbox_single),
app.getString(R.string.title_messages_sent_single),
app.getString(R.string.menu_announcements)
)
val everything = currentViewId == MainActivity.DRAWER_ITEM_HOME
val checkedItems = booleanArrayOf(
everything || currentViewId == MainActivity.DRAWER_ITEM_TIMETABLE,
everything || currentViewId == MainActivity.DRAWER_ITEM_AGENDA,
everything || currentViewId == MainActivity.DRAWER_ITEM_GRADES,
everything || currentViewId == MainActivity.DRAWER_ITEM_HOMEWORK,
everything || currentViewId == MainActivity.DRAWER_ITEM_BEHAVIOUR,
everything || currentViewId == MainActivity.DRAWER_ITEM_ATTENDANCE,
everything || currentViewId == MainActivity.DRAWER_ITEM_MESSAGES && MessagesFragment.pageSelection != 1,
everything || currentViewId == MainActivity.DRAWER_ITEM_MESSAGES && MessagesFragment.pageSelection == 1,
everything || currentViewId == MainActivity.DRAWER_ITEM_ANNOUNCEMENTS
)
val userChooses = checkedItems.toMutableList()
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_sync_view_list_title)
.setMultiChoiceItems(items, checkedItems) { _, which, isChecked ->
userChooses[which] = isChecked
}
.setPositiveButton(R.string.ok) { _, _ ->
dialog.dismiss()
val selectedViewIds = userChooses.mapIndexed { index, it ->
if (it)
viewIds[index] to when (index) {
7 -> 1
else -> 0
}
else
null
}.let {
listOfNotNull(*it.toTypedArray())
}
activity.swipeRefreshLayout.isRefreshing = true
EdziennikTask.syncProfile(
App.profileId,
selectedViewIds
).enqueue(activity)
}
.setNegativeButton(R.string.cancel) { _, _ ->
dialog.dismiss()
}
.show()
}}
}

View file

@ -21,7 +21,9 @@ import pl.szczodrzynski.edziennik.utils.models.Week
class LessonDetailsDialog( class LessonDetailsDialog(
val activity: AppCompatActivity, val activity: AppCompatActivity,
val lesson: LessonFull val lesson: LessonFull,
val onShowListener: ((tag: String) -> Unit)? = null,
val onDismissListener: ((tag: String) -> Unit)? = null
) { ) {
companion object { companion object {
private const val TAG = "LessonDetailsDialog" private const val TAG = "LessonDetailsDialog"
@ -31,6 +33,9 @@ class LessonDetailsDialog(
private lateinit var dialog: AlertDialog private lateinit var dialog: AlertDialog
init { run { init { run {
if (activity.isFinishing)
return@run
onShowListener?.invoke(TAG)
b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) b = DialogLessonDetailsBinding.inflate(activity.layoutInflater)
dialog = MaterialAlertDialogBuilder(activity) dialog = MaterialAlertDialogBuilder(activity)
.setView(b.root) .setView(b.root)
@ -38,8 +43,13 @@ class LessonDetailsDialog(
dialog.dismiss() dialog.dismiss()
} }
.setNeutralButton(R.string.add) { dialog, _ -> .setNeutralButton(R.string.add) { dialog, _ ->
dialog.dismiss() EventManualV2Dialog(
EventManualV2Dialog(activity, lesson.profileId, lesson) activity,
lesson.profileId,
lesson,
onShowListener = onShowListener,
onDismissListener = onDismissListener
)
/*MaterialAlertDialogBuilder(activity) /*MaterialAlertDialogBuilder(activity)
.setItems(R.array.main_menu_add_options) { dialog2, which -> .setItems(R.array.main_menu_add_options) { dialog2, which ->
dialog2.dismiss() dialog2.dismiss()
@ -59,6 +69,9 @@ class LessonDetailsDialog(
.setNegativeButton(R.string.cancel) { dialog2, _ -> dialog2.dismiss() } .setNegativeButton(R.string.cancel) { dialog2, _ -> dialog2.dismiss() }
.show()*/ .show()*/
} }
.setOnDismissListener {
onDismissListener?.invoke(TAG)
}
.show() .show()
update() update()
}} }}
@ -119,28 +132,28 @@ class LessonDetailsDialog(
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) {
b.oldSubjectName = lesson.oldSubjectName b.oldSubjectName = lesson.oldSubjectName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.subjectId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displaySubjectId != null) {
b.subjectName = lesson.subjectName b.subjectName = lesson.subjectName
} }
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) {
b.oldTeacherName = lesson.oldTeacherName b.oldTeacherName = lesson.oldTeacherName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teacherId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeacherId != null) {
b.teacherName = lesson.teacherName b.teacherName = lesson.teacherName
} }
if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) { if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) {
b.oldClassroom = lesson.oldClassroom b.oldClassroom = lesson.oldClassroom
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.classroom != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayClassroom != null) {
b.classroom = lesson.classroom b.classroom = lesson.classroom
} }
if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) { if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) {
b.oldTeamName = lesson.oldTeamName b.oldTeamName = lesson.oldTeamName
} }
if (lesson.type != Lesson.TYPE_CANCELLED && lesson.teamId != null) { if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeamId != null) {
b.teamName = lesson.teamName b.teamName = lesson.teamName
} }
} }

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
*/
package pl.szczodrzynski.edziennik.ui.modules.error
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding
import kotlin.coroutines.CoroutineContext
class ErrorDialog(
val activity: AppCompatActivity,
val exception: Exception
) : CoroutineScope {
companion object {
private const val TAG = "ErrorDialog"
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val app by lazy { activity.application as App }
private lateinit var b: DialogLessonDetailsBinding
private lateinit var dialog: AlertDialog
init { run {
job = Job()
dialog = MaterialAlertDialogBuilder(activity)
.setTitle(R.string.error_occured)
.setMessage(exception.message + "\n" + Log.getStackTraceString(exception))
.setPositiveButton(R.string.ok) { _, _ ->
dialog.dismiss()
}
.show()
}}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-13.
*/
package pl.szczodrzynski.edziennik.ui.modules.error
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.ColorUtils
import com.google.android.material.snackbar.Snackbar
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.api.v2.models.ApiError
import pl.szczodrzynski.navlib.getColorFromAttr
class ErrorSnackbar(val activity: AppCompatActivity) {
companion object {
private const val TAG = "ErrorSnackbar"
}
private var snackbar: Snackbar? = null
private lateinit var coordinator: CoordinatorLayout
private val errors = mutableListOf<ApiError>()
fun setCoordinator(coordinatorLayout: CoordinatorLayout, showAbove: View? = null) {
this.coordinator = coordinatorLayout
snackbar = Snackbar.make(coordinator, R.string.snackbar_error_text, Snackbar.LENGTH_INDEFINITE)
snackbar?.setAction(R.string.more) {
}
val bgColor = ColorUtils.compositeColors(
getColorFromAttr(activity, R.attr.colorOnSurface) and 0xcfffffff.toInt(),
getColorFromAttr(activity, R.attr.colorSurface)
)
snackbar?.setBackgroundTint(bgColor)
showAbove?.let { snackbar?.anchorView = it }
}
fun addError(apiError: ApiError): ErrorSnackbar {
errors += apiError
snackbar?.setText(apiError.getStringReason(activity))
return this
}
fun show() = snackbar?.show()
fun dismiss() = snackbar?.dismiss()
}

View file

@ -12,8 +12,8 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding
import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata import pl.szczodrzynski.edziennik.data.db.modules.metadata.Metadata
import pl.szczodrzynski.edziennik.databinding.FragmentHomeworkBinding
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
@ -84,7 +84,11 @@ class HomeworkFragment : Fragment() {
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners() b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position

View file

@ -15,8 +15,9 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding; import pl.szczodrzynski.edziennik.databinding.ActivityLoginBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
public class LoginActivity extends AppCompatActivity { public class LoginActivity extends AppCompatActivity {
@ -26,7 +27,8 @@ public class LoginActivity extends AppCompatActivity {
public static final int RESULT_OK = 1; public static final int RESULT_OK = 1;
public static NavOptions navOptions; public static NavOptions navOptions;
static AppError error = null; static ApiError error = null;
ErrorSnackbar errorSnackbar = new ErrorSnackbar(this);
static List<LoginProfileObject> profileObjects; static List<LoginProfileObject> profileObjects;
public static boolean firstCompleted = false; // if a profile is already added during *this* login. This means that LoginChooser has to navigateUp onBackPressed. Else, finish the activity. public static boolean firstCompleted = false; // if a profile is already added during *this* login. This means that LoginChooser has to navigateUp onBackPressed. Else, finish the activity.
@ -69,6 +71,8 @@ public class LoginActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTheme(R.style.AppTheme_Light);
firstCompleted = false; firstCompleted = false;
profileObjects = new ArrayList<>(); profileObjects = new ArrayList<>();
error = null; error = null;
@ -83,6 +87,8 @@ public class LoginActivity extends AppCompatActivity {
b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_login, null, false); b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_login, null, false);
setContentView(b.getRoot()); setContentView(b.getRoot());
errorSnackbar.setCoordinator(b.coordinator, null);
app = (App) getApplication(); app = (App) getApplication();
if (!app.appConfig.loginFinished) { if (!app.appConfig.loginFinished) {

View file

@ -1,25 +1,23 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginIuczniowieBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_SCHOOL_NAME; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_SCHOOL_NAME;
@ -31,6 +29,7 @@ public class LoginIuczniowieFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginIuczniowieBinding b; private FragmentLoginIuczniowieBinding b;
private static final String TAG = "LoginIuczniowie"; private static final String TAG = "LoginIuczniowie";
private ErrorSnackbar errorSnackbar;
public LoginIuczniowieFragment() { } public LoginIuczniowieFragment() { }
@ -40,6 +39,7 @@ public class LoginIuczniowieFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -54,31 +54,17 @@ public class LoginIuczniowieFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_SCHOOL_NAME: case CODE_INVALID_SCHOOL_NAME:
b.loginSchoolNameLayout.setError(getString(R.string.login_error_incorrect_school_name)); b.loginSchoolNameLayout.setError(getString(R.string.login_error_incorrect_school_name));
break; break;
case CODE_INVALID_LOGIN: case CODE_INVALID_LOGIN:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View file

@ -1,28 +1,26 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginLibrusBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_LIBRUS_NOT_ACTIVATED; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED;
import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS; import static pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore.LOGIN_TYPE_LIBRUS;
public class LoginLibrusFragment extends Fragment { public class LoginLibrusFragment extends Fragment {
@ -31,6 +29,7 @@ public class LoginLibrusFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginLibrusBinding b; private FragmentLoginLibrusBinding b;
private static final String TAG = "LoginLibrus"; private static final String TAG = "LoginLibrus";
private ErrorSnackbar errorSnackbar;
public LoginLibrusFragment() { } public LoginLibrusFragment() { }
@ -40,6 +39,7 @@ public class LoginLibrusFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -54,32 +54,17 @@ public class LoginLibrusFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_LOGIN: case ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
case CODE_LIBRUS_NOT_ACTIVATED: case ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED:
b.loginEmailLayout.setError(getString(R.string.login_error_account_not_activated)); b.loginEmailLayout.setError(getString(R.string.login_error_account_not_activated));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.duration(10000)
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View file

@ -1,23 +1,23 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginMobidziennikBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_ARCHIVED; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_ARCHIVED;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_INVALID_LOGIN;
@ -31,6 +31,7 @@ public class LoginMobidziennikFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginMobidziennikBinding b; private FragmentLoginMobidziennikBinding b;
private static final String TAG = "LoginMobidziennik"; private static final String TAG = "LoginMobidziennik";
private ErrorSnackbar errorSnackbar;
public LoginMobidziennikFragment() { } public LoginMobidziennikFragment() { }
@ -40,6 +41,7 @@ public class LoginMobidziennikFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -54,9 +56,9 @@ public class LoginMobidziennikFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_LOGIN: case CODE_INVALID_LOGIN:
b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password)); b.loginPasswordLayout.setError(getString(R.string.login_error_incorrect_login_or_password));
break; break;
@ -69,22 +71,8 @@ public class LoginMobidziennikFragment extends Fragment {
case CODE_INVALID_SERVER_ADDRESS: case CODE_INVALID_SERVER_ADDRESS:
b.loginServerAddressLayout.setError(getString(R.string.login_error_incorrect_address)); b.loginServerAddressLayout.setError(getString(R.string.login_error_incorrect_address));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View file

@ -23,11 +23,11 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskErrorEvent; import pl.szczodrzynski.edziennik.api.v2.events.ApiTaskErrorEvent;
import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent; import pl.szczodrzynski.edziennik.api.v2.events.FirstLoginFinishedEvent;
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask; import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask;
import pl.szczodrzynski.edziennik.data.api.AppError; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore; import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginProgressBinding;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_OTHER; import static pl.szczodrzynski.edziennik.api.v2.ErrorsKt.LOGIN_NO_ARGUMENTS;
public class LoginProgressFragment extends Fragment { public class LoginProgressFragment extends Fragment {
@ -62,7 +62,7 @@ public class LoginProgressFragment extends Fragment {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onSyncErrorEvent(ApiTaskErrorEvent event) { public void onSyncErrorEvent(ApiTaskErrorEvent event) {
LoginActivity.error = event.getError().toAppError(); LoginActivity.error = event.getError();
if (getActivity() == null) if (getActivity() == null)
return; return;
nav.navigateUp(); nav.navigateUp();
@ -79,7 +79,7 @@ public class LoginProgressFragment extends Fragment {
LoginActivity.error = null; LoginActivity.error = null;
if (args == null) { if (args == null) {
LoginActivity.error = new AppError(TAG, 72, CODE_OTHER, getString(R.string.login_error_no_arguments)); LoginActivity.error = new ApiError(TAG, LOGIN_NO_ARGUMENTS);
nav.navigateUp(); nav.navigateUp();
return; return;
} }

View file

@ -1,6 +1,9 @@
package pl.szczodrzynski.edziennik.ui.modules.login; package pl.szczodrzynski.edziennik.ui.modules.login;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -8,14 +11,11 @@ import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginSyncErrorBinding;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class LoginSyncErrorFragment extends Fragment { public class LoginSyncErrorFragment extends Fragment {
private App app; private App app;
@ -44,10 +44,10 @@ public class LoginSyncErrorFragment extends Fragment {
assert getContext() != null; assert getContext() != null;
assert getActivity() != null; assert getActivity() != null;
b.errorDetails.setText(LoginActivity.error == null ? "" : LoginActivity.error.asReadableString(getActivity())); b.errorDetails.setText(LoginActivity.error == null ? "" : LoginActivity.error.getStringReason(getActivity()));
b.reportButton.setOnClickListener((v -> { b.reportButton.setOnClickListener((v -> {
app.apiEdziennik.guiReportError(getActivity(), LoginActivity.error, null); // TODO error report activity open here app.apiEdziennik.guiReportError(getActivity(), LoginActivity.error, null);
})); }));
b.nextButton.setOnClickListener((v -> { b.nextButton.setOnClickListener((v -> {

View file

@ -65,7 +65,7 @@ class LoginSyncFragment : Fragment() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onSyncErrorEvent(event: ApiTaskErrorEvent) { fun onSyncErrorEvent(event: ApiTaskErrorEvent) {
LoginActivity.error = event.error.toAppError() LoginActivity.error = event.error
nav.navigate(R.id.loginSyncErrorFragment, null, LoginActivity.navOptions) nav.navigate(R.id.loginSyncErrorFragment, null, LoginActivity.navOptions)
} }

View file

@ -3,27 +3,24 @@ package pl.szczodrzynski.edziennik.ui.modules.login;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import com.danimahardhika.cafebar.CafeBar; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.mikepenz.iconics.IconicsColor; import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -36,9 +33,10 @@ import javax.crypto.ShortBufferException;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.ui.modules.webpush.QrScannerActivity; import pl.szczodrzynski.edziennik.api.v2.models.ApiError;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding; import pl.szczodrzynski.edziennik.databinding.FragmentLoginVulcanBinding;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar;
import pl.szczodrzynski.edziennik.ui.modules.webpush.QrScannerActivity;
import pl.szczodrzynski.edziennik.utils.Utils; import pl.szczodrzynski.edziennik.utils.Utils;
import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_EXPIRED_TOKEN; import static pl.szczodrzynski.edziennik.data.api.AppError.CODE_EXPIRED_TOKEN;
@ -53,6 +51,7 @@ public class LoginVulcanFragment extends Fragment {
private NavController nav; private NavController nav;
private FragmentLoginVulcanBinding b; private FragmentLoginVulcanBinding b;
private static final String TAG = "LoginVulcan"; private static final String TAG = "LoginVulcan";
private ErrorSnackbar errorSnackbar;
public LoginVulcanFragment() { } public LoginVulcanFragment() { }
@ -62,6 +61,7 @@ public class LoginVulcanFragment extends Fragment {
if (getActivity() != null) { if (getActivity() != null) {
app = (App) getActivity().getApplicationContext(); app = (App) getActivity().getApplicationContext();
nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment); nav = Navigation.findNavController(getActivity(), R.id.nav_host_fragment);
errorSnackbar = ((LoginActivity) getActivity()).errorSnackbar;
} }
else { else {
return null; return null;
@ -76,9 +76,9 @@ public class LoginVulcanFragment extends Fragment {
assert getActivity() != null; assert getActivity() != null;
view.postDelayed(() -> { view.postDelayed(() -> {
AppError error = LoginActivity.error; ApiError error = LoginActivity.error;
if (error != null) { if (error != null) {
switch (error.errorCode) { switch (error.getErrorCode()) {
case CODE_INVALID_TOKEN: case CODE_INVALID_TOKEN:
b.loginTokenLayout.setError(getString(R.string.login_error_incorrect_token)); b.loginTokenLayout.setError(getString(R.string.login_error_incorrect_token));
break; break;
@ -89,28 +89,14 @@ public class LoginVulcanFragment extends Fragment {
b.loginSymbolLayout.setError(getString(R.string.login_error_incorrect_symbol)); b.loginSymbolLayout.setError(getString(R.string.login_error_incorrect_symbol));
break; break;
case CODE_INVALID_PIN: case CODE_INVALID_PIN:
if (!"?".equals(error.errorText)) { /*if (!"?".equals(error.errorText)) {
b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin_format, error.errorText)); b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin_format, error.errorText));
break; break;
} }*/
b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin)); b.loginPinLayout.setError(getString(R.string.login_error_incorrect_pin));
break; break;
default:
CafeBar.builder(getActivity())
.to(b.root)
.content(getString(R.string.login_error, error.asReadableString(getActivity())))
.autoDismiss(false)
.positiveText(R.string.ok)
.onPositive(CafeBar::dismiss)
.floating(true)
.swipeToDismiss(true)
.neutralText(R.string.more)
.onNeutral(cafeBar -> app.apiEdziennik.guiShowErrorDialog(getActivity(), error, R.string.error_details))
.negativeText(R.string.report)
.onNegative((cafeBar -> app.apiEdziennik.guiReportError(getActivity(), error, null)))
.show();
break;
} }
errorSnackbar.addError(error).show();
LoginActivity.error = null; LoginActivity.error = null;
} }
}, 100); }, 100);

View file

@ -97,7 +97,7 @@ class MessageFragment : Fragment(), CoroutineScope {
val msg = app.db.messageDao().getById(App.profileId, messageId)?.also { val msg = app.db.messageDao().getById(App.profileId, messageId)?.also {
it.recipients = app.db.messageRecipientDao().getAllByMessageId(it.profileId, it.id) it.recipients = app.db.messageRecipientDao().getAllByMessageId(it.profileId, it.id)
if (it.body != null && !it.seen) { if (it.body != null && !it.seen) {
app.db.metadataDao().setSeen(it.profileId, message, true) app.db.metadataDao().setSeen(it.profileId, it, true)
} }
} }
msg msg

View file

@ -4,7 +4,6 @@ import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -13,7 +12,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
@ -35,12 +33,6 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.data.api.Edziennik;
import pl.szczodrzynski.edziennik.data.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher; import pl.szczodrzynski.edziennik.data.db.modules.teachers.Teacher;
import pl.szczodrzynski.edziennik.databinding.ActivityComposeMessageBinding; import pl.szczodrzynski.edziennik.databinding.ActivityComposeMessageBinding;
import pl.szczodrzynski.edziennik.utils.Colors; import pl.szczodrzynski.edziennik.utils.Colors;
@ -63,7 +55,7 @@ public class MessagesComposeActivity extends AppCompatActivity {
b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_compose_message, null, false); b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_compose_message, null, false);
setContentView(b.getRoot()); setContentView(b.getRoot());
composeInfo = Edziennik.getApi(app, app.profile.getLoginStoreType()).getComposeInfo(app.profile); /*composeInfo = Edziennik.getApi(app, app.profile.getLoginStoreType()).getComposeInfo(app.profile);
Toolbar toolbar = b.toolbar; Toolbar toolbar = b.toolbar;
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
@ -99,7 +91,7 @@ public class MessagesComposeActivity extends AppCompatActivity {
MessagesComposeSuggestionAdapter adapter = new MessagesComposeSuggestionAdapter(this, teachers); MessagesComposeSuggestionAdapter adapter = new MessagesComposeSuggestionAdapter(this, teachers);
//ArrayAdapter<Teacher> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, teachers); //ArrayAdapter<Teacher> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, teachers);
b.nachoTextView.setAdapter(adapter); b.nachoTextView.setAdapter(adapter);
}); });*/
/*app.db.teacherDao().getAllTeachers(App.profileId).observe(this, teachers -> { /*app.db.teacherDao().getAllTeachers(App.profileId).observe(this, teachers -> {
});*/ });*/

View file

@ -1,17 +1,12 @@
package pl.szczodrzynski.edziennik.ui.modules.messages; package pl.szczodrzynski.edziennik.ui.modules.messages;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils; import android.media.ThumbnailUtils;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.Handler;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -19,12 +14,18 @@ import android.widget.FrameLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.mikepenz.iconics.IconicsColor; import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
import com.mikepenz.iconics.typeface.IIcon;
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial;
import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImage;
@ -39,36 +40,16 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import im.wangchao.mhttp.Response;
import im.wangchao.mhttp.callback.FileCallbackHandler;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.MainActivity; import pl.szczodrzynski.edziennik.MainActivity;
import pl.szczodrzynski.edziennik.data.api.Edziennik; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.api.AppError;
import pl.szczodrzynski.edziennik.data.api.interfaces.SyncCallback;
import pl.szczodrzynski.edziennik.databinding.MessagesDetailsBinding;
import pl.szczodrzynski.edziennik.data.db.modules.login.LoginStore;
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull; import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageFull;
import pl.szczodrzynski.edziennik.data.db.modules.messages.MessageRecipientFull; import pl.szczodrzynski.edziennik.databinding.MessagesDetailsBinding;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile; import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDialog;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
import pl.szczodrzynski.edziennik.utils.models.Date;
import pl.szczodrzynski.edziennik.utils.models.Time;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Themes;
import pl.szczodrzynski.edziennik.utils.Utils; import pl.szczodrzynski.edziennik.utils.Utils;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.view.Gravity.CENTER_VERTICAL;
import static android.view.Gravity.END;
import static pl.szczodrzynski.edziennik.utils.Utils.getResizedBitmap; import static pl.szczodrzynski.edziennik.utils.Utils.getResizedBitmap;
import static pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile; import static pl.szczodrzynski.edziennik.utils.Utils.getStringFromFile;
import static pl.szczodrzynski.edziennik.utils.Utils.readableFileSize; import static pl.szczodrzynski.edziennik.utils.Utils.readableFileSize;
@ -108,7 +89,7 @@ public class MessagesDetailsFragment extends Fragment {
b.messageContent.setVisibility(View.GONE); b.messageContent.setVisibility(View.GONE);
if (messageId != -1) { /*if (messageId != -1) {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded()) if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return; return;
@ -286,7 +267,7 @@ public class MessagesDetailsFragment extends Fragment {
}); });
}); });
} }*/
// click to expand subject and sender // click to expand subject and sender
b.messageSubject.setOnClickListener(v -> { b.messageSubject.setOnClickListener(v -> {
@ -340,7 +321,7 @@ public class MessagesDetailsFragment extends Fragment {
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
app.apiEdziennik.guiReportException(activity, 355, e); new ErrorDialog(activity, e);
} }
} }
} }
@ -359,7 +340,7 @@ public class MessagesDetailsFragment extends Fragment {
File storageDir = Utils.getStorageDir(); File storageDir = Utils.getStorageDir();
Edziennik.getApi(app, app.profile.getLoginStoreType()).getAttachment(activity, new SyncCallback() { /*Edziennik.getApi(app, app.profile.getLoginStoreType()).getAttachment(activity, new SyncCallback() {
@Override @Override
public void onLoginFirst(List<Profile> profileList, LoginStore loginStore) { public void onLoginFirst(List<Profile> profileList, LoginStore loginStore) {
@ -437,7 +418,7 @@ public class MessagesDetailsFragment extends Fragment {
} }
}) })
.build() .build()
.enqueue()); .enqueue());*/
} }
@Subscribe(threadMode = ThreadMode.POSTING) @Subscribe(threadMode = ThreadMode.POSTING)
@ -478,7 +459,7 @@ public class MessagesDetailsFragment extends Fragment {
.positiveText(R.string.ok) .positiveText(R.string.ok)
.neutralColor(R.string.report) .neutralColor(R.string.report)
.onNeutral((dialog, which) -> { .onNeutral((dialog, which) -> {
app.apiEdziennik.guiReportException(activity, 433, event.exception); new ErrorDialog(activity, event.exception);
}) })
.show(); .show();
} }
@ -486,7 +467,7 @@ public class MessagesDetailsFragment extends Fragment {
} }
} }
catch (Exception e) { catch (Exception e) {
app.apiEdziennik.guiReportException(activity, 425, e); new ErrorDialog(activity, e);
} }
} }

View file

@ -77,7 +77,11 @@ class MessagesFragment : Fragment() {
b.viewPager.currentItem = pageSelection b.viewPager.currentItem = pageSelection
b.viewPager.clearOnPageChangeListeners() b.viewPager.clearOnPageChangeListeners()
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = position pageSelection = position

View file

@ -1,474 +0,0 @@
package pl.szczodrzynski.edziennik.ui.modules.messages;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.MimeTypeMap;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.StackingBehavior;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.databinding.FragmentMessagesWebBinding;
import pl.szczodrzynski.edziennik.utils.Anim;
import pl.szczodrzynski.edziennik.utils.Themes;
import static android.app.Activity.RESULT_OK;
import static android.content.Context.DOWNLOAD_SERVICE;
import static pl.szczodrzynski.edziennik.utils.Utils.readableFileSize;
public class MessagesWebFragment extends Fragment {
private static final String TAG = "RegisterMessagesWeb";
private App app = null;
private Activity activity = null;
private FragmentMessagesWebBinding b = null;
private WebView webView;
private ProgressBar progressBar;
private TextView error;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = getActivity();
if (getActivity() == null || getContext() == null)
return null;
app = (App) activity.getApplication();
getContext().getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true);
if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false);
// activity, context and profile is valid
b = DataBindingUtil.inflate(inflater, R.layout.fragment_messages_web, container, false);
return b.getRoot();
}
private boolean isStoragePermissionGranted(Activity a) {
if (Build.VERSION.SDK_INT >= 23) {
if (a.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
ActivityCompat.requestPermissions(a, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
private String getFileNameFromDisposition(String header)
{
return header.substring(header.indexOf("\"") + 1, header.lastIndexOf("\""));
}
private File downloadingFile;
private void enqueueFile(String url, String filename, File downloadingFile, String cookieString) {
this.downloadingFile = downloadingFile;
long downloadReference;
DownloadManager downloadManager;
downloadManager = (DownloadManager)app.getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle(filename);
request.setDescription(getString(R.string.downloading));
request.addRequestHeader("Cookie", cookieString);
try {
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
}
catch (IllegalStateException e)
{
e.printStackTrace();
Toast.makeText(app, "Failed to get external storage files directory", Toast.LENGTH_SHORT).show();
}
downloadReference = downloadManager.enqueue(request);
}
private static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
return type;
}
private void openFile(File url) {
Toast.makeText(app, getString(R.string.opening_file, url.getName()), Toast.LENGTH_SHORT).show();
try {
Uri uri = FileProvider.getUriForFile(app, app.getApplicationContext().getPackageName() + ".provider", url);
//Uri uri = Uri.fromFile(url);
Intent intent = new Intent(Intent.ACTION_VIEW);
if (url.toString().contains(".doc") || url.toString().contains(".docx")) {
// Word document
intent.setDataAndType(uri, "application/msword");
} else if (url.toString().contains(".pdf")) {
// PDF file
intent.setDataAndType(uri, "application/pdf");
} else if (url.toString().contains(".ppt") || url.toString().contains(".pptx")) {
// Powerpoint file
intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
} else if (url.toString().contains(".xls") || url.toString().contains(".xlsx")) {
// Excel file
intent.setDataAndType(uri, "application/vnd.ms-excel");
} else if (url.toString().contains(".zip") || url.toString().contains(".rar")) {
// WAV audio file
intent.setDataAndType(uri, "application/x-wav");
} else if (url.toString().contains(".rtf")) {
// RTF file
intent.setDataAndType(uri, "application/rtf");
} else if (url.toString().contains(".wav") || url.toString().contains(".mp3")) {
// WAV audio file
intent.setDataAndType(uri, "audio/x-wav");
} else if (url.toString().contains(".gif")) {
// GIF file
intent.setDataAndType(uri, "image/gif");
} else if (url.toString().contains(".jpg") || url.toString().contains(".jpeg") || url.toString().contains(".png")) {
// JPG file
intent.setDataAndType(uri, "image/jpeg");
} else if (url.toString().contains(".txt")) {
// Text file
intent.setDataAndType(uri, "text/plain");
} else if (url.toString().contains(".3gp") || url.toString().contains(".mpg") ||
url.toString().contains(".mpeg") || url.toString().contains(".mpe") || url.toString().contains(".mp4") || url.toString().contains(".avi")) {
// Video files
intent.setDataAndType(uri, "video/*");
} else {
intent.setDataAndType(uri, "*/*");
}
intent.setDataAndType(uri, getMimeType(uri.toString()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
app.startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(app, R.string.opening_file_no_app, Toast.LENGTH_SHORT).show();
}
}
private void downloadFile(Activity a, String url, String filename, long contentLength, String cookieString) {
if (!isStoragePermissionGranted(a))
return;
new MaterialDialog.Builder(a)
.title(R.string.downloading_file)
.content(getString((R.string.download_file_question), filename, readableFileSize(contentLength)))
.positiveText(R.string.yes)
.negativeText(R.string.no)
.onPositive((dialog, which) -> {
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File existingFile = new File(downloadsDir, filename);
if (existingFile.exists()) {
new MaterialDialog.Builder(a)
.title(R.string.downloading_file)
.content(getString(R.string.downloading_file_exists_choose, filename))
.stackingBehavior(StackingBehavior.ADAPTIVE)
.positiveText(R.string.downloading_file_exists_overwrite)
.negativeText(R.string.downloading_file_exists_open)
.neutralText(R.string.downloading_file_exists_create_new)
.onPositive(((dialog1, which1) -> {
if (!existingFile.delete())
{
new MaterialDialog.Builder(a)
.title(R.string.downloading_file)
.content(R.string.downloading_file_cannot_remove)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.onPositive(((dialog2, which2) -> enqueueFile(url, filename, existingFile, cookieString)))
.show();
return;
}
enqueueFile(url, filename, existingFile, cookieString);
}))
.onNegative(((dialog1, which1) -> openFile(existingFile)))
.onNeutral((dialog1, which1) -> enqueueFile(url, filename, existingFile, cookieString))
.show();
return;
}
enqueueFile(url, filename, existingFile, cookieString);
})
.show();
}
private String photoPath;
private ValueCallback<Uri> mUM;
private ValueCallback<Uri[]> fileCallback;
private final static int REQUEST_FILE_CHOOSER = 1;
private boolean justLoaded = false;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(Build.VERSION.SDK_INT >= 21){
Uri[] results = null;
//Check if response is positive
if(resultCode == RESULT_OK){
if(requestCode == REQUEST_FILE_CHOOSER){
if(null == fileCallback){
return;
}
if(data == null){
//Capture Photo if no image available
if (photoPath != null) {
results = new Uri[]{Uri.parse(photoPath)};
}
}else{
String dataString = data.getDataString();
if(dataString != null){
results = new Uri[]{Uri.parse(dataString)};
}
}
}
}
fileCallback.onReceiveValue(results);
fileCallback = null;
} else {
if (requestCode == REQUEST_FILE_CHOOSER) {
if(null == mUM) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
mUM.onReceiveValue(result);
mUM = null;
}
}
}
private File createImageFile() throws IOException{
@SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "img_"+timeStamp+"_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
return File.createTempFile(imageFileName,".jpg",storageDir);
}
@SuppressLint("SetJavaScriptEnabled")
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
webView = b.messagesWebView;
progressBar = b.messagesWebProgressBar;
error = b.messagesWebError;
justLoaded = true;
new Handler().postDelayed(() -> activity.runOnUiThread(() -> {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
BroadcastReceiver onComplete = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (downloadingFile != null) {
openFile(downloadingFile);
downloadingFile = null;
}
}
};
activity.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
app.apiEdziennik.initMessagesWebView(webView, app, /*app.profile.messagesWebFullVersion*/false, false);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
String filename = getFileNameFromDisposition(contentDisposition);
try {
URL urlObj = new URL(url);
Log.d(TAG, "Host "+urlObj.getProtocol()+"://"+urlObj.getHost());
Log.d(TAG, "Cookies "+CookieManager.getInstance().getCookie(urlObj.getProtocol()+"://"+urlObj.getHost()));
downloadFile(getActivity(), url, filename, contentLength, CookieManager.getInstance().getCookie(urlObj.getProtocol()+"://"+urlObj.getHost()));
} catch (MalformedURLException e) {
e.printStackTrace();
}
Anim.fadeOut(progressBar, 400, null);
});
webView.setWebViewClient(new WebViewClient() {
boolean loadingFinished = true;
boolean redirect = false;
@Override
public boolean shouldOverrideUrlLoading(WebView view, String request) {
if (!loadingFinished) {
redirect = true;
}
loadingFinished = false;
webView.loadUrl(request);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
MessagesWebFragment.this.error.setVisibility(View.GONE);
loadingFinished = false;
//SHOW LOADING IF IT ISNT ALREADY VISIBLE
if (progressBar.getVisibility() != View.VISIBLE)
Anim.fadeIn(progressBar, 400, null);
}
@Override
public void onPageFinished(WebView view, String url) {
if (!redirect) {
loadingFinished = true;
}
if (loadingFinished && !redirect) {
//HIDE LOADING IT HAS FINISHED
//String cookies = CookieManager.getInstance().getCookie(url);
//Log.d(TAG, "All the cookies in a string:" + cookies);
Anim.fadeOut(progressBar, 400, null);
/*if (app.profile.messagesWebFullVersion && justLoaded && app.profile.loginType == Register.LOGIN_TYPE_MOBIDZIENNIK) {
if (!webView.getUrl().contains("wiadomosci")) {
// redirect to messages view
webView.loadUrl("https://" + app.getLoginData("serverName", "") + ".mobidziennik.pl/mobile/wiadomosci");
}
justLoaded = false;
}*/
} else {
redirect = false;
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (app == null || app.profile == null || activity == null || b == null || !isAdded())
return;
MessagesWebFragment.this.error.setVisibility(View.VISIBLE);
MessagesWebFragment.this.error.setText(getString(R.string.error_occured_format, error.toString()));
super.onReceivedError(view, request, error);
}
});
if(Build.VERSION.SDK_INT >= 21) {
webView.getSettings().setMixedContentMode(0);
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else if(Build.VERSION.SDK_INT >= 19) {
webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webView.setWebChromeClient(new WebChromeClient(){
//For Android 5.0+
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams){
if(fileCallback != null){
fileCallback.onReceiveValue(null);
}
fileCallback = filePathCallback;
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(takePictureIntent.resolveActivity(app.getPackageManager()) != null){
File photoFile = null;
try{
photoFile = createImageFile();
takePictureIntent.putExtra("PhotoPath", photoPath);
}catch(IOException ex){
Log.e(TAG, "Image file creation failed", ex);
}
if(photoFile != null){
photoPath = "file:" + photoFile.getAbsolutePath();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
}else{
takePictureIntent = null;
}
}
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*");
Intent[] intentArray;
if(takePictureIntent != null){
intentArray = new Intent[]{takePictureIntent};
}else{
intentArray = new Intent[0];
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, R.string.choose_file);
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, REQUEST_FILE_CHOOSER);
return true;
}
});
}), 200);
}
/*public void loadVersion(boolean fullVersion) {
if (app.profile.messagesWebFullVersion != fullVersion) {
app.profile.messagesWebFullVersion = fullVersion;
app.profile.savePending = true;
justLoaded = true;
app.apiEdziennik.initMessagesWebView(webView, app, fullVersion, true);
}
}*/
public void performReload() {
webView.reload();
}
public boolean processBackKey()
{
if (webView.canGoBack())
{
webView.goBack();
return true;
}
return false;
}
}

View file

@ -50,6 +50,7 @@ import pl.szczodrzynski.edziennik.network.ServerRequest;
import pl.szczodrzynski.edziennik.receivers.BootReceiver; import pl.szczodrzynski.edziennik.receivers.BootReceiver;
import pl.szczodrzynski.edziennik.sync.SyncWorker; import pl.szczodrzynski.edziennik.sync.SyncWorker;
import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog; import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog;
import pl.szczodrzynski.edziennik.ui.dialogs.settings.ProfileRemoveDialog;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment; import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushConfigActivity; import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushConfigActivity;
import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Themes;
@ -263,7 +264,7 @@ public class SettingsNewFragment extends MaterialAboutFragment {
.color(IconicsColor.colorInt(iconColor)) .color(IconicsColor.colorInt(iconColor))
) )
.setOnClickAction(() -> { .setOnClickAction(() -> {
app.apiEdziennik.guiRemoveProfile(activity, app.profile.getId(), app.profile.getName()); new ProfileRemoveDialog(activity, app.profile.getId(), app.profile.getName());
}) })
); );
@ -1291,8 +1292,6 @@ public class SettingsNewFragment extends MaterialAboutFragment {
if (app.profile == null) if (app.profile == null)
return new MaterialAboutList.Builder().build(); return new MaterialAboutList.Builder().build();
//configurableEndpoints = Edziennik.getApi(app, app.profile.loginStoreType).getConfigurableEndpoints(app.profile);
MaterialAboutList materialAboutList = new MaterialAboutList(); MaterialAboutList materialAboutList = new MaterialAboutList();
materialAboutList.addCard(getCardWithItems(null, getProfileCard(false))); materialAboutList.addCard(getCardWithItems(null, getProfileCard(false)));
materialAboutList.addCard(getCardWithItems(getString(R.string.settings_theme_title_text), getThemeCard(false))); materialAboutList.addCard(getCardWithItems(getString(R.string.settings_theme_title_text), getThemeCard(false)));

View file

@ -51,6 +51,7 @@ import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull; import pl.szczodrzynski.edziennik.data.db.modules.lessons.LessonFull;
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableBinding; import pl.szczodrzynski.edziennik.databinding.FragmentTimetableBinding;
import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog; import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog;
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorDialog;
import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment; import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment;
import pl.szczodrzynski.edziennik.utils.SpannableHtmlTagHandler; import pl.szczodrzynski.edziennik.utils.SpannableHtmlTagHandler;
import pl.szczodrzynski.edziennik.utils.Themes; import pl.szczodrzynski.edziennik.utils.Themes;
@ -422,7 +423,7 @@ public class TimetableFragment extends Fragment {
linearLayout = (LinearLayout) getLayoutInflater().inflate(R.layout.row_timetable_block_item, null); linearLayout = (LinearLayout) getLayoutInflater().inflate(R.layout.row_timetable_block_item, null);
} }
catch (Exception e) { catch (Exception e) {
app.apiEdziennik.guiReportException(activity, 385, e); new ErrorDialog(activity, e);
return; return;
} }

View file

@ -11,6 +11,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.R import pl.szczodrzynski.edziennik.R
@ -18,16 +19,25 @@ import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Themes
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import kotlin.coroutines.CoroutineContext
class TimetableFragment : Fragment() { class TimetableFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableFragment" private const val TAG = "TimetableFragment"
const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE" const val ACTION_SCROLL_TO_DATE = "pl.szczodrzynski.edziennik.timetable.SCROLL_TO_DATE"
const val DEFAULT_START_HOUR = 6
const val DEFAULT_END_HOUR = 19
var pageSelection: Date? = null
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2Binding private lateinit var b: FragmentTimetableV2Binding
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private var fabShown = false private var fabShown = false
private val items = mutableListOf<Date>() private val items = mutableListOf<Date>()
@ -36,11 +46,13 @@ class TimetableFragment : Fragment() {
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
job = Job()
context!!.theme.applyStyle(Themes.appTheme, true) context!!.theme.applyStyle(Themes.appTheme, true)
if (app.profile == null) if (app.profile == null)
return inflater.inflate(R.layout.fragment_loading, container, false) return inflater.inflate(R.layout.fragment_loading, container, false)
// activity, context and profile is valid // activity, context and profile is valid
b = FragmentTimetableV2Binding.inflate(inflater) b = FragmentTimetableV2Binding.inflate(inflater)
b.refreshLayout.setParent(activity.swipeRefreshLayout)
return b.root return b.root
} }
@ -62,48 +74,64 @@ class TimetableFragment : Fragment() {
activity.unregisterReceiver(broadcastReceiver) activity.unregisterReceiver(broadcastReceiver)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { launch {
// TODO check if app, activity, b can be null // TODO check if app, activity, b can be null
if (app.profile == null || !isAdded) if (app.profile == null || !isAdded)
return return@launch
if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) { if (app.profile.loginStoreType == LOGIN_TYPE_LIBRUS && app.profile.getLoginData("timetableNotPublic", false)) {
b.timetableLayout.visibility = View.GONE b.timetableLayout.visibility = View.GONE
b.timetableNotPublicLayout.visibility = View.VISIBLE b.timetableNotPublicLayout.visibility = View.VISIBLE
return return@launch
} }
b.timetableLayout.visibility = View.VISIBLE b.timetableLayout.visibility = View.VISIBLE
b.timetableNotPublicLayout.visibility = View.GONE b.timetableNotPublicLayout.visibility = View.GONE
items.clear()
val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val today = Date.getToday().value val today = Date.getToday().value
val yearStart = app.profile.dateSemester1Start?.clone() ?: return var startHour = DEFAULT_START_HOUR
val yearEnd = app.profile.dateYearEnd ?: return var endHour = DEFAULT_END_HOUR
while (yearStart.value <= yearEnd.value) { val deferred = async(Dispatchers.Default) {
items += yearStart.clone() items.clear()
var maxDays = monthDayCount[yearStart.month-1]
if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
yearStart.day++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
}
val pagerAdapter = TimetablePagerAdapter(fragmentManager ?: return, items) val monthDayCount = listOf(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
val yearStart = app.profile.dateSemester1Start?.clone() ?: return@async
val yearEnd = app.profile.dateYearEnd ?: return@async
while (yearStart.value <= yearEnd.value) {
items += yearStart.clone()
var maxDays = monthDayCount[yearStart.month-1]
if (yearStart.month == 2 && yearStart.isLeap)
maxDays++
yearStart.day++
if (yearStart.day > maxDays) {
yearStart.day = 1
yearStart.month++
}
if (yearStart.month > 12) {
yearStart.month = 1
yearStart.year++
}
}
val lessonRanges = app.db.lessonRangeDao().getAllNow(App.profileId)
startHour = lessonRanges.map { it.startTime.hour }.min() ?: DEFAULT_START_HOUR
endHour = lessonRanges.map { it.endTime.hour }.max()?.plus(1) ?: DEFAULT_END_HOUR
}
deferred.await()
val pagerAdapter = TimetablePagerAdapter(
fragmentManager ?: return@launch,
items,
startHour,
endHour
)
b.viewPager.offscreenPageLimit = 2 b.viewPager.offscreenPageLimit = 2
b.viewPager.adapter = pagerAdapter b.viewPager.adapter = pagerAdapter
b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { b.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) { override fun onPageScrollStateChanged(state: Int) {
if (b.refreshLayout != null) {
b.refreshLayout.isEnabled = state == ViewPager.SCROLL_STATE_IDLE
}
} }
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
@ -111,6 +139,7 @@ class TimetableFragment : Fragment() {
} }
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
pageSelection = items[position]
activity.navView.bottomBar.fabEnable = items[position].value != today activity.navView.bottomBar.fabEnable = items[position].value != today
if (activity.navView.bottomBar.fabEnable && !fabShown) { if (activity.navView.bottomBar.fabEnable && !fabShown) {
activity.gainAttentionFAB() activity.gainAttentionFAB()
@ -123,10 +152,10 @@ class TimetableFragment : Fragment() {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, false)
//activity.navView.bottomBar.fabEnable = true //activity.navView.bottomBar.fabEnable = true
activity.navView.bottomBar.fabExtendedText = getString(pl.szczodrzynski.edziennik.R.string.timetable_today) activity.navView.bottomBar.fabExtendedText = getString(R.string.timetable_today)
activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today activity.navView.bottomBar.fabIcon = CommunityMaterial.Icon.cmd_calendar_today
activity.navView.setFabOnClickListener(View.OnClickListener { activity.navView.setFabOnClickListener(View.OnClickListener {
b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true) b.tabLayout.setCurrentItem(items.indexOfFirst { it.value == today }, true)
}) })
} }}
} }

View file

@ -1,12 +1,19 @@
package pl.szczodrzynski.edziennik.ui.modules.timetable.v2 package pl.szczodrzynski.edziennik.ui.modules.timetable.v2
import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragment
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.edziennik.utils.models.Week import pl.szczodrzynski.edziennik.utils.models.Week
class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: List<Date>) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { class TimetablePagerAdapter(
fragmentManager: FragmentManager,
private val items: List<Date>,
private val startHour: Int,
private val endHour: Int
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
companion object { companion object {
private const val TAG = "TimetablePagerAdapter" private const val TAG = "TimetablePagerAdapter"
} }
@ -16,7 +23,13 @@ class TimetablePagerAdapter(val fragmentManager: FragmentManager, val items: Lis
private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) } private val weekEnd by lazy { weekStart.clone().stepForward(0, 0, 6) }
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
return pl.szczodrzynski.edziennik.ui.modules.timetable.v2.day.TimetableDayFragment(items[position]) return TimetableDayFragment().apply {
arguments = Bundle().apply {
putInt("date", items[position].value)
putInt("startHour", startHour)
putInt("endHour", endHour)
}
}
/*return TimetableDayFragment().apply { /*return TimetableDayFragment().apply {
arguments = Bundle().also { arguments = Bundle().also {
it.putLong("date", items[position].value.toLong()) it.putLong("date", items[position].value.toLong())

View file

@ -7,41 +7,97 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.linkedin.android.tachyon.DayView import com.linkedin.android.tachyon.DayView
import com.linkedin.android.tachyon.DayViewConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import pl.szczodrzynski.edziennik.* import pl.szczodrzynski.edziennik.*
import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE
import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS import pl.szczodrzynski.edziennik.api.v2.LOGIN_TYPE_LIBRUS
import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask import pl.szczodrzynski.edziennik.api.v2.events.task.EdziennikTask
import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2DayBinding
import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding
import pl.szczodrzynski.edziennik.databinding.TimetableNoTimetableBinding
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_END_HOUR
import pl.szczodrzynski.edziennik.ui.modules.timetable.v2.TimetableFragment.Companion.DEFAULT_START_HOUR
import pl.szczodrzynski.edziennik.utils.ListenerScrollView
import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Date
import pl.szczodrzynski.navlib.getColorFromAttr import pl.szczodrzynski.navlib.getColorFromAttr
import java.util.* import java.util.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.min import kotlin.math.min
class TimetableDayFragment(val date: Date) : Fragment() { class TimetableDayFragment : Fragment(), CoroutineScope {
companion object { companion object {
private const val TAG = "TimetableDayFragment" private const val TAG = "TimetableDayFragment"
} }
private lateinit var app: App private lateinit var app: App
private lateinit var activity: MainActivity private lateinit var activity: MainActivity
private lateinit var b: FragmentTimetableV2DayBinding private lateinit var inflater: AsyncLayoutInflater
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private lateinit var date: Date
private var startHour = DEFAULT_START_HOUR
private var endHour = DEFAULT_END_HOUR
private var firstEventMinute = 24*60
// find SwipeRefreshLayout in the hierarchy
private val refreshLayout by lazy { view?.findParentById(R.id.refreshLayout) }
// the day ScrollView
private val dayScrollDelegate = lazy {
val dayScroll = ListenerScrollView(context!!)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
dayScroll.setOnRefreshLayoutEnabledListener { enabled ->
refreshLayout?.isEnabled = enabled
}
dayScroll
}
private val dayScroll by dayScrollDelegate
// the lesson DayView
private val dayView by lazy {
val dayView = DayView(context!!, DayViewConfig(
startHour = startHour,
endHour = endHour,
dividerHeight = 1.dp,
halfHourHeight = 60.dp,
hourDividerColor = R.attr.hourDividerColor.resolveAttr(context),
halfHourDividerColor = R.attr.halfHourDividerColor.resolveAttr(context),
hourLabelWidth = 40.dp,
hourLabelMarginEnd = 10.dp,
eventMargin = 2.dp
), true)
dayView.setPadding(10.dp)
dayScroll.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
dayScroll.addView(dayView)
dayView
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as MainActivity?) ?: return null activity = (getActivity() as MainActivity?) ?: return null
if (context == null) if (context == null)
return null return null
app = activity.application as App app = activity.application as App
b = FragmentTimetableV2DayBinding.inflate(inflater) job = Job()
Log.d(TAG, "onCreateView, date=$date") this.inflater = AsyncLayoutInflater(context!!)
return b.root date = arguments?.getInt("date")?.let { Date.fromValue(it) } ?: Date.getToday()
startHour = arguments?.getInt("startHour") ?: DEFAULT_START_HOUR
endHour = arguments?.getInt("endHour") ?: DEFAULT_END_HOUR
return FrameLayout(activity).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -51,45 +107,42 @@ class TimetableDayFragment(val date: Date) : Fragment() {
Log.d(TAG, "onViewCreated, date=$date") Log.d(TAG, "onViewCreated, date=$date")
// Inflate a label view for each hour the day view will display // observe lesson database
val hourLabelViews = ArrayList<View>()
for (i in b.day.startHour..b.day.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, b.day, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
b.day.setHourLabelViews(hourLabelViews)
app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons -> app.db.timetableDao().getForDate(App.profileId, date).observe(this, Observer<List<LessonFull>> { lessons ->
buildLessonViews(lessons) processLessonList(lessons)
}) })
} }
private fun buildLessonViews(lessons: List<LessonFull>) { private fun processLessonList(lessons: List<LessonFull>) {
// no lessons - timetable not downloaded yet
if (lessons.isEmpty()) { if (lessons.isEmpty()) {
b.dayScroll.visibility = View.GONE inflater.inflate(R.layout.timetable_no_timetable, view as FrameLayout) { view, _, parent ->
b.noTimetableLayout.visibility = View.VISIBLE parent?.removeAllViews()
b.noLessonsLayout.visibility = View.GONE parent?.addView(view)
val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d val b = TimetableNoTimetableBinding.bind(view)
b.noTimetableSync.onClick { val weekStart = date.clone().stepForward(0, 0, -date.weekDay).stringY_m_d
it.isEnabled = false b.noTimetableSync.onClick {
EdziennikTask.syncProfile( it.isEnabled = false
profileId = App.profileId, EdziennikTask.syncProfile(
viewIds = listOf( profileId = App.profileId,
DRAWER_ITEM_TIMETABLE to 0 viewIds = listOf(
), DRAWER_ITEM_TIMETABLE to 0
arguments = JsonObject( ),
"weekStart" to weekStart arguments = JsonObject(
) "weekStart" to weekStart
).enqueue(activity) )
).enqueue(activity)
}
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
} }
b.noTimetableWeek.setText(R.string.timetable_no_timetable_week, weekStart)
return return
} }
// one lesson indicating a day without lessons
if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) { if (lessons.size == 1 && lessons[0].type == Lesson.TYPE_NO_LESSONS) {
b.dayScroll.visibility = View.GONE inflater.inflate(R.layout.timetable_no_lessons, view as FrameLayout) { view, _, parent ->
b.noTimetableLayout.visibility = View.GONE parent?.removeAllViews()
b.noLessonsLayout.visibility = View.VISIBLE parent?.addView(view)
}
return return
} }
@ -100,23 +153,37 @@ class TimetableDayFragment(val date: Date) : Fragment() {
return return
} }
b.dayScroll.visibility = View.VISIBLE // clear the root view and add the ScrollView
b.noTimetableLayout.visibility = View.GONE (view as FrameLayout).removeAllViews()
b.noLessonsLayout.visibility = View.GONE (view as FrameLayout).addView(dayScroll)
var firstEventMinute = 24*60 // Inflate a label view for each hour the day view will display
val hourLabelViews = ArrayList<View>()
for (i in dayView.startHour..dayView.endHour) {
val hourLabelView = layoutInflater.inflate(R.layout.timetable_hour_label, dayView, false) as TextView
hourLabelView.text = "$i:00"
hourLabelViews.add(hourLabelView)
}
dayView.setHourLabelViews(hourLabelViews)
buildLessonViews(lessons)
}
private fun buildLessonViews(lessons: List<LessonFull>) {
if (!isAdded)
return
val eventViews = mutableListOf<View>() val eventViews = mutableListOf<View>()
val eventTimeRanges = mutableListOf<DayView.EventTimeRange>() val eventTimeRanges = mutableListOf<DayView.EventTimeRange>()
// Reclaim all of the existing event views so we can reuse them if needed, this process // Reclaim all of the existing event views so we can reuse them if needed, this process
// can be useful if your day view is hosted in a recycler view for example // can be useful if your day view is hosted in a recycler view for example
val recycled = b.day.removeEventViews() val recycled = dayView.removeEventViews()
var remaining = recycled?.size ?: 0 var remaining = recycled?.size ?: 0
val arrowRight = "" val arrowRight = ""
val bullet = "" val bullet = ""
val colorSecondary = getColorFromAttr(activity, android.R.attr.textColorSecondary) val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity)
for (lesson in lessons) { for (lesson in lessons) {
val startTime = lesson.displayStartTime ?: continue val startTime = lesson.displayStartTime ?: continue
@ -126,7 +193,7 @@ class TimetableDayFragment(val date: Date) : Fragment() {
// Try to recycle an existing event view if there are enough left, otherwise inflate // Try to recycle an existing event view if there are enough left, otherwise inflate
// a new one // a new one
val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, b.day, false)) val eventView = (if (remaining > 0) recycled?.get(--remaining) else layoutInflater.inflate(R.layout.timetable_lesson, dayView, false))
?: continue ?: continue
val lb = TimetableLessonBinding.bind(eventView) val lb = TimetableLessonBinding.bind(eventView)
eventViews += eventView eventViews += eventView
@ -273,9 +340,16 @@ class TimetableDayFragment(val date: Date) : Fragment() {
eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute)) eventTimeRanges.add(DayView.EventTimeRange(startMinute, endMinute))
} }
val minuteHeight = (b.day.getHourTop(1) - b.day.getHourTop(0)).toFloat() / 60f dayView.setEventViews(eventViews, eventTimeRanges)
val firstEventTop = (firstEventMinute - b.day.startHour * 60) * minuteHeight val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
b.day.setEventViews(eventViews, eventTimeRanges) dayScroll.scrollTo(0, firstEventTop.toInt())
b.dayScroll.scrollTo(0, firstEventTop.toInt()) }
override fun onResume() {
super.onResume()
if (dayScrollDelegate.isInitialized()) {
val firstEventTop = (firstEventMinute - dayView.startHour * 60) * dayView.minuteHeight
dayScroll.scrollTo(0, firstEventTop.toInt())
}
} }
} }

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-16.
*/
package pl.szczodrzynski.edziennik.utils
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
class ListenerScrollView(
context: Context,
attrs: AttributeSet? = null
) : ScrollView(context, attrs) {
private var onScrollChangedListener: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)? = null
private var onRefreshLayoutEnabledListener: ((enabled: Boolean) -> Unit)? = null
private var refreshLayoutEnabled = true
init {
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
refreshLayoutEnabled = scrollY < 10
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
false
}
}
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
onScrollChangedListener?.invoke(this, l, t, oldl, oldt)
if (t > 10 && refreshLayoutEnabled) {
refreshLayoutEnabled = false
onRefreshLayoutEnabledListener?.invoke(refreshLayoutEnabled)
}
}
fun setOnScrollChangedListener(l: ((v: ListenerScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int) -> Unit)?) {
onScrollChangedListener = l
}
fun setOnRefreshLayoutEnabledListener(l: ((enabled: Boolean) -> Unit)?) {
onRefreshLayoutEnabledListener = l
}
}

View file

@ -75,21 +75,22 @@ class TextInputDropDown : TextInputEditText {
} }
} }
fun select(item: Item) { fun select(item: Item): Item? {
selected = item selected = item
updateText() updateText()
return item
} }
fun select(id: Long?) { fun select(id: Long?): Item? {
items.singleOrNull { it.id == id }?.let { select(it) } return items.singleOrNull { it.id == id }?.let { select(it) }
} }
fun select(tag: Any?) { fun select(tag: Any?): Item? {
items.singleOrNull { it.tag == tag }?.let { select(it) } return items.singleOrNull { it.tag == tag }?.let { select(it) }
} }
fun select(index: Int) { fun select(index: Int): Item? {
items.getOrNull(index)?.let { select(it) } return items.getOrNull(index)?.let { select(it) }
} }
fun deselect(): TextInputDropDown { fun deselect(): TextInputDropDown {

View file

@ -95,6 +95,7 @@ public class AppConfig {
public String updateUrl = ""; public String updateUrl = "";
public String updateFilename = ""; public String updateFilename = "";
public boolean updateMandatory = false; public boolean updateMandatory = false;
public boolean updateDirect = false;
public boolean webPushEnabled = false; public boolean webPushEnabled = false;

View file

@ -12,13 +12,11 @@ public class Date implements Comparable<Date> {
public int month = 0; public int month = 0;
public int day = 0; public int day = 0;
public Date() public Date() {
{
this(2000, 0, 0); this(2000, 0, 0);
} }
public Date(int year, int month, int day) public Date(int year, int month, int day) {
{
this.year = year; this.year = year;
this.month = month; this.month = month;
this.day = day; this.day = day;
@ -43,14 +41,8 @@ public class Date implements Comparable<Date> {
return new Date(this.year, this.month, this.day); return new Date(this.year, this.month, this.day);
} }
public long combineWith(Time time) { public static Date fromYmd(String dateTime) {
if (time == null) { return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(4, 6)), Integer.parseInt(dateTime.substring(6, 8)));
return getInMillis();
}
Calendar c = Calendar.getInstance();
c.set(this.year, this.month-1, this.day, time.hour, time.minute, time.second);
c.set(Calendar.MILLISECOND, 0);
return c.getTimeInMillis();
} }
public static Date fromMillis(long millis) { public static Date fromMillis(long millis) {
@ -67,9 +59,8 @@ public class Date implements Comparable<Date> {
return Calendar.getInstance().getTimeInMillis(); return Calendar.getInstance().getTimeInMillis();
} }
public int getWeekDay() public static Date fromY_m_d(String dateTime) {
{ return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(5, 7)), Integer.parseInt(dateTime.substring(8, 10)));
return Week.getWeekDayFromDate(this);
} }
public long getInMillis() { public long getInMillis() {
@ -83,142 +74,167 @@ public class Date implements Comparable<Date> {
return getInMillis() / 1000; return getInMillis() / 1000;
} }
public Date stepForward(int years, int months, int days) public static Date fromd_m_Y(String dateTime) {
{ return new Date(Integer.parseInt(dateTime.substring(6, 10)), Integer.parseInt(dateTime.substring(3, 5)), Integer.parseInt(dateTime.substring(0, 2)));
}
public static long fromIso(String dateTime) {
return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19))));
}
public static long fromIsoHm(String dateTime) {
return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), 0));
}
public static Date fromValue(int value) {
int year = value / 10000;
int month = (value - year * 10000) / 100;
int day = (value - year * 10000 - month * 100);
return new Date(year, month, day);
}
public static Date getToday() {
Calendar cal = Calendar.getInstance();
return new Date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH));
}
public static int diffDays(Date d1, Date d2) {
return (int) ((d1.getInMillis() - d2.getInMillis()) / (24 * 60 * 60 * 1000));
}
public static boolean isToday(Date date) {
return equals(date, getToday());
}
public static boolean isToday(String dateStr) {
return equals(dateStr, getToday());
}
public static boolean equals(Date first, Date second) {
return (first.getValue() == second.getValue());
}
public static boolean equals(String first, Date second) {
return equals(new Date().parseFromYmd(first), second);
}
public long combineWith(Time time) {
if (time == null) {
return getInMillis();
}
Calendar c = Calendar.getInstance(); Calendar c = Calendar.getInstance();
c.set(this.year, this.month - 1, this.day, time.hour, time.minute, time.second);
c.set(Calendar.MILLISECOND, 0);
return c.getTimeInMillis();
}
public int getWeekDay() {
return Week.getWeekDayFromDate(this);
}
public Date stepForward(int years, int months, int days) {
this.day += days;
if (day > daysInMonth()) {
day -= daysInMonth();
month++;
}
this.month += months;
if (month > 12) {
month -= 12;
year++;
}
this.year += years;
/*Calendar c = Calendar.getInstance();
int newMonth = month + months; int newMonth = month + months;
if (newMonth > 12) { if (newMonth > 12) {
newMonth = 1; newMonth = 1;
years++; years++;
} }
c.set(year+years, newMonth - 1, day); c.set(year + years, newMonth - 1, day);
c.setTimeInMillis(c.getTimeInMillis() + days*24*60*60*1000); c.setTimeInMillis(c.getTimeInMillis() + days * 24 * 60 * 60 * 1000);
this.year = c.get(Calendar.YEAR); this.year = c.get(Calendar.YEAR);
this.month = c.get(Calendar.MONTH) + 1; this.month = c.get(Calendar.MONTH) + 1;
this.day = c.get(Calendar.DAY_OF_MONTH); this.day = c.get(Calendar.DAY_OF_MONTH);*/
return this; return this;
} }
public Date parseFromYmd(String dateTime) public Date parseFromYmd(String dateTime) {
{
this.year = Integer.parseInt(dateTime.substring(0, 4)); this.year = Integer.parseInt(dateTime.substring(0, 4));
this.month = Integer.parseInt(dateTime.substring(4, 6)); this.month = Integer.parseInt(dateTime.substring(4, 6));
this.day = Integer.parseInt(dateTime.substring(6, 8)); this.day = Integer.parseInt(dateTime.substring(6, 8));
return this; return this;
} }
public static Date fromYmd(String dateTime) public int getValue() {
{
return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(4, 6)), Integer.parseInt(dateTime.substring(6, 8)));
}
public static Date fromY_m_d(String dateTime)
{
return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(5, 7)), Integer.parseInt(dateTime.substring(8, 10)));
}
public static long fromIso(String dateTime)
{
return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), Integer.parseInt(dateTime.substring(17, 19))));
}
public static long fromIsoHm(String dateTime)
{
return Date.fromY_m_d(dateTime).combineWith(new Time(Integer.parseInt(dateTime.substring(11, 13)), Integer.parseInt(dateTime.substring(14, 16)), 0));
}
public int getValue()
{
return year * 10000 + month * 100 + day; return year * 10000 + month * 100 + day;
} }
public static Date fromValue(int value) { public String getStringValue() {
int year = value / 10000;
int month = (value-year*10000) / 100;
int day = (value-year*10000-month*100);
return new Date(year, month, day);
}
public String getStringValue()
{
return Integer.toString(getValue()); return Integer.toString(getValue());
} }
public String getStringYmd()
{
return year +(month < 10 ? "0" : "")+ month +(day < 10 ? "0" : "")+ day;
}
/**
* @return 2019-06-02
*/
public String getStringY_m_d()
{
return year +(month < 10 ? "-0" : "-")+ month +(day < 10 ? "-0" : "-")+ day;
}
public String getStringDm()
{
return day +"."+(month < 10 ? "0" : "")+ month;
}
public String getStringDmy()
{
return day +"."+(month < 10 ? "0" : "")+ month +"."+ year;
}
public String getFormattedString()
{
java.util.Date date = new java.util.Date();
date.setTime(getInMillis());
DateFormat format = DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault());
if (year == Date.getToday().year) {
return format.format(date).replace(", "+year, "").replace(" "+year, "");
}
else {
return format.format(date);
}
}
public String getFormattedStringShort()
{
java.util.Date date = new java.util.Date();
date.setTime(getInMillis());
DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault());
if (year == Date.getToday().year) {
return format.format(date).replace(", "+year, "").replace(" "+year, "");
}
else {
return format.format(date);
}
}
public boolean isLeap() { public boolean isLeap() {
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0); return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
} }
public int daysInMonth() {
public static Date getToday() switch (month) {
{ case 1:
Calendar cal = Calendar.getInstance(); case 3:
return new Date(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH)); case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 2: return isLeap() ? 29 : 28;
case 4:
case 6:
case 9:
case 11:
return 30;
}
return 31;
} }
public static int diffDays(Date d1, Date d2) { public String getStringYmd() {
return (int)((d1.getInMillis() - d2.getInMillis()) / (24*60*60*1000)); return year + (month < 10 ? "0" : "") + month + (day < 10 ? "0" : "") + day;
} }
public static boolean isToday(Date date) /**
{ * @return 2019-06-02
return equals(date, getToday()); */
public String getStringY_m_d() {
return year + (month < 10 ? "-0" : "-") + month + (day < 10 ? "-0" : "-") + day;
} }
public static boolean isToday(String dateStr)
{ public String getStringDm() {
return equals(dateStr, getToday()); return day + "." + (month < 10 ? "0" : "") + month;
} }
public static boolean equals(Date first, Date second)
{ public String getStringDmy() {
return (first.getValue() == second.getValue()); return day + "." + (month < 10 ? "0" : "") + month + "." + year;
} }
public static boolean equals(String first, Date second)
{ public String getFormattedString() {
return equals(new Date().parseFromYmd(first), second); java.util.Date date = new java.util.Date();
date.setTime(getInMillis());
DateFormat format = DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault());
if (year == Date.getToday().year) {
return format.format(date).replace(", " + year, "").replace(" " + year, "");
} else {
return format.format(date);
}
}
public String getFormattedStringShort() {
java.util.Date date = new java.util.Date();
date.setTime(getInMillis());
DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault());
if (year == Date.getToday().year) {
return format.format(date).replace(", " + year, "").replace(" " + year, "");
} else {
return format.format(date);
}
} }
@Override @Override

View file

@ -9,6 +9,7 @@ public class ItemWidgetTimetableModel {
public String separatorProfileName = null; public String separatorProfileName = null;
public int profileId; public int profileId;
public long lessonId;
public Date lessonDate; public Date lessonDate;
public Time startTime; public Time startTime;
public Time endTime; public Time endTime;

View file

@ -1,10 +1,11 @@
package pl.szczodrzynski.edziennik.utils.models; package pl.szczodrzynski.edziennik.utils.models;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.Calendar; import java.util.Calendar;
public class Time { public class Time implements Comparable<Time> {
public int hour = 0; public int hour = 0;
public int minute = 0; public int minute = 0;
public int second = 0; public int second = 0;
@ -175,6 +176,11 @@ public class Time {
return (currentTime.getValue() >= startTime.getValue() && currentTime.getValue() <= endTime.getValue()); return (currentTime.getValue() >= startTime.getValue() && currentTime.getValue() <= endTime.getValue());
} }
@Override
public int compareTo(@NonNull Time o) {
return this.getValue() - o.getValue();
}
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
return obj instanceof Time && this.getValue() == ((Time) obj).getValue(); return obj instanceof Time && this.getValue() == ((Time) obj).getValue();

View file

@ -21,8 +21,8 @@ import java.util.List;
import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.App;
import pl.szczodrzynski.edziennik.R; import pl.szczodrzynski.edziennik.R;
import pl.szczodrzynski.edziennik.WidgetTimetable; import pl.szczodrzynski.edziennik.WidgetTimetable;
import pl.szczodrzynski.edziennik.databinding.DialogWidgetConfigBinding;
import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile; import pl.szczodrzynski.edziennik.data.db.modules.profiles.Profile;
import pl.szczodrzynski.edziennik.databinding.DialogWidgetConfigBinding;
import pl.szczodrzynski.edziennik.widgets.luckynumber.WidgetLuckyNumber; import pl.szczodrzynski.edziennik.widgets.luckynumber.WidgetLuckyNumber;
import pl.szczodrzynski.edziennik.widgets.notifications.WidgetNotifications; import pl.szczodrzynski.edziennik.widgets.notifications.WidgetNotifications;
@ -78,7 +78,7 @@ public class WidgetConfigActivity extends Activity {
AsyncTask.execute(() -> { AsyncTask.execute(() -> {
profileList = app.db.profileDao().getAllNow(); profileList = app.db.profileDao().getAllNow();
filterOutArchived(profileList); profileList = filterOutArchived(profileList);
if (widgetType == WIDGET_NOTIFICATIONS) if (widgetType == WIDGET_NOTIFICATIONS)
this.runOnUiThread(this::configure); this.runOnUiThread(this::configure);

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) Kuba Szczodrzyński 2019-11-14.
*/
package pl.szczodrzynski.edziennik.widgets.timetable
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.MainActivity
import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog
import pl.szczodrzynski.edziennik.utils.Themes
import kotlin.coroutines.CoroutineContext
class LessonDialogActivity : AppCompatActivity(), CoroutineScope {
companion object {
private const val TAG = "LessonDialogActivity"
}
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
private val shownDialogs = hashSetOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setBackgroundDrawable(ColorDrawable(0))
job = Job()
setTheme(Themes.appThemeNoDisplay)
val app = application as App
launch {
val deferred = async(Dispatchers.Default) {
val extras = intent?.extras
val profileId = extras?.getInt("profileId") ?: return@async null
if (extras.getBoolean("separatorItem", false)) {
val i = Intent(app, MainActivity::class.java)
.putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)
.putExtra("profileId", profileId)
.addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT)
app.startActivity(i)
finish()
return@async null
}
val lessonId = extras.getLong("lessonId")
app.db.timetableDao().getByIdNow(profileId, lessonId)
}
val lesson = deferred.await()
lesson?.let {
LessonDetailsDialog(
this@LessonDialogActivity,
lesson,
onShowListener = { tag ->
shownDialogs.add(tag)
},
onDismissListener = { tag ->
shownDialogs.remove(tag)
if (shownDialogs.isEmpty())
finish()
}
)
}
}
}
}

View file

@ -12,13 +12,14 @@ import android.graphics.Paint;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.DrawableRes;
import android.text.Html; import android.text.Html;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import android.widget.RemoteViewsService; import android.widget.RemoteViewsService;
import androidx.annotation.DrawableRes;
import com.mikepenz.iconics.IconicsColor; import com.mikepenz.iconics.IconicsColor;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.IconicsSize; import com.mikepenz.iconics.IconicsSize;
@ -62,7 +63,7 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
public void onDataSetChanged() { public void onDataSetChanged() {
// executed EVERY TIME // executed EVERY TIME
Log.d(TAG, "onDataSetChanged for appWidgetId: "+appWidgetId); Log.d(TAG, "onDataSetChanged for appWidgetId: "+appWidgetId);
lessons = WidgetTimetable.timetables == null ? null : WidgetTimetable.timetables.get(appWidgetId); lessons = WidgetTimetable.Companion.getTimetables() == null ? null : WidgetTimetable.Companion.getTimetables().get(appWidgetId);
} }
@Override @Override
@ -163,12 +164,17 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra("profileId", lesson.profileId); intent.putExtra("profileId", lesson.profileId);
intent.putExtra("date", lesson.lessonDate.getStringValue()); intent.putExtra("lessonId", lesson.lessonId);
intent.putExtra("startTime", lesson.startTime.getStringValue()); if (lesson.lessonDate != null)
intent.putExtra("endTime", lesson.endTime.getStringValue()); intent.putExtra("date", lesson.lessonDate.getStringValue());
if (lesson.startTime != null)
intent.putExtra("startTime", lesson.startTime.getStringValue());
if (lesson.endTime != null)
intent.putExtra("endTime", lesson.endTime.getStringValue());
views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent); views.setOnClickFillInIntent(R.id.widgetTimetableRoot, intent);
views.setTextViewText(R.id.widgetTimetableTime, lesson.startTime.getStringHM() + " - " + lesson.endTime.getStringHM()); if (lesson.startTime != null && lesson.endTime != null)
views.setTextViewText(R.id.widgetTimetableTime, lesson.startTime.getStringHM() + " - " + lesson.endTime.getStringHM());
views.setViewVisibility(R.id.widgetTimetableEvent1, View.GONE); views.setViewVisibility(R.id.widgetTimetableEvent1, View.GONE);
views.setViewVisibility(R.id.widgetTimetableEvent2, View.GONE); views.setViewVisibility(R.id.widgetTimetableEvent2, View.GONE);
@ -239,30 +245,18 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
} }
} }
} }
//views.setViewVisibility(R.id.widgetTimetableEvent1, View.VISIBLE);
//views.setBitmap(R.id.widgetTimetableEvent1, "setImageBitmap", getColoredBitmap(context, R.drawable.event_color_circle, 0xff4caf50, eventIndicatorSize, eventIndicatorSize)); if (lesson.subjectName == null) {
lesson.subjectName = context.getString(R.string.timetable_no_subject_name);
}
if (lesson.classroomName == null) {
lesson.classroomName = context.getString(R.string.timetable_no_classroom);
}
views.setViewVisibility(R.id.widgetTimetableOldSubjectName, View.GONE); views.setViewVisibility(R.id.widgetTimetableOldSubjectName, View.GONE);
if (lesson.lessonChange) { if (lesson.lessonChange) {
if (lesson.newSubjectName == null) { views.setTextViewText(R.id.widgetTimetableSubjectName, Html.fromHtml("<i>"+lesson.subjectName+"</i>"));
views.setTextViewText(R.id.widgetTimetableSubjectName, Html.fromHtml("<i>"+lesson.subjectName+"</i>")); views.setTextViewText(R.id.widgetTimetableClassroomName, Html.fromHtml("<i>"+lesson.classroomName+"</i>"));
}
else {
views.setViewVisibility(R.id.widgetTimetableOldSubjectName, View.VISIBLE);
views.setTextViewText(R.id.widgetTimetableOldSubjectName, Html.fromHtml("<del>"+lesson.subjectName+"</del>"));
views.setTextViewText(R.id.widgetTimetableSubjectName, lesson.newSubjectName);
}
if (lesson.newClassroomName == null) {
if (lesson.classroomName == null || lesson.classroomName.equals("")) {
lesson.classroomName = context.getString(R.string.timetable_no_classroom);
}
views.setTextViewText(R.id.widgetTimetableClassroomName, lesson.classroomName);
}
else {
views.setTextViewText(R.id.widgetTimetableClassroomName, Html.fromHtml("<i>"+lesson.newClassroomName+"</i>"));
}
} }
else if (lesson.lessonCancelled) { else if (lesson.lessonCancelled) {
views.setTextViewText(R.id.widgetTimetableSubjectName, Html.fromHtml("<del>"+lesson.subjectName+"</del>")); views.setTextViewText(R.id.widgetTimetableSubjectName, Html.fromHtml("<del>"+lesson.subjectName+"</del>"));
@ -272,7 +266,6 @@ public class WidgetTimetableListProvider implements RemoteViewsService.RemoteVie
views.setTextViewText(R.id.widgetTimetableSubjectName, lesson.subjectName); views.setTextViewText(R.id.widgetTimetableSubjectName, lesson.subjectName);
views.setTextViewText(R.id.widgetTimetableClassroomName, lesson.classroomName); views.setTextViewText(R.id.widgetTimetableClassroomName, lesson.classroomName);
} }
} }
else if (!triedToReload) { else if (!triedToReload) {
// try to reload the widget // try to reload the widget

View file

@ -8,16 +8,17 @@
android:orientation="vertical" android:orientation="vertical"
android:visibility="visible"> android:visibility="visible">
<LinearLayout <Space
android:layout_width="0dp"
android:layout_height="32dp" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:visibility="visible"> android:visibility="visible">
<Space
android:layout_width="0dp"
android:layout_height="32dp" />
<fragment <fragment
android:id="@+id/nav_host_fragment" android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
@ -30,7 +31,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_login" /> app:navGraph="@navigation/nav_login" />
</LinearLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<!--<io.codetail.widget.RevealFrameLayout <!--<io.codetail.widget.RevealFrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -4,11 +4,17 @@
--> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:padding="24dp"> android:padding="24dp">
@ -46,41 +52,63 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_team"> android:hint="@string/dialog_event_manual_team">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown <pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teamDropdown" android:id="@+id/teamDropdown"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:enabled="false" android:enabled="false"
tools:text="2b3T"/> tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.switchmaterial.SwitchMaterial
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" android:id="@+id/shareSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_event_manual_share_enabled" />
<TextView
android:id="@+id/shareDetails"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dialog_event_manual_share_first_notice"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject"> android:baselineAligned="false"
<pl.szczodrzynski.edziennik.utils.TextInputDropDown android:orientation="horizontal">
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:enabled="false" android:layout_weight="1"
tools:text="2b3T"/> android:hint="@string/dialog_event_manual_type">
</com.google.android.material.textfield.TextInputLayout>
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/typeDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:id="@+id/typeColor"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginTop="2dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:background="@drawable/bg_circle"
android:foreground="?selectableItemBackgroundBorderless" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
@ -98,5 +126,64 @@
tools:text="2b3T" /> tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.mikepenz.iconics.view.IconicsCheckableTextView
android:id="@+id/showMore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:text="@string/dialog_event_manual_more_options"
android:background="?selectableItemBackground"
app:iiv_end_icon="cmd-chevron-down"
app:iiv_end_color="?android:textColorSecondary"
app:iiv_end_size="16dp"
app:iiv_end_checked_icon="cmd-chevron-up"
app:iiv_end_checked_color="?android:textColorSecondary"
app:iiv_end_checked_size="16dp"/>
<LinearLayout
android:id="@+id/moreLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_subject">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/subjectDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/dialog_event_manual_teacher">
<pl.szczodrzynski.edziennik.utils.TextInputDropDown
android:id="@+id/teacherDropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
tools:text="2b3T" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView>
</layout> </layout>

View file

@ -21,217 +21,207 @@
<variable name="oldTeamName" type="String" /> <variable name="oldTeamName" type="String" />
<variable name="teamName" type="String" /> <variable name="teamName" type="String" />
</data> </data>
<LinearLayout <ScrollView
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:padding="24dp">
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:padding="24dp">
android:baselineAligned="false">
<LinearLayout <LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{oldSubjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:textColor="?android:textColorTertiary"
android:visibility="@{oldSubjectName == null ? View.GONE : View.VISIBLE}"
app:strikeThrough="@{true}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{subjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Title"
android:visibility="@{subjectName == null ? View.GONE : View.VISIBLE}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:id="@+id/lessonDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="czwartek, 14 listopada 2019"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_number"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:text="@{lesson.displayLessonNumber.toString()}"
android:textSize="36sp"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"
tools:text="4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{lesson.displayStartTime.stringHM + ` - ` + lesson.displayEndTime.stringHM}"
tools:text="14:55 - 15:40" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/shiftedLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/shiftedText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="italic"
tools:text="Lekcja przeniesiona na czwartek, 17 października" />
<Button
android:id="@+id/shiftedGoTo"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przejdź" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_teacher"
android:visibility="@{teacherName != null || oldTeacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeacherName}"
android:textIsSelectable="true"
android:visibility="@{oldTeacherName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teacherName}"
android:textIsSelectable="true"
android:visibility="@{teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_classroom"
android:visibility="@{classroom != null || oldClassroom != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldClassroom}"
android:textIsSelectable="true"
android:visibility="@{oldClassroom != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{classroom}"
android:textIsSelectable="true"
android:visibility="@{classroom != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_team"
android:visibility="@{teamName != null || oldTeamName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeamName}"
android:textIsSelectable="true"
android:visibility="@{oldTeamName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teamName}"
android:textIsSelectable="true"
android:visibility="@{teamName != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
android:text="@string/dialog_lesson_details_id" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@{Long.toString(lesson.id)}"
android:textIsSelectable="true"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
tools:text="12345" />
<!--<androidx.core.widget.NestedScrollView
android:id="@+id/gradeHistoryNest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{historyVisible ? View.VISIBLE : View.GONE}">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gradeHistoryList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:listitem="@layout/row_grades_list_item" /> android:orientation="horizontal"
android:baselineAligned="false">
</androidx.core.widget.NestedScrollView>--> <LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1">
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{oldSubjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Medium"
android:textColor="?android:textColorTertiary"
android:visibility="@{oldSubjectName == null ? View.GONE : View.VISIBLE}"
app:strikeThrough="@{true}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{subjectName}"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Title"
android:visibility="@{subjectName == null ? View.GONE : View.VISIBLE}"
tools:text="pracownia urządzeń techniki komputerowej" />
<TextView
android:id="@+id/lessonDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="czwartek, 14 listopada 2019"
tools:visibility="visible" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_number"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:text="@{lesson.displayLessonNumber.toString()}"
android:textSize="36sp"
android:visibility="@{lesson.displayLessonNumber == null ? View.GONE : View.VISIBLE}"
tools:text="4" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{lesson.displayStartTime.stringHM + ` - ` + lesson.displayEndTime.stringHM}"
tools:text="14:55 - 15:40" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/shiftedLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/shiftedText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="16sp"
android:textStyle="italic"
tools:text="Lekcja przeniesiona na czwartek, 17 października" />
<Button
android:id="@+id/shiftedGoTo"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Przejdź" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_teacher"
android:visibility="@{teacherName != null || oldTeacherName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeacherName}"
android:textIsSelectable="true"
android:visibility="@{oldTeacherName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teacherName}"
android:textIsSelectable="true"
android:visibility="@{teacherName != null ? View.VISIBLE : View.GONE}"
tools:text="Janósz Kowalski" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_classroom"
android:visibility="@{classroom != null || oldClassroom != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldClassroom}"
android:textIsSelectable="true"
android:visibility="@{oldClassroom != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{classroom}"
android:textIsSelectable="true"
android:visibility="@{classroom != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@string/dialog_lesson_details_team"
android:visibility="@{teamName != null || oldTeamName != null ? View.VISIBLE : View.GONE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/NavView.TextView.Helper"
android:text="@{oldTeamName}"
android:textIsSelectable="true"
android:visibility="@{oldTeamName != null ? View.VISIBLE : View.GONE}"
app:strikeThrough="@{true}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{teamName}"
android:textIsSelectable="true"
android:visibility="@{teamName != null ? View.VISIBLE : View.GONE}"
tools:text="013 informatyczna" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="@style/NavView.TextView.Helper"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
android:text="@string/dialog_lesson_details_id" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:text="@{Long.toString(lesson.id)}"
android:textIsSelectable="true"
android:visibility="@{App.devMode ? View.VISIBLE : View.GONE}"
tools:text="12345" />
</LinearLayout>
</ScrollView>
</layout> </layout>

View file

@ -2,89 +2,96 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
<pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator
android:id="@+id/refreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout <FrameLayout
android:id="@+id/timetableLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
tools:visibility="gone">
<com.google.android.material.appbar.AppBarLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:id="@+id/timetableLayout"
android:layout_height="wrap_content"
android:background="?colorSurface"
style="@style/Widget.MaterialComponents.AppBarLayout.Surface">
<com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/colorSurface_6dp"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab"
app:rtl_tabIndicatorColor="?colorPrimary"
app:rtl_tabMinWidth="90dp"
app:rtl_tabMaxWidth="300dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabPaddingBottom="12dp"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> tools:visibility="gone">
</androidx.coordinatorlayout.widget.CoordinatorLayout> <com.google.android.material.appbar.AppBarLayout
style="@style/Widget.MaterialComponents.AppBarLayout.Surface"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface">
<LinearLayout <com.nshmura.recyclertablayout.RecyclerTabLayout
android:id="@+id/timetableNotPublicLayout" android:id="@+id/tabLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="48dp"
android:orientation="vertical" android:background="@color/colorSurface_6dp"
android:visibility="gone" app:rtl_tabIndicatorColor="?colorPrimary"
android:gravity="center" app:rtl_tabMaxWidth="300dp"
tools:visibility="visible"> app:rtl_tabMinWidth="90dp"
app:rtl_tabPaddingBottom="12dp"
app:rtl_tabPaddingEnd="16dp"
app:rtl_tabPaddingStart="16dp"
app:rtl_tabPaddingTop="12dp"
app:rtl_tabSelectedTextColor="?colorPrimary"
app:rtl_tabTextAppearance="@style/rtl_RecyclerTabLayout.Tab" />
</com.google.android.material.appbar.AppBarLayout>
<ImageView <androidx.viewpager.widget.ViewPager
android:layout_width="wrap_content" android:id="@+id/viewPager"
android:layout_height="wrap_content" android:layout_width="match_parent"
app:srcCompat="@drawable/ic_no_timetable" /> android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<TextView </androidx.coordinatorlayout.widget.CoordinatorLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
<TextView <LinearLayout
android:layout_width="wrap_content" android:id="@+id/timetableNotPublicLayout"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_marginTop="16dp" android:layout_height="match_parent"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center" android:gravity="center"
android:text="@string/timetable_not_public_text" android:orientation="vertical"
android:textSize="16sp" /> android:visibility="gone"
tools:visibility="visible">
<TextView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" app:srcCompat="@drawable/ic_no_timetable" />
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_not_public_title"
android:textSize="24sp" />
</FrameLayout> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_text"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_not_public_hint"
android:textSize="14sp" />
</LinearLayout>
</FrameLayout>
</pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoIndicator>
</layout> </layout>

View file

@ -28,126 +28,5 @@
tools:visibility="gone"/> tools:visibility="gone"/>
</ScrollView> </ScrollView>
<LinearLayout
android:id="@+id/noLessonsLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_timetable" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_lessons_title"
android:textSize="24sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/freeDayLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sunbed" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_free_day_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_text"
android:textSize="14sp" />
<TextView
android:id="@+id/freeDayText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
tools:text="Dzień wolny dla szkoły z puli dyrektorskiej z okazji obchodów Światowego Dnia Wtorku w mieście Poznań i na przedmieśiach"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/freeDayShowTimetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_free_day_show" />
</LinearLayout>
<LinearLayout
android:id="@+id/noTimetableLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone"
android:gravity="center"
tools:visibility="visible">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_sync" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"
android:text="@string/timetable_no_timetable_title"
android:textSize="24sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/timetable_no_timetable_text"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/noTimetableSync"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/timetable_no_timetable_sync" />
<TextView
android:id="@+id/noTimetableWeek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/timetable_no_timetable_week"
android:textSize="12sp"
android:textStyle="italic"/>
</LinearLayout>
</FrameLayout> </FrameLayout>
</layout> </layout>

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