diff --git a/.gitignore b/.gitignore index e7ded9f4..25786692 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ build/ local.properties # Proguard folder generated by Eclipse -proguard/ +#proguard/ # Log Files *.log @@ -47,6 +47,7 @@ captures/ .idea/modules.xml # Comment next line if keeping position of elements in Navigation Editor is relevant for you .idea/navEditor.xml +.idea/copyright/profiles_settings.xml # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. @@ -80,3 +81,9 @@ lint/generated/ lint/outputs/ lint/tmp/ # lint/reports/ + +app/schemas/ + +signatures/ + +app/.cxx \ No newline at end of file diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..9ffcbb8a --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Szkolny.eu \ No newline at end of file diff --git a/.idea/copyright/Kacper.xml b/.idea/copyright/Kacper.xml new file mode 100644 index 00000000..a4d5e6ec --- /dev/null +++ b/.idea/copyright/Kacper.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/kubasz.xml b/.idea/copyright/kubasz.xml new file mode 100644 index 00000000..2a54803f --- /dev/null +++ b/.idea/copyright/kubasz.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..6afdce41 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 284dd50e..24f8c447 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,12 +5,19 @@ + + + + + + + - + diff --git a/MaterialDrawer/build.gradle b/MaterialDrawer/build.gradle deleted file mode 100644 index f1f52f6c..00000000 --- a/MaterialDrawer/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion "28.0.3" - - defaultConfig { - minSdkVersion 14 - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 6102 - versionName "6.1.2" - - resValue "string", "materialdrawer_lib_version", "6.1.2" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' - } - debugMinify { - debuggable = true - minifyEnabled = true - proguardFiles 'proguard-android.txt' - } - } - productFlavors { - } - lintOptions { - abortOnError false - } -} - -dependencies { - implementation "androidx.appcompat:appcompat:${androidXAppCompat}" - implementation "androidx.recyclerview:recyclerview:${androidXRecyclerView}" - implementation "androidx.annotation:annotation:1.0.2" - implementation "com.google.android.material:material:${googleMaterial}" - implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' - - // add the constraintLayout used to create the items and headers - implementation "androidx.constraintlayout:constraintlayout:1.1.3" - - // used to base on some backwards compatible themes - // contains util classes to support various android versions, and clean up code - // comes with the awesome "Holder"-Pattern - // https://github.com/mikepenz/Materialize - api 'com.mikepenz:materialize:1.2.0' - - // used to provide out of the box icon font support. simplifies development, - // and provides scalable icons. the core is very very light - // https://github.com/mikepenz/Android-Iconics - api "com.mikepenz:iconics-core:${iconics}" - - // used to fill the RecyclerView with the DrawerItems - // and provides single and multi selection, expandable items - // https://github.com/mikepenz/FastAdapter - api 'com.mikepenz:fastadapter:3.3.0' - api 'com.mikepenz:fastadapter-extensions-expandable:3.3.0' -} \ No newline at end of file diff --git a/MaterialDrawer/gradle.properties b/MaterialDrawer/gradle.properties deleted file mode 100644 index 86351b9f..00000000 --- a/MaterialDrawer/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -POM_NAME=MaterialDrawer Library -POM_DESCRIPTION=The flexible, easy to use, all in one drawer library for your Android project. -POM_ARTIFACT_ID=materialdrawer -POM_PACKAGING=aar \ No newline at end of file diff --git a/MaterialDrawer/src/main/AndroidManifest.xml b/MaterialDrawer/src/main/AndroidManifest.xml deleted file mode 100644 index 1182871d..00000000 --- a/MaterialDrawer/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/MaterialDrawer/src/main/assets/fonts/materialdrawerfont-font-v5.0.0.ttf b/MaterialDrawer/src/main/assets/fonts/materialdrawerfont-font-v5.0.0.ttf deleted file mode 100644 index f02fa65a..00000000 Binary files a/MaterialDrawer/src/main/assets/fonts/materialdrawerfont-font-v5.0.0.ttf and /dev/null differ diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/AccountHeader.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/AccountHeader.java deleted file mode 100644 index 0899fca3..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/AccountHeader.java +++ /dev/null @@ -1,455 +0,0 @@ -package com.mikepenz.materialdrawer; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import pl.droidsonroids.gif.GifDrawable; -import pl.droidsonroids.gif.GifImageView; - -import android.view.View; -import android.widget.ImageView; - -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Created by mikepenz on 27.02.15. - */ -public class AccountHeader { - protected final static double NAVIGATION_DRAWER_ACCOUNT_ASPECT_RATIO = 9d / 16d; - - protected static final String BUNDLE_SELECTION_HEADER = "bundle_selection_header"; - - - protected final AccountHeaderBuilder mAccountHeaderBuilder; - - protected AccountHeader(AccountHeaderBuilder accountHeaderBuilder) { - this.mAccountHeaderBuilder = accountHeaderBuilder; - } - - /** - * the protected getter for the AccountHeaderBuilder - * - * @return the AccountHeaderBuilder - */ - protected AccountHeaderBuilder getAccountHeaderBuilder() { - return mAccountHeaderBuilder; - } - - /** - * Get the Root view for the Header - * - * @return - */ - public View getView() { - return mAccountHeaderBuilder.mAccountHeaderContainer; - } - - /** - * Set the drawer for the AccountHeader so we can use it for the select - * - * @param drawer - */ - public void setDrawer(Drawer drawer) { - mAccountHeaderBuilder.mDrawer = drawer; - } - - /** - * Returns the header background view so the dev can set everything on it - * - * @return - */ - public GifImageView getHeaderBackgroundView() { - return mAccountHeaderBuilder.mAccountHeaderBackground; - } - - /** - * set the background for the header via the ImageHolder class - * - * @param imageHolder - */ - public void setHeaderBackground(ImageHolder imageHolder) { - ImageHolder.applyTo(imageHolder, mAccountHeaderBuilder.mAccountHeaderBackground); - } - - /** - * Set the background for the Header - * - * @param headerBackground - */ - public void setBackground(Drawable headerBackground) { - mAccountHeaderBuilder.mAccountHeaderBackground.setImageDrawable(headerBackground); - } - - /** - * set the background for the header as file name - * - * @param headerBackgroundPath - * @return - */ - public void setBackground(String headerBackgroundPath) { - try { - if (headerBackgroundPath.endsWith(".gif")) { - setHeaderBackground(new ImageHolder(new GifDrawable(headerBackgroundPath))); - } - else { - setHeaderBackground(new ImageHolder(Uri.parse(headerBackgroundPath))); - } - - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Set the background for the Header as resource - * - * @param headerBackgroundRes - */ - public void setBackgroundRes(@DrawableRes int headerBackgroundRes) { - mAccountHeaderBuilder.mAccountHeaderBackground.setImageResource(headerBackgroundRes); - } - - /** - * Toggle the selection list (show or hide it) - * - * @param ctx - */ - public void toggleSelectionList(Context ctx) { - mAccountHeaderBuilder.toggleSelectionList(ctx); - } - - /** - * returns if the selection list is currently shown - * - * @return - */ - public boolean isSelectionListShown() { - return mAccountHeaderBuilder.mSelectionListShown; - } - - - /** - * set this to false if you want to hide the first line of the selection box in the header (first line would be the name) - * - * @param selectionFirstLineShown - */ - public void setSelectionFirstLineShown(boolean selectionFirstLineShown) { - mAccountHeaderBuilder.mSelectionFirstLineShown = selectionFirstLineShown; - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * set this to false if you want to hide the second line of the selection box in the header (second line would be the e-mail) - * - * @param selectionSecondLineShown - */ - public void setSelectionSecondLineShown(boolean selectionSecondLineShown) { - mAccountHeaderBuilder.mSelectionSecondLineShown = selectionSecondLineShown; - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * set this to define the first line in the selection area if there is no profile - * note this will block any values from profiles! - * - * @param selectionFirstLine - */ - public void setSelectionFirstLine(String selectionFirstLine) { - mAccountHeaderBuilder.mSelectionFirstLine = selectionFirstLine; - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * set this to define the second line in the selection area if there is no profile - * note this will block any values from profiles! - * - * @param selectionSecondLine - */ - public void setSelectionSecondLine(String selectionSecondLine) { - mAccountHeaderBuilder.mSelectionSecondLine = selectionSecondLine; - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * returns the current list of profiles set for this header - * - * @return - */ - public List getProfiles() { - return mAccountHeaderBuilder.mProfiles; - } - - /** - * Set a new list of profiles for the header - * - * @param profiles - */ - public void setProfiles(List profiles) { - mAccountHeaderBuilder.mProfiles = profiles; - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * Selects the given profile and sets it to the new active profile - * - * @param profile - */ - public void setActiveProfile(IProfile profile) { - setActiveProfile(profile, false); - } - - /** - * Selects the given profile and sets it to the new active profile - * - * @param profile - */ - public void setActiveProfile(IProfile profile, boolean fireOnProfileChanged) { - final boolean isCurrentSelectedProfile = mAccountHeaderBuilder.switchProfiles(profile); - //if the selectionList is shown we should also update the current selected profile in the list - if (mAccountHeaderBuilder.mDrawer != null && isSelectionListShown()) { - mAccountHeaderBuilder.mDrawer.setSelection(profile.getIdentifier(), false); - } - //fire the event if enabled and a listener is set - if (fireOnProfileChanged && mAccountHeaderBuilder.mOnAccountHeaderListener != null) { - mAccountHeaderBuilder.mOnAccountHeaderListener.onProfileChanged(null, profile, isCurrentSelectedProfile); - } - } - - /** - * Selects a profile by its identifier - * - * @param identifier - */ - public void setActiveProfile(long identifier) { - setActiveProfile(identifier, false); - } - - /** - * Selects a profile by its identifier - * - * @param identifier - */ - public void setActiveProfile(long identifier, boolean fireOnProfileChanged) { - if (mAccountHeaderBuilder.mProfiles != null) { - for (IProfile profile : mAccountHeaderBuilder.mProfiles) { - if (profile != null) { - if (profile.getIdentifier() == identifier) { - setActiveProfile(profile, fireOnProfileChanged); - return; - } - } - } - } - } - - /** - * get the current active profile - * - * @return - */ - public IProfile getActiveProfile() { - return mAccountHeaderBuilder.mCurrentProfile; - } - - - /** - * Helper method to update a profile using it's identifier - * - * @param newProfile - */ - public void updateProfile(@NonNull IProfile newProfile) { - updateProfileByIdentifier(newProfile); - } - - /** - * Helper method to update a profile using it's identifier - * - * @param newProfile - */ - @Deprecated - public void updateProfileByIdentifier(@NonNull IProfile newProfile) { - int found = getPositionByIdentifier(newProfile.getIdentifier()); - if (found > -1) { - mAccountHeaderBuilder.mProfiles.set(found, newProfile); - mAccountHeaderBuilder.updateHeaderAndList(); - } - } - - - /** - * Add new profiles to the existing list of profiles - * - * @param profiles - */ - public void addProfiles(@NonNull IProfile... profiles) { - if (mAccountHeaderBuilder.mProfiles == null) { - mAccountHeaderBuilder.mProfiles = new ArrayList<>(); - } - - Collections.addAll(mAccountHeaderBuilder.mProfiles, profiles); - - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * Add a new profile at a specific position to the list - * - * @param profile - * @param position - */ - public void addProfile(@NonNull IProfile profile, int position) { - if (mAccountHeaderBuilder.mProfiles == null) { - mAccountHeaderBuilder.mProfiles = new ArrayList<>(); - } - mAccountHeaderBuilder.mProfiles.add(position, profile); - - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * remove a profile from the given position - * - * @param position - */ - public void removeProfile(int position) { - if (mAccountHeaderBuilder.mProfiles != null && mAccountHeaderBuilder.mProfiles.size() > position) { - mAccountHeaderBuilder.mProfiles.remove(position); - } - - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * remove the profile with the given identifier - * - * @param identifier - */ - public void removeProfileByIdentifier(long identifier) { - int found = getPositionByIdentifier(identifier); - if (found > -1) { - mAccountHeaderBuilder.mProfiles.remove(found); - } - - mAccountHeaderBuilder.updateHeaderAndList(); - } - - /** - * try to remove the given profile - * - * @param profile - */ - public void removeProfile(@NonNull IProfile profile) { - removeProfileByIdentifier(profile.getIdentifier()); - } - - /** - * Clear the header - */ - public void clear() { - mAccountHeaderBuilder.mProfiles = null; - - //calculate the profiles to set - mAccountHeaderBuilder.calculateProfiles(); - - //process and build the profiles - mAccountHeaderBuilder.buildProfiles(); - } - - /** - * gets the position of a profile by it's identifier - * - * @param identifier - * @return - */ - private int getPositionByIdentifier(long identifier) { - int found = -1; - if (mAccountHeaderBuilder.mProfiles != null && identifier != -1) { - for (int i = 0; i < mAccountHeaderBuilder.mProfiles.size(); i++) { - if (mAccountHeaderBuilder.mProfiles.get(i) != null) { - if (mAccountHeaderBuilder.mProfiles.get(i).getIdentifier() == identifier) { - found = i; - break; - } - } - } - } - return found; - } - - /** - * add the values to the bundle for saveInstanceState - * - * @param savedInstanceState - * @return - */ - public Bundle saveInstanceState(Bundle savedInstanceState) { - if (savedInstanceState != null) { - savedInstanceState.putInt(BUNDLE_SELECTION_HEADER, mAccountHeaderBuilder.getCurrentSelection()); - } - return savedInstanceState; - } - - - public interface OnAccountHeaderListener { - /** - * the event when the profile changes - * - * @param view - * @param profile - * @return if the event was consumed - */ - boolean onProfileChanged(View view, IProfile profile, boolean current); - } - - public interface OnAccountHeaderItemLongClickListener { - /** - * the event when the profile item is longClicked inside the list - * - * @param view - * @param profile - * @param current - * @return if the event was consumed - */ - boolean onProfileLongClick(View view, IProfile profile, boolean current); - } - - public interface OnAccountHeaderProfileImageListener { - /** - * the event when the profile image is clicked - * - * @param view - * @param profile - * @return if the event was consumed - */ - boolean onProfileImageClick(View view, IProfile profile, boolean current); - - /** - * the event when the profile image is long clicked - * - * @param view - * @param profile - * @return if the event was consumed - */ - boolean onProfileImageLongClick(View view, IProfile profile, boolean current); - } - - public interface OnAccountHeaderSelectionViewClickListener { - /** - * the event when the user clicks the selection list under the profile icons - * - * @param view - * @param profile - * @return if the event was consumed - */ - boolean onClick(View view, IProfile profile); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/AccountHeaderBuilder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/AccountHeaderBuilder.java deleted file mode 100644 index bb447671..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/AccountHeaderBuilder.java +++ /dev/null @@ -1,1500 +0,0 @@ -package com.mikepenz.materialdrawer; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.text.TextUtils; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.DimenHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.icons.MaterialDrawerFont; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; -import com.mikepenz.materialdrawer.util.DrawerImageLoader; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; -import com.mikepenz.materialdrawer.view.BezelImageView; -import com.mikepenz.materialize.util.UIUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Stack; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DimenRes; -import androidx.annotation.DrawableRes; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.constraintlayout.widget.Guideline; -import androidx.core.view.ViewCompat; -import pl.droidsonroids.gif.GifDrawable; -import pl.droidsonroids.gif.GifImageView; - -/** - * Created by mikepenz on 23.05.15. - */ -public class AccountHeaderBuilder { - // global references to views we need later - protected Guideline mStatusBarGuideline; - protected View mAccountHeader; - protected GifImageView mAccountHeaderBackground; - protected BezelImageView mCurrentProfileView; - protected ImageView mAccountSwitcherArrow; - protected TextView mCurrentProfileName; - protected TextView mCurrentProfileEmail; - protected BezelImageView mProfileFirstView; - protected BezelImageView mProfileSecondView; - protected BezelImageView mProfileThirdView; - - // global references to the profiles - protected IProfile mCurrentProfile; - protected IProfile mProfileFirst; - protected IProfile mProfileSecond; - protected IProfile mProfileThird; - - - // global stuff - protected boolean mSelectionListShown = false; - protected int mAccountHeaderTextSectionBackgroundResource = -1; - - // the activity to use - protected Activity mActivity; - - /** - * Pass the activity you use the drawer in ;) - * - * @param activity - * @return - */ - public AccountHeaderBuilder withActivity(@NonNull Activity activity) { - this.mActivity = activity; - return this; - } - - // defines if we use the compactStyle - protected boolean mCompactStyle = false; - - /** - * Defines if we should use the compact style for the header. - * - * @param compactStyle - * @return - */ - public AccountHeaderBuilder withCompactStyle(boolean compactStyle) { - this.mCompactStyle = compactStyle; - return this; - } - - // the typeface used for textViews within the AccountHeader - protected Typeface mTypeface; - - // the typeface used for name textView only. overrides mTypeface - protected Typeface mNameTypeface; - - // the typeface used for email textView only. overrides mTypeface - protected Typeface mEmailTypeface; - - /** - * Define the typeface which will be used for all textViews in the AccountHeader - * - * @param typeface - * @return - */ - public AccountHeaderBuilder withTypeface(@NonNull Typeface typeface) { - this.mTypeface = typeface; - return this; - } - - /** - * Define the typeface which will be used for name textView in the AccountHeader. - * Overrides typeface supplied to {@link AccountHeaderBuilder#withTypeface(android.graphics.Typeface)} - * - * @param typeface - * @return - * @see #withTypeface(android.graphics.Typeface) - */ - public AccountHeaderBuilder withNameTypeface(@NonNull Typeface typeface) { - this.mNameTypeface = typeface; - return this; - } - - /** - * Define the typeface which will be used for email textView in the AccountHeader. - * Overrides typeface supplied to {@link AccountHeaderBuilder#withTypeface(android.graphics.Typeface)} - * - * @param typeface - * @return - * @see #withTypeface(android.graphics.Typeface) - */ - public AccountHeaderBuilder withEmailTypeface(@NonNull Typeface typeface) { - this.mEmailTypeface = typeface; - return this; - } - - // set the account header height - protected DimenHolder mHeight; - - /** - * set the height for the header - * - * @param heightPx - * @return - */ - public AccountHeaderBuilder withHeightPx(int heightPx) { - this.mHeight = DimenHolder.fromPixel(heightPx); - return this; - } - - - /** - * set the height for the header - * - * @param heightDp - * @return - */ - public AccountHeaderBuilder withHeightDp(int heightDp) { - this.mHeight = DimenHolder.fromDp(heightDp); - return this; - } - - /** - * set the height for the header by resource - * - * @param heightRes - * @return - */ - public AccountHeaderBuilder withHeightRes(@DimenRes int heightRes) { - this.mHeight = DimenHolder.fromResource(heightRes); - return this; - } - - //the background color for the slider - protected ColorHolder mTextColor; - - /** - * set the background for the slider as color - * - * @param textColor - * @return - */ - public AccountHeaderBuilder withTextColor(@ColorInt int textColor) { - this.mTextColor = ColorHolder.fromColor(textColor); - return this; - } - - /** - * set the background for the slider as resource - * - * @param textColorRes - * @return - */ - public AccountHeaderBuilder withTextColorRes(@ColorRes int textColorRes) { - this.mTextColor = ColorHolder.fromColorRes(textColorRes); - return this; - } - - //the current selected profile is visible in the list - protected boolean mCurrentHiddenInList = false; - - /** - * hide the current selected profile from the list - * - * @param currentProfileHiddenInList - * @return - */ - public AccountHeaderBuilder withCurrentProfileHiddenInList(boolean currentProfileHiddenInList) { - mCurrentHiddenInList = currentProfileHiddenInList; - return this; - } - - //set to hide the first or second line - protected boolean mSelectionFirstLineShown = true; - protected boolean mSelectionSecondLineShown = true; - - /** - * set this to false if you want to hide the first line of the selection box in the header (first line would be the name) - * - * @param selectionFirstLineShown - * @return - * @deprecated replaced by {@link #withSelectionFirstLineShown} - */ - @Deprecated - public AccountHeaderBuilder withSelectionFistLineShown(boolean selectionFirstLineShown) { - this.mSelectionFirstLineShown = selectionFirstLineShown; - return this; - } - - /** - * set this to false if you want to hide the first line of the selection box in the header (first line would be the name) - * - * @param selectionFirstLineShown - * @return - */ - public AccountHeaderBuilder withSelectionFirstLineShown(boolean selectionFirstLineShown) { - this.mSelectionFirstLineShown = selectionFirstLineShown; - return this; - } - - /** - * set this to false if you want to hide the second line of the selection box in the header (second line would be the e-mail) - * - * @param selectionSecondLineShown - * @return - */ - public AccountHeaderBuilder withSelectionSecondLineShown(boolean selectionSecondLineShown) { - this.mSelectionSecondLineShown = selectionSecondLineShown; - return this; - } - - - //set one of these to define the text in the first or second line with in the account selector - protected String mSelectionFirstLine; - protected String mSelectionSecondLine; - - /** - * set this to define the first line in the selection area if there is no profile - * note this will block any values from profiles! - * - * @param selectionFirstLine - * @return - */ - public AccountHeaderBuilder withSelectionFirstLine(String selectionFirstLine) { - this.mSelectionFirstLine = selectionFirstLine; - return this; - } - - /** - * set this to define the second line in the selection area if there is no profile - * note this will block any values from profiles! - * - * @param selectionSecondLine - * @return - */ - public AccountHeaderBuilder withSelectionSecondLine(String selectionSecondLine) { - this.mSelectionSecondLine = selectionSecondLine; - return this; - } - - // set no divider below the header - protected boolean mPaddingBelowHeader = true; - - /** - * Set this to false if you want no padding below the Header - * - * @param paddingBelowHeader - * @return - */ - public AccountHeaderBuilder withPaddingBelowHeader(boolean paddingBelowHeader) { - this.mPaddingBelowHeader = paddingBelowHeader; - return this; - } - - // set no divider below the header - protected boolean mDividerBelowHeader = true; - - /** - * Set this to false if you want no divider below the Header - * - * @param dividerBelowHeader - * @return - */ - public AccountHeaderBuilder withDividerBelowHeader(boolean dividerBelowHeader) { - this.mDividerBelowHeader = dividerBelowHeader; - return this; - } - - // set non translucent statusBar mode - protected boolean mTranslucentStatusBar = true; - - /** - * Set or disable this if you use a translucent statusbar - * - * @param translucentStatusBar - * @return - */ - public AccountHeaderBuilder withTranslucentStatusBar(boolean translucentStatusBar) { - this.mTranslucentStatusBar = translucentStatusBar; - return this; - } - - //the background for the header - protected ImageHolder mHeaderBackground; - - /** - * set the background for the slider as color - * - * @param headerBackground - * @return - */ - public AccountHeaderBuilder withHeaderBackground(Drawable headerBackground) { - this.mHeaderBackground = new ImageHolder(headerBackground); - return this; - } - - /** - * set the background for the header as resource - * - * @param headerBackgroundRes - * @return - */ - public AccountHeaderBuilder withHeaderBackground(@DrawableRes int headerBackgroundRes) { - this.mHeaderBackground = new ImageHolder(headerBackgroundRes); - return this; - } - - /** - * set the background for the header as file name - * - * @param headerBackgroundPath - * @return - */ - public AccountHeaderBuilder withHeaderBackground(String headerBackgroundPath) { - try { - if (headerBackgroundPath.endsWith(".gif")) { - this.mHeaderBackground = new ImageHolder(new GifDrawable(headerBackgroundPath)); - } - else { - this.mHeaderBackground = new ImageHolder(Uri.parse(headerBackgroundPath)); - } - - } catch (IOException e) { - e.printStackTrace(); - } - return this; - } - - /** - * set the background for the header via the ImageHolder class - * - * @param headerBackground - * @return - */ - public AccountHeaderBuilder withHeaderBackground(ImageHolder headerBackground) { - this.mHeaderBackground = headerBackground; - return this; - } - - //background scale type - protected ImageView.ScaleType mHeaderBackgroundScaleType = null; - - /** - * define the ScaleType for the header background - * - * @param headerBackgroundScaleType - * @return - */ - public AccountHeaderBuilder withHeaderBackgroundScaleType(ImageView.ScaleType headerBackgroundScaleType) { - this.mHeaderBackgroundScaleType = headerBackgroundScaleType; - return this; - } - - //profile images in the header are shown or not - protected boolean mProfileImagesVisible = true; - - /** - * define if the profile images in the header are shown or not - * - * @param profileImagesVisible - * @return - */ - public AccountHeaderBuilder withProfileImagesVisible(boolean profileImagesVisible) { - this.mProfileImagesVisible = profileImagesVisible; - return this; - } - - //only the main profile image is visible - protected boolean mOnlyMainProfileImageVisible = false; - - /** - * define if only the main (current selected) profile image should be visible - * - * @param onlyMainProfileImageVisible - * @return - */ - public AccountHeaderBuilder withOnlyMainProfileImageVisible(boolean onlyMainProfileImageVisible) { - this.mOnlyMainProfileImageVisible = onlyMainProfileImageVisible; - return this; - } - - //show small profile images but hide MainProfileImage - protected boolean mOnlySmallProfileImagesVisible = false; - - /** - * define if only the small profile images should be visible - * - * @param onlySmallProfileImagesVisible - * @return - */ - public AccountHeaderBuilder withOnlySmallProfileImagesVisible(boolean onlySmallProfileImagesVisible) { - this.mOnlySmallProfileImagesVisible = onlySmallProfileImagesVisible; - return this; - } - - //close the drawer after a profile was clicked in the list - protected Boolean mCloseDrawerOnProfileListClick = null; - - /** - * define if the drawer should close if the user clicks on a profile item if the selection list is shown - * - * @param closeDrawerOnProfileListClick - * @return - */ - public AccountHeaderBuilder withCloseDrawerOnProfileListClick(boolean closeDrawerOnProfileListClick) { - this.mCloseDrawerOnProfileListClick = closeDrawerOnProfileListClick; - return this; - } - - //reset the drawer list to the main drawer list after the profile was clicked in the list - protected boolean mResetDrawerOnProfileListClick = true; - - /** - * define if the drawer selection list should be reseted after the user clicks on a profile item if the selection list is shown - * - * @param resetDrawerOnProfileListClick - * @return - */ - public AccountHeaderBuilder withResetDrawerOnProfileListClick(boolean resetDrawerOnProfileListClick) { - this.mResetDrawerOnProfileListClick = resetDrawerOnProfileListClick; - return this; - } - - // set the profile images clickable or not - protected boolean mProfileImagesClickable = true; - - /** - * enable or disable the profile images to be clickable - * - * @param profileImagesClickable - * @return - */ - public AccountHeaderBuilder withProfileImagesClickable(boolean profileImagesClickable) { - this.mProfileImagesClickable = profileImagesClickable; - return this; - } - - // set to use the alternative profile header switching - protected boolean mAlternativeProfileHeaderSwitching = false; - - /** - * enable the alternative profile header switching - * - * @param alternativeProfileHeaderSwitching - * @return - */ - public AccountHeaderBuilder withAlternativeProfileHeaderSwitching(boolean alternativeProfileHeaderSwitching) { - this.mAlternativeProfileHeaderSwitching = alternativeProfileHeaderSwitching; - return this; - } - - // enable 3 small header previews - protected boolean mThreeSmallProfileImages = false; - - /** - * enable the extended profile icon view with 3 small header images instead of two - * - * @param threeSmallProfileImages - * @return - */ - public AccountHeaderBuilder withThreeSmallProfileImages(boolean threeSmallProfileImages) { - this.mThreeSmallProfileImages = threeSmallProfileImages; - return this; - } - - //the delay which is waited before the drawer is closed - protected int mOnProfileClickDrawerCloseDelay = 100; - - /** - * Define the delay for the drawer close operation after a click. - * This is a small trick to improve the speed (and remove lag) if you open a new activity after a DrawerItem - * was selected. - * NOTE: Disable this by passing -1 - * - * @param onProfileClickDrawerCloseDelay the delay in MS (-1 to disable) - * @return - */ - public AccountHeaderBuilder withOnProfileClickDrawerCloseDelay(int onProfileClickDrawerCloseDelay) { - this.mOnProfileClickDrawerCloseDelay = onProfileClickDrawerCloseDelay; - return this; - } - - // the onAccountHeaderProfileImageListener to set - protected AccountHeader.OnAccountHeaderProfileImageListener mOnAccountHeaderProfileImageListener; - - /** - * set click / longClick listener for the header images - * - * @param onAccountHeaderProfileImageListener - * @return - */ - public AccountHeaderBuilder withOnAccountHeaderProfileImageListener(AccountHeader.OnAccountHeaderProfileImageListener onAccountHeaderProfileImageListener) { - this.mOnAccountHeaderProfileImageListener = onAccountHeaderProfileImageListener; - return this; - } - - // the onAccountHeaderSelectionListener to set - protected AccountHeader.OnAccountHeaderSelectionViewClickListener mOnAccountHeaderSelectionViewClickListener; - - /** - * set a onSelection listener for the selection box - * - * @param onAccountHeaderSelectionViewClickListener - * @return - */ - public AccountHeaderBuilder withOnAccountHeaderSelectionViewClickListener(AccountHeader.OnAccountHeaderSelectionViewClickListener onAccountHeaderSelectionViewClickListener) { - this.mOnAccountHeaderSelectionViewClickListener = onAccountHeaderSelectionViewClickListener; - return this; - } - - //set the selection list enabled if there is only a single profile - protected boolean mSelectionListEnabledForSingleProfile = true; - - /** - * enable or disable the selection list if there is only a single profile - * - * @param selectionListEnabledForSingleProfile - * @return - */ - public AccountHeaderBuilder withSelectionListEnabledForSingleProfile(boolean selectionListEnabledForSingleProfile) { - this.mSelectionListEnabledForSingleProfile = selectionListEnabledForSingleProfile; - return this; - } - - //set the selection enabled disabled - protected boolean mSelectionListEnabled = true; - - /** - * enable or disable the selection list - * - * @param selectionListEnabled - * @return - */ - public AccountHeaderBuilder withSelectionListEnabled(boolean selectionListEnabled) { - this.mSelectionListEnabled = selectionListEnabled; - return this; - } - - // the drawerLayout to use - protected View mAccountHeaderContainer; - - /** - * You can pass a custom view for the drawer lib. note this requires the same structure as the drawer.xml - * - * @param accountHeader - * @return - */ - public AccountHeaderBuilder withAccountHeader(@NonNull View accountHeader) { - this.mAccountHeaderContainer = accountHeader; - return this; - } - - /** - * You can pass a custom layout for the drawer lib. see the drawer.xml in layouts of this lib on GitHub - * - * @param resLayout - * @return - */ - public AccountHeaderBuilder withAccountHeader(@LayoutRes int resLayout) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - if (resLayout != -1) { - this.mAccountHeaderContainer = mActivity.getLayoutInflater().inflate(resLayout, null, false); - } else { - if (mCompactStyle) { - this.mAccountHeaderContainer = mActivity.getLayoutInflater().inflate(R.layout.material_drawer_compact_header, null, false); - } else { - this.mAccountHeaderContainer = mActivity.getLayoutInflater().inflate(R.layout.material_drawer_header, null, false); - } - } - - return this; - } - - // the profiles to display - protected List mProfiles; - - /** - * set the arrayList of DrawerItems for the drawer - * - * @param profiles - * @return - */ - public AccountHeaderBuilder withProfiles(@NonNull List profiles) { - if (mDrawer != null) { - mDrawer.mDrawerBuilder.idDistributor.checkIds(profiles); - } - this.mProfiles = profiles; - return this; - } - - /** - * add single ore more DrawerItems to the Drawer - * - * @param profiles - * @return - */ - public AccountHeaderBuilder addProfiles(@NonNull IProfile... profiles) { - if (this.mProfiles == null) { - this.mProfiles = new ArrayList<>(); - } - if (mDrawer != null) { - mDrawer.mDrawerBuilder.idDistributor.checkIds(profiles); - } - Collections.addAll(this.mProfiles, profiles); - - return this; - } - - // the click listener to be fired on profile or selection click - protected AccountHeader.OnAccountHeaderListener mOnAccountHeaderListener; - - /** - * add a listener for the accountHeader - * - * @param onAccountHeaderListener - * @return - */ - public AccountHeaderBuilder withOnAccountHeaderListener(AccountHeader.OnAccountHeaderListener onAccountHeaderListener) { - this.mOnAccountHeaderListener = onAccountHeaderListener; - return this; - } - - //the on long click listener to be fired on profile longClick inside the list - protected AccountHeader.OnAccountHeaderItemLongClickListener mOnAccountHeaderItemLongClickListener; - - /** - * the on long click listener to be fired on profile longClick inside the list - * - * @param onAccountHeaderItemLongClickListener - * @return - */ - public AccountHeaderBuilder withOnAccountHeaderItemLongClickListener(AccountHeader.OnAccountHeaderItemLongClickListener onAccountHeaderItemLongClickListener) { - this.mOnAccountHeaderItemLongClickListener = onAccountHeaderItemLongClickListener; - return this; - } - - // the drawer to set the AccountSwitcher for - protected Drawer mDrawer; - - /** - * @param drawer - * @return - */ - public AccountHeaderBuilder withDrawer(@NonNull Drawer drawer) { - this.mDrawer = drawer; - - //set the top padding to 0 as this would happen when the AccountHeader is created during Drawer build time - drawer.getRecyclerView().setPadding(drawer.getRecyclerView().getPaddingLeft(), 0, drawer.getRecyclerView().getPaddingRight(), drawer.getRecyclerView().getPaddingBottom()); - return this; - } - - // savedInstance to restore state - protected Bundle mSavedInstance; - - /** - * create the drawer with the values of a savedInstance - * - * @param savedInstance - * @return - */ - public AccountHeaderBuilder withSavedInstance(Bundle savedInstance) { - this.mSavedInstance = savedInstance; - return this; - } - - /** - * helper method to set the height for the header! - * - * @param height - */ - private void setHeaderHeight(int height) { - if (mAccountHeaderContainer != null) { - ViewGroup.LayoutParams params = mAccountHeaderContainer.getLayoutParams(); - if (params != null) { - params.height = height; - mAccountHeaderContainer.setLayoutParams(params); - } - - View accountHeader = mAccountHeaderContainer.findViewById(R.id.material_drawer_account_header); - if (accountHeader != null) { - // TODO why is this null? - params = accountHeader.getLayoutParams(); - if(params != null) { - params.height = height; - accountHeader.setLayoutParams(params); - } - } - - View accountHeaderBackground = mAccountHeaderContainer.findViewById(R.id.material_drawer_account_header_background); - if (accountHeaderBackground != null) { - params = accountHeaderBackground.getLayoutParams(); - params.height = height; - accountHeaderBackground.setLayoutParams(params); - } - } - } - - /** - * a small helper to handle the selectionView - * - * @param on - */ - private void handleSelectionView(IProfile profile, boolean on) { - if (on) { - if (Build.VERSION.SDK_INT >= 23) { - mAccountHeaderContainer.setForeground(AppCompatResources.getDrawable(mAccountHeaderContainer.getContext(), mAccountHeaderTextSectionBackgroundResource)); - } else { - // todo foreground thing? - } - mAccountHeaderContainer.setOnClickListener(onSelectionClickListener); - mAccountHeaderContainer.setTag(R.id.material_drawer_profile_header, profile); - } else { - if (Build.VERSION.SDK_INT >= 23) { - mAccountHeaderContainer.setForeground(null); - } else { - // TODO foreground reset - } - mAccountHeaderContainer.setOnClickListener(null); - } - } - - /** - * method to build the header view - * - * @return - */ - public AccountHeader build() { - // if the user has not set a accountHeader use the default one :D - if (mAccountHeaderContainer == null) { - withAccountHeader(-1); - } - - // get the header view within the container - mAccountHeader = mAccountHeaderContainer.findViewById(R.id.material_drawer_account_header); - mStatusBarGuideline = mAccountHeaderContainer.findViewById(R.id.material_drawer_statusbar_guideline); - - //the default min header height by default 148dp - int defaultHeaderMinHeight = mActivity.getResources().getDimensionPixelSize(R.dimen.material_drawer_account_header_height); - int statusBarHeight = UIUtils.getStatusBarHeight(mActivity, true); - - // handle the height for the header - int height; - if (mHeight != null) { - height = mHeight.asPixel(mActivity); - } else { - if (mCompactStyle) { - height = mActivity.getResources().getDimensionPixelSize(R.dimen.material_drawer_account_header_height_compact); - } else { - //calculate the header height by getting the optimal drawer width and calculating it * 9 / 16 - height = (int) (DrawerUIUtils.getOptimalDrawerWidth(mActivity) * AccountHeader.NAVIGATION_DRAWER_ACCOUNT_ASPECT_RATIO); - - //if we are lower than api 19 (>= 19 we have a translucentStatusBar) the height should be a bit lower - //probably even if we are non translucent on > 19 devices? - if (Build.VERSION.SDK_INT < 19) { - int tempHeight = height - statusBarHeight; - //if we are lower than api 19 we are not able to have a translucent statusBar so we remove the height of the statusBar from the padding - //to prevent display issues we only reduce the height if we still fit the required minHeight of 148dp (R.dimen.material_drawer_account_header_height) - //we remove additional 8dp from the defaultMinHeaderHeight as there is some buffer in the header and to prevent to large spacings - if (tempHeight > defaultHeaderMinHeight - UIUtils.convertDpToPixel(8, mActivity)) { - height = tempHeight; - } - } - } - } - - // handle everything if we have a translucent status bar which only is possible on API >= 19 - if (mTranslucentStatusBar && Build.VERSION.SDK_INT >= 21) { - mStatusBarGuideline.setGuidelineBegin(statusBarHeight); - - //in fact it makes no difference if we have a translucent statusBar or not. we want 9/16 just if we are not compact - if (mCompactStyle) { - height = height + statusBarHeight; - } else if ((height - statusBarHeight) <= defaultHeaderMinHeight) { - //if the height + statusBar of the header is lower than the required 148dp + statusBar we change the height to be able to display all the data - height = defaultHeaderMinHeight + statusBarHeight; - } - } - - //set the height for the header - setHeaderHeight(height); - - // get the background view - mAccountHeaderBackground = (GifImageView) mAccountHeaderContainer.findViewById(R.id.material_drawer_account_header_background); - // set the background - ImageHolder.applyTo(mHeaderBackground, mAccountHeaderBackground, DrawerImageLoader.Tags.ACCOUNT_HEADER.name()); - - if (mHeaderBackgroundScaleType != null) { - mAccountHeaderBackground.setScaleType(mHeaderBackgroundScaleType); - } - - // get the text color to use for the text section - int textColor = ColorHolder.color(mTextColor, mActivity, R.attr.material_drawer_header_selection_text, R.color.material_drawer_header_selection_text); - int subTextColor = ColorHolder.color(mTextColor, mActivity, R.attr.material_drawer_header_selection_subtext, R.color.material_drawer_header_selection_subtext); - - mAccountHeaderTextSectionBackgroundResource = UIUtils.getSelectableBackgroundRes(mActivity); - handleSelectionView(mCurrentProfile, true); - - // set the arrow :D - mAccountSwitcherArrow = mAccountHeaderContainer.findViewById(R.id.material_drawer_account_header_text_switcher); - mAccountSwitcherArrow.setImageDrawable(new IconicsDrawable(mActivity, MaterialDrawerFont.Icon.mdf_arrow_drop_down).sizeRes(R.dimen.material_drawer_account_header_dropdown).paddingRes(R.dimen.material_drawer_account_header_dropdown_padding).color(subTextColor)); - - //get the fields for the name - mCurrentProfileView = mAccountHeader.findViewById(R.id.material_drawer_account_header_current); - mCurrentProfileName = mAccountHeader.findViewById(R.id.material_drawer_account_header_name); - mCurrentProfileEmail = mAccountHeader.findViewById(R.id.material_drawer_account_header_email); - - //set the typeface for the AccountHeader - if (mNameTypeface != null) { - mCurrentProfileName.setTypeface(mNameTypeface); - } else if (mTypeface != null) { - mCurrentProfileName.setTypeface(mTypeface); - } - - if (mEmailTypeface != null) { - mCurrentProfileEmail.setTypeface(mEmailTypeface); - } else if (mTypeface != null) { - mCurrentProfileEmail.setTypeface(mTypeface); - } - - mCurrentProfileName.setTextColor(textColor); - mCurrentProfileEmail.setTextColor(subTextColor); - - mProfileFirstView = mAccountHeader.findViewById(R.id.material_drawer_account_header_small_first); - mProfileSecondView = mAccountHeader.findViewById(R.id.material_drawer_account_header_small_second); - mProfileThirdView = mAccountHeader.findViewById(R.id.material_drawer_account_header_small_third); - - //calculate the profiles to set - calculateProfiles(); - - //process and build the profiles - buildProfiles(); - - // try to restore all saved values again - if (mSavedInstance != null) { - int selection = mSavedInstance.getInt(AccountHeader.BUNDLE_SELECTION_HEADER, -1); - if (selection != -1) { - //predefine selection (should be the first element - if (mProfiles != null && (selection) > -1 && selection < mProfiles.size()) { - switchProfiles(mProfiles.get(selection)); - } - } - } - - //everything created. now set the header - if (mDrawer != null) { - mDrawer.setHeader(mAccountHeaderContainer, mPaddingBelowHeader, mDividerBelowHeader); - } - - //forget the reference to the activity - mActivity = null; - - return new AccountHeader(this); - } - - /** - * helper method to calculate the order of the profiles - */ - protected void calculateProfiles() { - if (mProfiles == null) { - mProfiles = new ArrayList<>(); - } - - if (mCurrentProfile == null) { - int setCount = 0; - int size = mProfiles.size(); - for (int i = 0; i < size; i++) { - if (mProfiles.size() > i && mProfiles.get(i).isSelectable()) { - if (setCount == 0 && (mCurrentProfile == null)) { - mCurrentProfile = mProfiles.get(i); - } else if (setCount == 1 && (mProfileFirst == null)) { - mProfileFirst = mProfiles.get(i); - } else if (setCount == 2 && (mProfileSecond == null)) { - mProfileSecond = mProfiles.get(i); - } else if (setCount == 3 && (mProfileThird == null)) { - mProfileThird = mProfiles.get(i); - } - setCount++; - } - } - - return; - } - - IProfile[] previousActiveProfiles = new IProfile[]{ - mCurrentProfile, - mProfileFirst, - mProfileSecond, - mProfileThird - }; - - IProfile[] newActiveProfiles = new IProfile[4]; - Stack unusedProfiles = new Stack<>(); - - // try to keep existing active profiles in the same positions - for (int i = 0; i < mProfiles.size(); i++) { - IProfile p = mProfiles.get(i); - if (p.isSelectable()) { - boolean used = false; - for (int j = 0; j < 4; j++) { - if (previousActiveProfiles[j] == p) { - newActiveProfiles[j] = p; - used = true; - break; - } - } - if (!used) { - unusedProfiles.push(p); - } - } - } - - Stack activeProfiles = new Stack<>(); - // try to fill the gaps with new available profiles - for (int i = 0; i < 4; i++) { - if (newActiveProfiles[i] != null) { - activeProfiles.push(newActiveProfiles[i]); - } else if (!unusedProfiles.isEmpty()) { - activeProfiles.push(unusedProfiles.pop()); - } - } - - Stack reversedActiveProfiles = new Stack<>(); - while (!activeProfiles.empty()) { - reversedActiveProfiles.push(activeProfiles.pop()); - } - - // reassign active profiles - if (reversedActiveProfiles.isEmpty()) { - mCurrentProfile = null; - } else { - mCurrentProfile = reversedActiveProfiles.pop(); - } - if (reversedActiveProfiles.isEmpty()) { - mProfileFirst = null; - } else { - mProfileFirst = reversedActiveProfiles.pop(); - } - if (reversedActiveProfiles.isEmpty()) { - mProfileSecond = null; - } else { - mProfileSecond = reversedActiveProfiles.pop(); - } - if (reversedActiveProfiles.isEmpty()) { - mProfileThird = null; - } else { - mProfileThird = reversedActiveProfiles.pop(); - } - } - - /** - * helper method to switch the profiles - * - * @param newSelection - * @return true if the new selection was the current profile - */ - protected boolean switchProfiles(IProfile newSelection) { - if (newSelection == null) { - return false; - } - if (mCurrentProfile == newSelection) { - return true; - } - - if (mAlternativeProfileHeaderSwitching) { - int prevSelection = -1; - if (mProfileFirst == newSelection) { - prevSelection = 1; - } else if (mProfileSecond == newSelection) { - prevSelection = 2; - } else if (mProfileThird == newSelection) { - prevSelection = 3; - } - - IProfile tmp = mCurrentProfile; - mCurrentProfile = newSelection; - - if (prevSelection == 1) { - mProfileFirst = tmp; - } else if (prevSelection == 2) { - mProfileSecond = tmp; - } else if (prevSelection == 3) { - mProfileThird = tmp; - } - } else { - if (mProfiles != null) { - ArrayList previousActiveProfiles = new ArrayList<>(Arrays.asList(mCurrentProfile, mProfileFirst, mProfileSecond, mProfileThird)); - - if (previousActiveProfiles.contains(newSelection)) { - int position = -1; - - for (int i = 0; i < 4; i++) { - if (previousActiveProfiles.get(i) == newSelection) { - position = i; - break; - } - } - - if (position != -1) { - previousActiveProfiles.remove(position); - previousActiveProfiles.add(0, newSelection); - - mCurrentProfile = previousActiveProfiles.get(0); - mProfileFirst = previousActiveProfiles.get(1); - mProfileSecond = previousActiveProfiles.get(2); - mProfileThird = previousActiveProfiles.get(3); - } - } else { - mProfileThird = mProfileSecond; - mProfileSecond = mProfileFirst; - mProfileFirst = mCurrentProfile; - mCurrentProfile = newSelection; - } - } - } - - //if we only show the small profile images we have to make sure the first (would be the current selected) profile is also shown - if (mOnlySmallProfileImagesVisible) { - mProfileThird = mProfileSecond; - mProfileSecond = mProfileFirst; - mProfileFirst = mCurrentProfile; - //mCurrentProfile = mProfileThird; - } - - buildProfiles(); - - return false; - } - - /** - * helper method to build the views for the ui - */ - protected void buildProfiles() { - mCurrentProfileView.setVisibility(View.GONE); - mAccountSwitcherArrow.setVisibility(View.GONE); - mProfileFirstView.setVisibility(View.GONE); - mProfileFirstView.setOnClickListener(null); - mProfileSecondView.setVisibility(View.GONE); - mProfileSecondView.setOnClickListener(null); - mProfileThirdView.setVisibility(View.GONE); - mProfileThirdView.setOnClickListener(null); - mCurrentProfileName.setText(""); - mCurrentProfileEmail.setText(""); - - handleSelectionView(mCurrentProfile, true); - - if (mCurrentProfile != null) { - if ((mProfileImagesVisible || mOnlyMainProfileImageVisible) && !mOnlySmallProfileImagesVisible) { - setImageOrPlaceholder(mCurrentProfileView, mCurrentProfile.getIcon()); - if (mProfileImagesClickable) { - mCurrentProfileView.setOnClickListener(onCurrentProfileClickListener); - mCurrentProfileView.setOnLongClickListener(onCurrentProfileLongClickListener); - mCurrentProfileView.disableTouchFeedback(false); - } else { - mCurrentProfileView.disableTouchFeedback(true); - } - mCurrentProfileView.setVisibility(View.VISIBLE); - - mCurrentProfileView.invalidate(); - } else if (mCompactStyle) { - mCurrentProfileView.setVisibility(View.GONE); - } - - handleSelectionView(mCurrentProfile, true); - mAccountSwitcherArrow.setVisibility(View.VISIBLE); - mCurrentProfileView.setTag(R.id.material_drawer_profile_header, mCurrentProfile); - - StringHolder.applyTo(mCurrentProfile.getName(), mCurrentProfileName); - StringHolder.applyTo(mCurrentProfile.getEmail(), mCurrentProfileEmail); - - if (mProfileFirst != null && mProfileImagesVisible && !mOnlyMainProfileImageVisible) { - setImageOrPlaceholder(mProfileFirstView, mProfileFirst.getIcon()); - mProfileFirstView.setTag(R.id.material_drawer_profile_header, mProfileFirst); - if (mProfileImagesClickable) { - mProfileFirstView.setOnClickListener(onProfileClickListener); - mProfileFirstView.setOnLongClickListener(onProfileLongClickListener); - mProfileFirstView.disableTouchFeedback(false); - } else { - mProfileFirstView.disableTouchFeedback(true); - } - mProfileFirstView.setVisibility(View.VISIBLE); - mProfileFirstView.invalidate(); - } - if (mProfileSecond != null && mProfileImagesVisible && !mOnlyMainProfileImageVisible) { - setImageOrPlaceholder(mProfileSecondView, mProfileSecond.getIcon()); - mProfileSecondView.setTag(R.id.material_drawer_profile_header, mProfileSecond); - if (mProfileImagesClickable) { - mProfileSecondView.setOnClickListener(onProfileClickListener); - mProfileSecondView.setOnLongClickListener(onProfileLongClickListener); - mProfileSecondView.disableTouchFeedback(false); - } else { - mProfileSecondView.disableTouchFeedback(true); - } - mProfileSecondView.setVisibility(View.VISIBLE); - mProfileSecondView.invalidate(); - } - if (mProfileThird != null && mThreeSmallProfileImages && mProfileImagesVisible && !mOnlyMainProfileImageVisible) { - setImageOrPlaceholder(mProfileThirdView, mProfileThird.getIcon()); - mProfileThirdView.setTag(R.id.material_drawer_profile_header, mProfileThird); - if (mProfileImagesClickable) { - mProfileThirdView.setOnClickListener(onProfileClickListener); - mProfileThirdView.setOnLongClickListener(onProfileLongClickListener); - mProfileThirdView.disableTouchFeedback(false); - } else { - mProfileThirdView.disableTouchFeedback(true); - } - mProfileThirdView.setVisibility(View.VISIBLE); - mProfileThirdView.invalidate(); - } - } else if (mProfiles != null && mProfiles.size() > 0) { - IProfile profile = mProfiles.get(0); - mAccountHeader.setTag(R.id.material_drawer_profile_header, profile); - handleSelectionView(mCurrentProfile, true); - mAccountSwitcherArrow.setVisibility(View.VISIBLE); - if (mCurrentProfile != null) { - StringHolder.applyTo(mCurrentProfile.getName(), mCurrentProfileName); - StringHolder.applyTo(mCurrentProfile.getEmail(), mCurrentProfileEmail); - } - } - - if (!mSelectionFirstLineShown) { - mCurrentProfileName.setVisibility(View.GONE); - } - if (!TextUtils.isEmpty(mSelectionFirstLine)) { - mCurrentProfileName.setText(mSelectionFirstLine); - } - if (!mSelectionSecondLineShown) { - mCurrentProfileEmail.setVisibility(View.GONE); - } - if (!TextUtils.isEmpty(mSelectionSecondLine)) { - mCurrentProfileEmail.setText(mSelectionSecondLine); - } - - //if we disabled the list - if (!mSelectionListEnabled || !mSelectionListEnabledForSingleProfile && mProfileFirst == null && (mProfiles == null || mProfiles.size() == 1)) { - mAccountSwitcherArrow.setVisibility(View.GONE); - handleSelectionView(null, false); - } - - //if we disabled the list but still have set a custom listener - if (mOnAccountHeaderSelectionViewClickListener != null) { - handleSelectionView(mCurrentProfile, true); - } - } - - /** - * small helper method to set an profile image or a placeholder - * - * @param iv - * @param imageHolder - */ - private void setImageOrPlaceholder(ImageView iv, ImageHolder imageHolder) { - //cancel previous started image loading processes - DrawerImageLoader.getInstance().cancelImage(iv); - //set the placeholder - iv.setImageDrawable(DrawerImageLoader.getInstance().getImageLoader().placeholder(iv.getContext(), DrawerImageLoader.Tags.PROFILE.name())); - //set the real image (probably also the uri) - ImageHolder.applyTo(imageHolder, iv, DrawerImageLoader.Tags.PROFILE.name()); - } - - /** - * onProfileClickListener to notify onClick on the current profile image - */ - private View.OnClickListener onCurrentProfileClickListener = new View.OnClickListener() { - @Override - public void onClick(final View v) { - onProfileImageClick(v, true); - } - }; - - /** - * onProfileClickListener to notify onClick on a profile image - */ - private View.OnClickListener onProfileClickListener = new View.OnClickListener() { - @Override - public void onClick(final View v) { - onProfileImageClick(v, false); - } - }; - - /** - * calls the mOnAccountHEaderProfileImageListener and continues with the actions afterwards - * - * @param v - * @param current - */ - private void onProfileImageClick(View v, boolean current) { - IProfile profile = (IProfile) v.getTag(R.id.material_drawer_profile_header); - - boolean consumed = false; - if (mOnAccountHeaderProfileImageListener != null) { - consumed = mOnAccountHeaderProfileImageListener.onProfileImageClick(v, profile, current); - } - - //if the event was already consumed by the click don't continue. note that this will also stop the profile change event - if (!consumed) { - onProfileClick(v, current); - } - } - - /** - * onProfileLongClickListener to call the onProfileImageLongClick on the current profile image - */ - private View.OnLongClickListener onCurrentProfileLongClickListener = new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mOnAccountHeaderProfileImageListener != null) { - IProfile profile = (IProfile) v.getTag(R.id.material_drawer_profile_header); - return mOnAccountHeaderProfileImageListener.onProfileImageLongClick(v, profile, true); - } - return false; - } - }; - - /** - * onProfileLongClickListener to call the onProfileImageLongClick on a profile image - */ - private View.OnLongClickListener onProfileLongClickListener = new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (mOnAccountHeaderProfileImageListener != null) { - IProfile profile = (IProfile) v.getTag(R.id.material_drawer_profile_header); - return mOnAccountHeaderProfileImageListener.onProfileImageLongClick(v, profile, false); - } - return false; - } - }; - - protected void onProfileClick(View v, boolean current) { - final IProfile profile = (IProfile) v.getTag(R.id.material_drawer_profile_header); - switchProfiles(profile); - - //reset the drawer content - resetDrawerContent(v.getContext()); - - //notify the MiniDrawer about the clicked profile (only if one exists and is hooked to the Drawer - if (mDrawer != null && mDrawer.getDrawerBuilder() != null && mDrawer.getDrawerBuilder().mMiniDrawer != null) { - mDrawer.getDrawerBuilder().mMiniDrawer.onProfileClick(); - } - - //notify about the changed profile - boolean consumed = false; - if (mOnAccountHeaderListener != null) { - consumed = mOnAccountHeaderListener.onProfileChanged(v, profile, current); - } - - if (!consumed) { - if (mOnProfileClickDrawerCloseDelay > 0) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (mDrawer != null) { - mDrawer.closeDrawer(); - } - } - }, mOnProfileClickDrawerCloseDelay); - } else { - if (mDrawer != null) { - mDrawer.closeDrawer(); - } - } - } - } - - /** - * get the current selection - * - * @return - */ - protected int getCurrentSelection() { - if (mCurrentProfile != null && mProfiles != null) { - int i = 0; - for (IProfile profile : mProfiles) { - if (profile == mCurrentProfile) { - return i; - } - i++; - } - } - return -1; - } - - /** - * onSelectionClickListener to notify the onClick on the checkbox - */ - private View.OnClickListener onSelectionClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - boolean consumed = false; - if (mOnAccountHeaderSelectionViewClickListener != null) { - consumed = mOnAccountHeaderSelectionViewClickListener.onClick(v, (IProfile) v.getTag(R.id.material_drawer_profile_header)); - } - - if (mAccountSwitcherArrow.getVisibility() == View.VISIBLE && !consumed) { - toggleSelectionList(v.getContext()); - } - } - }; - - /** - * helper method to toggle the collection - * - * @param ctx - */ - protected void toggleSelectionList(Context ctx) { - if (mDrawer != null) { - //if we already show the list. reset everything instead - if (mDrawer.switchedDrawerContent()) { - resetDrawerContent(ctx); - mSelectionListShown = false; - } else { - //build and set the drawer selection list - buildDrawerSelectionList(); - - // update the arrow image within the drawer - mAccountSwitcherArrow.clearAnimation(); - ViewCompat.animate(mAccountSwitcherArrow).rotation(180).start(); - //mAccountSwitcherArrow.setImageDrawable(new IconicsDrawable(ctx, MaterialDrawerFont.Icon.mdf_arrow_drop_up).sizeRes(R.dimen.material_drawer_account_header_dropdown).paddingRes(R.dimen.material_drawer_account_header_dropdown_padding).color(ColorHolder.color(mTextColor, ctx, R.attr.material_drawer_header_selection_text, R.color.material_drawer_header_selection_text))); - mSelectionListShown = true; - } - } - } - - /** - * helper method to build and set the drawer selection list - */ - protected void buildDrawerSelectionList() { - int selectedPosition = -1; - int position = 0; - ArrayList profileDrawerItems = new ArrayList<>(); - if (mProfiles != null) { - for (IProfile profile : mProfiles) { - if (profile == mCurrentProfile) { - if (mCurrentHiddenInList) { - continue; - } else { - selectedPosition = mDrawer.mDrawerBuilder.getItemAdapter().getGlobalPosition(position); - } - } - if (profile instanceof IDrawerItem) { - ((IDrawerItem) profile).withSetSelected(false); - profileDrawerItems.add((IDrawerItem) profile); - } - position = position + 1; - } - } - mDrawer.switchDrawerContent(onDrawerItemClickListener, onDrawerItemLongClickListener, profileDrawerItems, selectedPosition); - } - - /** - * onDrawerItemClickListener to catch the selection for the new profile! - */ - private Drawer.OnDrawerItemClickListener onDrawerItemClickListener = new Drawer.OnDrawerItemClickListener() { - @Override - public boolean onItemClick(final View view, int position, final IDrawerItem drawerItem) { - final boolean isCurrentSelectedProfile; - if (drawerItem != null && drawerItem instanceof IProfile && drawerItem.isSelectable()) { - isCurrentSelectedProfile = switchProfiles((IProfile) drawerItem); - } else { - isCurrentSelectedProfile = false; - } - - if (mResetDrawerOnProfileListClick) { - mDrawer.setOnDrawerItemClickListener(null); - } - - //wrap the onSelection call and the reset stuff within a handler to prevent lag - if (mResetDrawerOnProfileListClick && mDrawer != null && view != null && view.getContext() != null) { - resetDrawerContent(view.getContext()); - } - - //notify the MiniDrawer about the clicked profile (only if one exists and is hooked to the Drawer - if (mDrawer != null && mDrawer.getDrawerBuilder() != null && mDrawer.getDrawerBuilder().mMiniDrawer != null) { - mDrawer.getDrawerBuilder().mMiniDrawer.onProfileClick(); - } - - boolean consumed = false; - if (drawerItem != null && drawerItem instanceof IProfile) { - if (mOnAccountHeaderListener != null) { - consumed = mOnAccountHeaderListener.onProfileChanged(view, (IProfile) drawerItem, isCurrentSelectedProfile); - } - } - - //if a custom behavior was chosen via the CloseDrawerOnProfileListClick then use this. else react on the result of the onProfileChanged listener - if (mCloseDrawerOnProfileListClick != null) { - consumed = consumed && !mCloseDrawerOnProfileListClick; - } - - //totally custom handling of the drawer behavior as otherwise the selection of the profile list is set to the Drawer - if (mDrawer != null && !consumed) { - //close the drawer after click - mDrawer.mDrawerBuilder.closeDrawerDelayed(); - } - - //consume the event to prevent setting the clicked item as selected in the already switched item list - return true; - } - }; - - /** - * onDrawerItemLongClickListener to catch the longClick for a profile - */ - private Drawer.OnDrawerItemLongClickListener onDrawerItemLongClickListener = new Drawer.OnDrawerItemLongClickListener() { - @Override - public boolean onItemLongClick(View view, int position, IDrawerItem drawerItem) { - //if a longClickListener was defined use it - if (mOnAccountHeaderItemLongClickListener != null) { - final boolean isCurrentSelectedProfile; - isCurrentSelectedProfile = drawerItem != null && drawerItem.isSelected(); - - if (drawerItem != null && drawerItem instanceof IProfile) { - return mOnAccountHeaderItemLongClickListener.onProfileLongClick(view, (IProfile) drawerItem, isCurrentSelectedProfile); - } - } - return false; - } - }; - - /** - * helper method to reset the drawer content - */ - private void resetDrawerContent(Context ctx) { - if (mDrawer != null) { - mDrawer.resetDrawerContent(); - } - - mAccountSwitcherArrow.clearAnimation(); - ViewCompat.animate(mAccountSwitcherArrow).rotation(0).start(); - //mAccountSwitcherArrow.setImageDrawable(new IconicsDrawable(ctx, MaterialDrawerFont.Icon.mdf_arrow_drop_down).sizeRes(R.dimen.material_drawer_account_header_dropdown).paddingRes(R.dimen.material_drawer_account_header_dropdown_padding).color(ColorHolder.color(mTextColor, ctx, R.attr.material_drawer_header_selection_text, R.color.material_drawer_header_selection_text))); - } - - /** - * small helper class to update the header and the list - */ - protected void updateHeaderAndList() { - //recalculate the profiles - calculateProfiles(); - //update the profiles in the header - buildProfiles(); - //if we currently show the list add the new item directly to it - if (mSelectionListShown) { - buildDrawerSelectionList(); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/Drawer.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/Drawer.java deleted file mode 100644 index 91446f13..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/Drawer.java +++ /dev/null @@ -1,1174 +0,0 @@ -package com.mikepenz.materialdrawer; - -import android.app.Activity; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.util.Pair; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.Toolbar; -import pl.droidsonroids.gif.GifDrawable; - -import android.view.View; -import android.widget.AdapterView; -import android.widget.FrameLayout; - -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.adapters.ModelAdapter; -import com.mikepenz.fastadapter.expandable.ExpandableExtension; -import com.mikepenz.fastadapter.select.SelectExtension; -import com.mikepenz.materialdrawer.holder.DimenHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.AbstractDrawerItem; -import com.mikepenz.materialdrawer.model.ContainerDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.Badgeable; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.Iconable; -import com.mikepenz.materialdrawer.model.interfaces.Nameable; -import com.mikepenz.materialize.Materialize; -import com.mikepenz.materialize.view.ScrimInsetsRelativeLayout; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public class Drawer { - /** - * BUNDLE param to store the selection - */ - protected static final String BUNDLE_SELECTION = "_selection"; - protected static final String BUNDLE_SELECTION_APPENDED = "_selection_appended"; - protected static final String BUNDLE_STICKY_FOOTER_SELECTION = "bundle_sticky_footer_selection"; - protected static final String BUNDLE_STICKY_FOOTER_SELECTION_APPENDED = "bundle_sticky_footer_selection_appended"; - protected static final String BUNDLE_DRAWER_CONTENT_SWITCHED = "bundle_drawer_content_switched"; - protected static final String BUNDLE_DRAWER_CONTENT_SWITCHED_APPENDED = "bundle_drawer_content_switched_appended"; - - /** - * Per the design guidelines, you should show the drawer on launch until the user manually - * expands it. This shared preference tracks this. - */ - protected static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned"; - - /** - * Per the design guidelines, you should show the drawer on launch until the user manually - * expands it. This shared preference tracks this. - */ - protected static final String PREF_USER_OPENED_DRAWER_BY_DRAGGING = "navigation_drawer_dragged_open"; - - - protected final DrawerBuilder mDrawerBuilder; - private FrameLayout mContentView; - - /** - * the protected Constructor for the result - * - * @param drawerBuilder - */ - protected Drawer(DrawerBuilder drawerBuilder) { - this.mDrawerBuilder = drawerBuilder; - } - - /** - * the protected getter of the mDrawerBuilder - * only used internally to prevent the default behavior of some public methods - * - * @return - */ - protected DrawerBuilder getDrawerBuilder() { - return this.mDrawerBuilder; - } - - /** - * Get the DrawerLayout of the current drawer - * - * @return - */ - public DrawerLayout getDrawerLayout() { - return this.mDrawerBuilder.mDrawerLayout; - } - - /** - * Sets the toolbar which should be used in combination with the drawer - * This will handle the ActionBarDrawerToggle for you. - * Do not set this if you are in a sub activity and want to handle the back arrow on your own - * - * @param activity - * @param toolbar the toolbar which is used in combination with the drawer - */ - public void setToolbar(@NonNull Activity activity, @NonNull Toolbar toolbar) { - setToolbar(activity, toolbar, false); - } - - /** - * Sets the toolbar which should be used in combination with the drawer - * This will handle the ActionBarDrawerToggle for you. - * Do not set this if you are in a sub activity and want to handle the back arrow on your own - * - * @param activity - * @param toolbar the toolbar which is used in combination with the drawer - * @param recreateActionBarDrawerToggle defines if the ActionBarDrawerToggle needs to be recreated with the new set Toolbar - */ - public void setToolbar(@NonNull Activity activity, @NonNull Toolbar toolbar, boolean recreateActionBarDrawerToggle) { - this.mDrawerBuilder.mToolbar = toolbar; - this.mDrawerBuilder.handleDrawerNavigation(activity, recreateActionBarDrawerToggle); - } - - /** - * Add a custom ActionBarDrawerToggle which will be used in combination with this drawer. - * - * @param actionBarDrawerToggle - */ - public void setActionBarDrawerToggle(@NonNull ActionBarDrawerToggle actionBarDrawerToggle) { - this.mDrawerBuilder.mActionBarDrawerToggleEnabled = true; - this.mDrawerBuilder.mActionBarDrawerToggle = actionBarDrawerToggle; - this.mDrawerBuilder.handleDrawerNavigation(null, false); - } - - /** - * Open the drawer - */ - public void openDrawer() { - if (mDrawerBuilder.mDrawerLayout != null && mDrawerBuilder.mSliderLayout != null) { - mDrawerBuilder.mDrawerLayout.openDrawer(mDrawerBuilder.mDrawerGravity); - } - } - - /** - * close the drawer - */ - public void closeDrawer() { - if (mDrawerBuilder.mDrawerLayout != null) { - mDrawerBuilder.mDrawerLayout.closeDrawer(mDrawerBuilder.mDrawerGravity); - } - } - - /** - * Get the current state of the drawer. - * True if the drawer is currently open. - * - * @return - */ - public boolean isDrawerOpen() { - if (mDrawerBuilder.mDrawerLayout != null && mDrawerBuilder.mSliderLayout != null) { - return mDrawerBuilder.mDrawerLayout.isDrawerOpen(mDrawerBuilder.mDrawerGravity); - } - return false; - } - - - /** - * set the insetsFrameLayout to display the content in fullscreen - * under the statusBar and navigationBar - * - * @param fullscreen - */ - public void setFullscreen(boolean fullscreen) { - if (mDrawerBuilder.mMaterialize != null) { - mDrawerBuilder.mMaterialize.setFullscreen(fullscreen); - } - } - - /** - * get the Materialize object used to beautify your activity - * - * @return - */ - public Materialize getMaterialize() { - return mDrawerBuilder.mMaterialize; - } - - - /** - * gets the already generated MiniDrawer or creates a new one - * - * @return - */ - public MiniDrawer getMiniDrawer() { - if (mDrawerBuilder.mMiniDrawer == null) { - mDrawerBuilder.mMiniDrawer = new MiniDrawer().withDrawer(this).withAccountHeader(mDrawerBuilder.mAccountHeader); - } - return mDrawerBuilder.mMiniDrawer; - } - - /** - * get the slider layout of the current drawer. - * This is the layout containing the ListView - * - * @return - */ - public ScrimInsetsRelativeLayout getSlider() { - return mDrawerBuilder.mSliderLayout; - } - - /** - * get the container frameLayout of the current drawer - * - * @return - */ - public FrameLayout getContent() { - if (mContentView == null && this.mDrawerBuilder.mDrawerLayout != null) { - mContentView = (FrameLayout) this.mDrawerBuilder.mDrawerLayout.findViewById(R.id.content_layout); - } - return mContentView; - } - - /** - * get the listView of the current drawer - * - * @return - */ - public RecyclerView getRecyclerView() { - return mDrawerBuilder.mRecyclerView; - } - - /** - * get the FastAdapter of the current drawer - * - * @return - */ - public FastAdapter getAdapter() { - return mDrawerBuilder.mAdapter; - } - - /** - * get the HeaderAdapter of the current drawer - * - * @return - */ - public ModelAdapter getHeaderAdapter() { - return mDrawerBuilder.mHeaderAdapter; - } - - /** - * get the ItemAdapter of the current drawer - * - * @return - */ - public ModelAdapter getItemAdapter() { - return mDrawerBuilder.mItemAdapter; - } - - /** - * get the FooterAdapter of the current drawer - * - * @return - */ - public ModelAdapter getFooterAdapter() { - return mDrawerBuilder.mFooterAdapter; - } - - /** - * get the ExpandableExtension of the current drawer - * - * @return - */ - public ExpandableExtension getExpandableExtension() { - return mDrawerBuilder.mExpandableExtension; - } - - /** - * get all drawerItems of the current drawer - * - * @return - */ - public List getDrawerItems() { - return mDrawerBuilder.getItemAdapter().getAdapterItems(); - } - - /** - * get the Header View if set else NULL - * - * @return - */ - public View getHeader() { - return mDrawerBuilder.mHeaderView; - } - - public void setHeaderBackground(String headerBackgroundPath) { - if (headerBackgroundPath.endsWith(".gif")) { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - mDrawerBuilder.mAccountHeader.mAccountHeaderBuilder.mAccountHeaderBackground.setImageURI(null); - mDrawerBuilder.mAccountHeader.mAccountHeaderBuilder.mAccountHeaderBackground.setImageDrawable(new GifDrawable(headerBackgroundPath)); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - else { - mDrawerBuilder.mAccountHeader.mAccountHeaderBuilder.mAccountHeaderBackground.setImageURI(Uri.parse(headerBackgroundPath)); - } - } - - public void setHeaderBackground(int headerBackgroundRes) { - mDrawerBuilder.mAccountHeader.mAccountHeaderBuilder.mAccountHeaderBackground.setImageResource(headerBackgroundRes); - } - - - /** - * get the StickyHeader View if set else NULL - * - * @return - */ - public View getStickyHeader() { - return mDrawerBuilder.mStickyHeaderView; - } - - /** - * method to replace a previous set header - * - * @param view - */ - public void setHeader(@NonNull View view) { - setHeader(view, true, true); - } - - /** - * method to replace a previous set header - * - * @param view - * @param divider - */ - public void setHeader(@NonNull View view, boolean divider) { - setHeader(view, true, divider); - } - - /** - * method to replace a previous set header - * - * @param view - * @param padding - * @param divider - */ - public void setHeader(@NonNull View view, boolean padding, boolean divider) { - setHeader(view, padding, divider, null); - } - - /** - * method to replace a previous set header - * - * @param view - * @param padding - * @param divider - * @param height - */ - public void setHeader(@NonNull View view, boolean padding, boolean divider, DimenHolder height) { - mDrawerBuilder.getHeaderAdapter().clear(); - if (padding) { - mDrawerBuilder.getHeaderAdapter().add(new ContainerDrawerItem().withView(view).withDivider(divider).withHeight(height).withViewPosition(ContainerDrawerItem.Position.TOP)); - } else { - mDrawerBuilder.getHeaderAdapter().add(new ContainerDrawerItem().withView(view).withDivider(divider).withHeight(height).withViewPosition(ContainerDrawerItem.Position.NONE)); - } - //we need to set the padding so the header starts on top - mDrawerBuilder.mRecyclerView.setPadding(mDrawerBuilder.mRecyclerView.getPaddingLeft(), 0, mDrawerBuilder.mRecyclerView.getPaddingRight(), mDrawerBuilder.mRecyclerView.getPaddingBottom()); - } - - /** - * method to remove the header of the list - */ - public void removeHeader() { - mDrawerBuilder.getHeaderAdapter().clear(); - //possibly there should be also a reset of the padding so the first item starts below the toolbar - } - - /** - * get the Footer View if set else NULL - * - * @return - */ - public View getFooter() { - return mDrawerBuilder.mFooterView; - } - - /** - * get the StickyFooter View if set else NULL - * - * @return - */ - public View getStickyFooter() { - return mDrawerBuilder.mStickyFooterView; - } - - /** - * get the StickyFooter Shadow View if set else NULL - * - * @return - */ - private View getStickyFooterShadow() { - return mDrawerBuilder.mStickyFooterShadowView; - } - - /** - * get the ActionBarDrawerToggle - * - * @return - */ - public ActionBarDrawerToggle getActionBarDrawerToggle() { - return mDrawerBuilder.mActionBarDrawerToggle; - } - - /** - * sets the gravity for this drawer. - * - * @param gravity the gravity which is defined for the drawer - */ - public void setGravity(int gravity) { - DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) getSlider().getLayoutParams(); - params.gravity = gravity; - getSlider().setLayoutParams(params); - mDrawerBuilder.mDrawerGravity = gravity; - } - - /** - * calculates the position of an drawerItem. searching by it's identifier - * - * @param drawerItem - * @return - */ - public int getPosition(@NonNull IDrawerItem drawerItem) { - return getPosition(drawerItem.getIdentifier()); - } - - /** - * calculates the position of an drawerItem. searching by it's identifier - * - * @param identifier - * @return - */ - public int getPosition(long identifier) { - return DrawerUtils.getPositionByIdentifier(mDrawerBuilder, identifier); - } - - /** - * returns the DrawerItem by the given identifier - * - * @param identifier - * @return - */ - public IDrawerItem getDrawerItem(long identifier) { - Pair res = getAdapter().getItemById(identifier); - if (res != null) { - return res.first; - } else { - return null; - } - } - - /** - * returns the found drawerItem by the given tag - * - * @param tag - * @return - */ - public IDrawerItem getDrawerItem(Object tag) { - return DrawerUtils.getDrawerItem(getDrawerItems(), tag); - } - - /** - * calculates the position of an drawerItem. searching by it's identifier - * - * @param drawerItem - * @return - */ - public int getStickyFooterPosition(@NonNull IDrawerItem drawerItem) { - return getStickyFooterPosition(drawerItem.getIdentifier()); - } - - /** - * calculates the position of an drawerItem inside the footer. searching by it's identfier - * - * @param identifier - * @return - */ - public int getStickyFooterPosition(long identifier) { - return DrawerUtils.getStickyFooterPositionByIdentifier(mDrawerBuilder, identifier); - } - - /** - * get the current position of the selected drawer element - * - * @return - */ - public int getCurrentSelectedPosition() { - return mDrawerBuilder.mAdapter.getSelections().size() == 0 ? -1 : mDrawerBuilder.mAdapter.getSelections().iterator().next(); - } - - /** - * get the current selected item identifier - * - * @return - */ - public long getCurrentSelection() { - IDrawerItem drawerItem = mDrawerBuilder.getDrawerItem(getCurrentSelectedPosition()); - if (drawerItem != null) { - return drawerItem.getIdentifier(); - } - return -1; - } - - /** - * get the current position of the selected sticky footer element - * - * @return - */ - public int getCurrentStickyFooterSelectedPosition() { - return mDrawerBuilder.mCurrentStickyFooterSelection; - } - - /** - * deselects all selected items - */ - public void deselect() { - getAdapter().deselect(); - } - - /** - * deselects the item with the given identifier - * - * @param identifier the identifier to search for - */ - public void deselect(long identifier) { - getAdapter().deselect(getPosition(identifier)); - } - - /** - * set the current selection in the drawer - * NOTE: This will trigger onDrawerItemSelected without a view! - * - * @param identifier the identifier to search for - */ - public void setSelection(long identifier) { - setSelection(identifier, true); - } - - /** - * set the current selection in the drawer - * NOTE: This will trigger onDrawerItemSelected without a view if you pass fireOnClick = true; - * - * @param identifier the identifier to search for - * @param fireOnClick true if the click listener should be called - */ - public void setSelection(long identifier, boolean fireOnClick) { - SelectExtension select = getAdapter().getExtension(SelectExtension.class); - if (select != null) { - select.deselect(); - select.selectByIdentifier(identifier, false, true); - - //we also have to call the general notify - Pair res = getAdapter().getItemById(identifier); - if (res != null) { - Integer position = res.second; - notifySelect(position != null ? position : -1, fireOnClick); - } - } - } - - /** - * set the current selection in the footer of the drawer - * NOTE: This will trigger onDrawerItemSelected without a view if you pass fireOnClick = true; - * - * @param identifier the identifier to search for - * @param fireOnClick true if the click listener should be called - */ - public void setStickyFooterSelection(long identifier, boolean fireOnClick) { - setStickyFooterSelectionAtPosition(getStickyFooterPosition(identifier), fireOnClick); - } - - /** - * set the current selection in the drawer - * NOTE: This will trigger onDrawerItemSelected without a view! - * - * @param drawerItem the drawerItem to select (this requires a set identifier) - */ - public void setSelection(@NonNull IDrawerItem drawerItem) { - setSelection(drawerItem.getIdentifier(), true); - } - - /** - * set the current selection in the drawer - * NOTE: This will trigger onDrawerItemSelected without a view if you pass fireOnClick = true; - * - * @param drawerItem the drawerItem to select (this requires a set identifier) - * @param fireOnClick true if the click listener should be called - */ - public void setSelection(@NonNull IDrawerItem drawerItem, boolean fireOnClick) { - setSelection(drawerItem.getIdentifier(), fireOnClick); - } - - /** - * set the current selection in the drawer - * NOTE: This will trigger onDrawerItemSelected without a view! - * - * @param position the position to select - */ - public boolean setSelectionAtPosition(int position) { - return setSelectionAtPosition(position, true); - } - - /* - * set the current selection in the drawer - * NOTE: this also deselects all other selections. if you do not want this. use the direct api of the adater .getAdapter().select(position, fireOnClick) - * NOTE: This will trigger onDrawerItemSelected without a view if you pass fireOnClick = true; - * - * @param position - * @param fireOnClick - * @return true if the event was consumed - */ - public boolean setSelectionAtPosition(int position, boolean fireOnClick) { - if (mDrawerBuilder.mRecyclerView != null) { - SelectExtension select = getAdapter().getExtension(SelectExtension.class); - if (select != null) { - select.deselect(); - select.select(position, false); - notifySelect(position, fireOnClick); - } - } - return false; - } - - private void notifySelect(int position, boolean fireOnClick) { - if (fireOnClick && position >= 0) { - IDrawerItem item = mDrawerBuilder.mAdapter.getItem(position); - - if (item instanceof AbstractDrawerItem && ((AbstractDrawerItem) item).getOnDrawerItemClickListener() != null) { - ((AbstractDrawerItem) item).getOnDrawerItemClickListener().onItemClick(null, position, item); - } - - if (mDrawerBuilder.mOnDrawerItemClickListener != null) { - mDrawerBuilder.mOnDrawerItemClickListener.onItemClick(null, position, item); - } - } - - //we set the selection on a normal item in the drawer so we have to deselect the items in the StickyDrawer - mDrawerBuilder.resetStickyFooterSelection(); - } - - /** - * set the current selection in the footer of the drawer - * NOTE: This will trigger onDrawerItemSelected without a view! - * - * @param position the position to select - */ - public void setStickyFooterSelectionAtPosition(int position) { - setStickyFooterSelectionAtPosition(position, true); - } - - /** - * set the current selection in the footer of the drawer - * NOTE: This will trigger onDrawerItemSelected without a view if you pass fireOnClick = true; - * - * @param position - * @param fireOnClick - */ - public void setStickyFooterSelectionAtPosition(int position, boolean fireOnClick) { - DrawerUtils.setStickyFooterSelection(mDrawerBuilder, position, fireOnClick); - } - - /** - * update a specific drawer item :D - * automatically identified by its id - * - * @param drawerItem - */ - public void updateItem(@NonNull IDrawerItem drawerItem) { - updateItemAtPosition(drawerItem, getPosition(drawerItem)); - } - - /** - * update the badge for a specific drawerItem - * identified by its id - * - * @param identifier - * @param badge - */ - public void updateBadge(long identifier, StringHolder badge) { - IDrawerItem drawerItem = getDrawerItem(identifier); - if (drawerItem instanceof Badgeable) { - Badgeable badgeable = (Badgeable) drawerItem; - badgeable.withBadge(badge); - updateItem((IDrawerItem) badgeable); - } - } - - /** - * update the name for a specific drawerItem - * identified by its id - * - * @param identifier - * @param name - */ - public void updateName(long identifier, StringHolder name) { - IDrawerItem drawerItem = getDrawerItem(identifier); - if (drawerItem instanceof Nameable) { - Nameable pdi = (Nameable) drawerItem; - pdi.withName(name); - updateItem((IDrawerItem) pdi); - } - } - - /** - * update the name for a specific drawerItem - * identified by its id - * - * @param identifier - * @param image - */ - public void updateIcon(long identifier, ImageHolder image) { - IDrawerItem drawerItem = getDrawerItem(identifier); - if (drawerItem instanceof Iconable) { - Iconable pdi = (Iconable) drawerItem; - pdi.withIcon(image); - updateItem((IDrawerItem) pdi); - } - } - - /** - * Update a drawerItem at a specific position - * - * @param drawerItem - * @param position - */ - public void updateItemAtPosition(@NonNull IDrawerItem drawerItem, int position) { - if (mDrawerBuilder.checkDrawerItem(position, false)) { - mDrawerBuilder.getItemAdapter().set(position, drawerItem); - } - } - - /** - * Add a drawerItem at the end - * - * @param drawerItem - */ - public void addItem(@NonNull IDrawerItem drawerItem) { - mDrawerBuilder.getItemAdapter().add(drawerItem); - } - - /** - * Add a drawerItem at a specific position - * - * @param drawerItem - * @param position - */ - public void addItemAtPosition(@NonNull IDrawerItem drawerItem, int position) { - mDrawerBuilder.getItemAdapter().add(position, drawerItem); - } - - /** - * Set a drawerItem at a specific position - * - * @param drawerItem - * @param position - */ - public void setItemAtPosition(@NonNull IDrawerItem drawerItem, int position) { - mDrawerBuilder.getItemAdapter().add(position, drawerItem); - } - - /** - * Remove a drawerItem at a specific position - * - * @param position - */ - public void removeItemByPosition(int position) { - if (mDrawerBuilder.checkDrawerItem(position, false)) { - mDrawerBuilder.getItemAdapter().remove(position); - } - } - - /** - * Remove a drawerItem by the identifier - * - * @param identifier - */ - public void removeItem(long identifier) { - getItemAdapter().removeByIdentifier(identifier); - } - - /** - * remove a list of drawerItems by ther identifiers - * - * @param identifiers - */ - public void removeItems(long... identifiers) { - if (identifiers != null) { - for (long identifier : identifiers) { - removeItem(identifier); - } - } - } - - /** - * Removes all items from drawer - */ - public void removeAllItems() { - mDrawerBuilder.getItemAdapter().clear(); - } - - /** - * add new Items to the current DrawerItem List - * - * @param drawerItems - */ - public void addItems(@NonNull IDrawerItem... drawerItems) { - mDrawerBuilder.getItemAdapter().add(drawerItems); - } - - /** - * add new items to the current DrawerItem list at a specific position - * - * @param position - * @param drawerItems - */ - public void addItemsAtPosition(int position, @NonNull IDrawerItem... drawerItems) { - mDrawerBuilder.getItemAdapter().add(position, drawerItems); - } - - /** - * Replace the current DrawerItems with a new ArrayList of items - * - * @param drawerItems - */ - public void setItems(@NonNull List drawerItems) { - setItems(drawerItems, false); - } - - /** - * replace the current DrawerItems with the new ArrayList. - * - * @param drawerItems - * @param switchedItems - */ - private void setItems(@NonNull List drawerItems, boolean switchedItems) { - //if we are currently at a switched list set the new reference - if (originalDrawerItems != null && !switchedItems) { - originalDrawerItems = drawerItems; - } - mDrawerBuilder.getItemAdapter().setNewList(drawerItems); - } - - /** - * update a specific footerDrawerItem :D - * automatically identified by it's id - * - * @param drawerItem - */ - public void updateStickyFooterItem(@NonNull IDrawerItem drawerItem) { - updateStickyFooterItemAtPosition(drawerItem, getStickyFooterPosition(drawerItem)); - } - - /** - * update a footerDrawerItem at a specific position - * - * @param drawerItem - * @param position - */ - public void updateStickyFooterItemAtPosition(@NonNull IDrawerItem drawerItem, int position) { - if (mDrawerBuilder.mStickyDrawerItems != null && mDrawerBuilder.mStickyDrawerItems.size() > position) { - mDrawerBuilder.mStickyDrawerItems.set(position, drawerItem); - } - - DrawerUtils.rebuildStickyFooterView(mDrawerBuilder); - } - - - /** - * Add a footerDrawerItem at the end - * - * @param drawerItem - */ - public void addStickyFooterItem(@NonNull IDrawerItem drawerItem) { - if (mDrawerBuilder.mStickyDrawerItems == null) { - mDrawerBuilder.mStickyDrawerItems = new ArrayList<>(); - } - mDrawerBuilder.mStickyDrawerItems.add(drawerItem); - - DrawerUtils.rebuildStickyFooterView(mDrawerBuilder); - } - - /** - * Add a footerDrawerItem at a specific position - * - * @param drawerItem - * @param position - */ - public void addStickyFooterItemAtPosition(@NonNull IDrawerItem drawerItem, int position) { - if (mDrawerBuilder.mStickyDrawerItems == null) { - mDrawerBuilder.mStickyDrawerItems = new ArrayList<>(); - } - mDrawerBuilder.mStickyDrawerItems.add(position, drawerItem); - - DrawerUtils.rebuildStickyFooterView(mDrawerBuilder); - } - - /** - * Set a footerDrawerItem at a specific position - * - * @param drawerItem - * @param position - */ - public void setStickyFooterItemAtPosition(@NonNull IDrawerItem drawerItem, int position) { - if (mDrawerBuilder.mStickyDrawerItems != null && mDrawerBuilder.mStickyDrawerItems.size() > position) { - mDrawerBuilder.mStickyDrawerItems.set(position, drawerItem); - } - - DrawerUtils.rebuildStickyFooterView(mDrawerBuilder); - } - - - /** - * Remove a footerDrawerItem at a specific position - * - * @param position - */ - public void removeStickyFooterItemAtPosition(int position) { - if (mDrawerBuilder.mStickyDrawerItems != null && mDrawerBuilder.mStickyDrawerItems.size() > position) { - mDrawerBuilder.mStickyDrawerItems.remove(position); - } - - DrawerUtils.rebuildStickyFooterView(mDrawerBuilder); - } - - /** - * Removes all footerItems from drawer - */ - public void removeAllStickyFooterItems() { - if (mDrawerBuilder.mStickyDrawerItems != null) { - mDrawerBuilder.mStickyDrawerItems.clear(); - } - if (mDrawerBuilder.mStickyFooterView != null) { - mDrawerBuilder.mStickyFooterView.setVisibility(View.GONE); - } - } - - /** - * setter for the OnDrawerItemClickListener - * - * @param onDrawerItemClickListener - */ - public void setOnDrawerItemClickListener(OnDrawerItemClickListener onDrawerItemClickListener) { - mDrawerBuilder.mOnDrawerItemClickListener = onDrawerItemClickListener; - } - - public void setOnDrawerNavigationListener(OnDrawerNavigationListener onDrawerNavigationListener) { //WBE - mDrawerBuilder.mOnDrawerNavigationListener = onDrawerNavigationListener; - } - - /** - * method to get the OnDrawerItemClickListener - * - * @return - */ - public OnDrawerItemClickListener getOnDrawerItemClickListener() { - return mDrawerBuilder.mOnDrawerItemClickListener; - } - - /** - * method to get the OnDrawerNavigationListener - * - * @return - */ - public OnDrawerNavigationListener getOnDrawerNavigationListener() { //WBE - return mDrawerBuilder.mOnDrawerNavigationListener; - } - - /** - * setter for the OnDrawerItemLongClickListener - * - * @param onDrawerItemLongClickListener - */ - public void setOnDrawerItemLongClickListener(OnDrawerItemLongClickListener onDrawerItemLongClickListener) { - mDrawerBuilder.mOnDrawerItemLongClickListener = onDrawerItemLongClickListener; - } - - /** - * method to get the OnDrawerItemLongClickListener - * - * @return - */ - public OnDrawerItemLongClickListener getOnDrawerItemLongClickListener() { - return mDrawerBuilder.mOnDrawerItemLongClickListener; - } - - //variables to store and remember the original list of the drawer - private Drawer.OnDrawerItemClickListener originalOnDrawerItemClickListener; - private Drawer.OnDrawerItemLongClickListener originalOnDrawerItemLongClickListener; - private List originalDrawerItems; - private Bundle originalDrawerState; - - /** - * information if the current drawer content is switched by alternative content (profileItems) - * - * @return - */ - public boolean switchedDrawerContent() { - return !(originalOnDrawerItemClickListener == null && originalDrawerItems == null && originalDrawerState == null); - } - - /** - * get the original list of drawerItems - * - * @return - */ - public List getOriginalDrawerItems() { - return originalDrawerItems; - } - - /** - * method to switch the drawer content to new elements - * - * @param onDrawerItemClickListener - * @param drawerItems - * @param drawerSelection - */ - public void switchDrawerContent(@NonNull OnDrawerItemClickListener onDrawerItemClickListener, OnDrawerItemLongClickListener onDrawerItemLongClickListener, @NonNull List drawerItems, int drawerSelection) { - //just allow a single switched drawer - if (!switchedDrawerContent()) { - //save out previous values - originalOnDrawerItemClickListener = getOnDrawerItemClickListener(); - originalOnDrawerItemLongClickListener = getOnDrawerItemLongClickListener(); - originalDrawerState = getAdapter().saveInstanceState(new Bundle()); - mDrawerBuilder.mExpandableExtension.collapse(false); - originalDrawerItems = getDrawerItems(); - } - - //set the new items - setOnDrawerItemClickListener(onDrawerItemClickListener); - setOnDrawerItemLongClickListener(onDrawerItemLongClickListener); - setItems(drawerItems, true); - setSelectionAtPosition(drawerSelection, false); - - if (!mDrawerBuilder.mKeepStickyItemsVisible) { - //hide stickyFooter and it's shadow - if (getStickyFooter() != null) { - getStickyFooter().setVisibility(View.GONE); - } - if (getStickyFooterShadow() != null) { - getStickyFooterShadow().setVisibility(View.GONE); - } - } - } - - /** - * helper method to reset to the original drawerContent - */ - public void resetDrawerContent() { - if (switchedDrawerContent()) { - //set the new items - setOnDrawerItemClickListener(originalOnDrawerItemClickListener); - setOnDrawerItemLongClickListener(originalOnDrawerItemLongClickListener); - setItems(originalDrawerItems, true); - getAdapter().withSavedInstanceState(originalDrawerState); - //remove the references - originalOnDrawerItemClickListener = null; - originalOnDrawerItemLongClickListener = null; - originalDrawerItems = null; - originalDrawerState = null; - - //if we switch back scroll back to the top - mDrawerBuilder.mRecyclerView.smoothScrollToPosition(0); - - //show the stickyFooter and it's shadow again - if (getStickyFooter() != null) { - getStickyFooter().setVisibility(View.VISIBLE); - } - if (getStickyFooterShadow() != null) { - getStickyFooterShadow().setVisibility(View.VISIBLE); - } - - //if we currently show the accountHeader selection list make sure to reset this attr - if (mDrawerBuilder.mAccountHeader != null && mDrawerBuilder.mAccountHeader.mAccountHeaderBuilder != null) { - mDrawerBuilder.mAccountHeader.mAccountHeaderBuilder.mSelectionListShown = false; - } - } - } - - /** - * add the values to the bundle for saveInstanceState - * - * @param savedInstanceState - * @return - */ - public Bundle saveInstanceState(Bundle savedInstanceState) { - if (savedInstanceState != null) { - if (!mDrawerBuilder.mAppended) { - savedInstanceState = mDrawerBuilder.mAdapter.saveInstanceState(savedInstanceState, BUNDLE_SELECTION); - savedInstanceState.putInt(BUNDLE_STICKY_FOOTER_SELECTION, mDrawerBuilder.mCurrentStickyFooterSelection); - savedInstanceState.putBoolean(BUNDLE_DRAWER_CONTENT_SWITCHED, switchedDrawerContent()); - } else { - savedInstanceState = mDrawerBuilder.mAdapter.saveInstanceState(savedInstanceState, BUNDLE_SELECTION_APPENDED); - savedInstanceState.putInt(BUNDLE_STICKY_FOOTER_SELECTION_APPENDED, mDrawerBuilder.mCurrentStickyFooterSelection); - savedInstanceState.putBoolean(BUNDLE_DRAWER_CONTENT_SWITCHED_APPENDED, switchedDrawerContent()); - } - } - return savedInstanceState; - } - - - public interface OnDrawerNavigationListener { - /** - * @param clickedView - * @return true if the event was consumed - */ - boolean onNavigationClickListener(View clickedView); - } - - public interface OnDrawerItemClickListener { - /** - * @param view - * @param position - * @param drawerItem - * @return true if the event was consumed - */ - boolean onItemClick(View view, int position, IDrawerItem drawerItem); - } - - public interface OnDrawerItemLongClickListener { - /** - * @param view - * @param position - * @param drawerItem - * @return true if the event was consumed - */ - boolean onItemLongClick(View view, int position, IDrawerItem drawerItem); - } - - public interface OnDrawerListener { - /** - * @param drawerView - */ - void onDrawerOpened(View drawerView); - - /** - * @param drawerView - */ - void onDrawerClosed(View drawerView); - - /** - * @param drawerView - * @param slideOffset - */ - void onDrawerSlide(View drawerView, float slideOffset); - } - - public interface OnDrawerItemSelectedListener { - /** - * @param parent - * @param view - * @param position - * @param id - * @param drawerItem - */ - void onItemSelected(AdapterView parent, View view, int position, long id, IDrawerItem drawerItem); - - /** - * @param parent - */ - void onNothingSelected(AdapterView parent); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/DrawerBuilder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/DrawerBuilder.java deleted file mode 100644 index 6959ae48..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/DrawerBuilder.java +++ /dev/null @@ -1,1899 +0,0 @@ -package com.mikepenz.materialdrawer; - -import android.app.Activity; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DimenRes; -import androidx.annotation.DrawableRes; -import androidx.annotation.IdRes; -import androidx.annotation.LayoutRes; -import androidx.annotation.MenuRes; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.core.view.GravityCompat; -import androidx.core.view.ViewCompat; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.view.SupportMenuInflater; -import androidx.appcompat.view.menu.MenuBuilder; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.IAdapter; -import com.mikepenz.fastadapter.IAdapterExtension; -import com.mikepenz.fastadapter.IExpandable; -import com.mikepenz.fastadapter.IItemAdapter; -import com.mikepenz.fastadapter.adapters.ItemAdapter; -import com.mikepenz.fastadapter.adapters.ModelAdapter; -import com.mikepenz.fastadapter.expandable.ExpandableExtension; -import com.mikepenz.fastadapter.listeners.OnClickListener; -import com.mikepenz.fastadapter.listeners.OnLongClickListener; -import com.mikepenz.fastadapter.utils.DefaultIdDistributor; -import com.mikepenz.fastadapter.utils.DefaultIdDistributorImpl; -import com.mikepenz.iconics.utils.Utils; -import com.mikepenz.materialdrawer.holder.DimenHolder; -import com.mikepenz.materialdrawer.model.AbstractDrawerItem; -import com.mikepenz.materialdrawer.model.DividerDrawerItem; -import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; -import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.Selectable; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; -import com.mikepenz.materialize.Materialize; -import com.mikepenz.materialize.MaterializeBuilder; -import com.mikepenz.materialize.util.UIUtils; -import com.mikepenz.materialize.view.ScrimInsetsRelativeLayout; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Created by mikepenz on 23.05.15. - */ -public class DrawerBuilder { - - // some internal vars - // variable to check if a builder is only used once - protected boolean mUsed = false; - protected int mCurrentStickyFooterSelection = -1; - protected boolean mAppended = false; - - // the activity to use - protected Activity mActivity; - protected RecyclerView.LayoutManager mLayoutManager; - protected ViewGroup mRootView; - protected Materialize mMaterialize; - public final DefaultIdDistributor idDistributor = new DefaultIdDistributorImpl(); - - /** - * default constructor - */ - public DrawerBuilder() { - getAdapter(); - } - - /** - * Construct a Drawer by passing the activity to use for the generation - * - * @param activity current activity which will contain the drawer - */ - public DrawerBuilder(@NonNull Activity activity) { - this.mRootView = (ViewGroup) activity.findViewById(android.R.id.content); - this.mActivity = activity; - this.mLayoutManager = new LinearLayoutManager(mActivity); - getAdapter(); - } - - /** - * Sets the activity which will be generated for the generation - * The activity is required and will be used to inflate the content in. - * After generation it is set to null to prevent a memory leak. - * - * @param activity current activity which will contain the drawer - */ - public DrawerBuilder withActivity(@NonNull Activity activity) { - this.mRootView = (ViewGroup) activity.findViewById(android.R.id.content); - this.mActivity = activity; - this.mLayoutManager = new LinearLayoutManager(mActivity); - return this; - } - - /** - * Sets the rootView which will host the DrawerLayout - * The content of this view will be extracted and added as the new content inside the drawerLayout - * - * @param rootView a view which will get switched out by the DrawerLayout and added as its child - */ - public DrawerBuilder withRootView(@NonNull ViewGroup rootView) { - this.mRootView = rootView; - - //disable the translucent statusBar we don't need it - withTranslucentStatusBar(false); - - return this; - } - - /** - * Sets the rootView which will host the DrawerLayout - * The content of this view will be extracted and added as the new content inside the drawerLayout - * - * @param rootViewRes the id of a view which will get switched out by the DrawerLayout and added as its child - */ - public DrawerBuilder withRootView(@IdRes int rootViewRes) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - return withRootView((ViewGroup) mActivity.findViewById(rootViewRes)); - } - - // set non translucent statusBar mode - protected boolean mTranslucentStatusBar = true; - - /** - * Sets that the view which hosts the DrawerLayout should have a translucent statusBar - * This is true by default, so it's possible to display the drawer under the statusBar - * - * @param translucentStatusBar sets whether the statusBar is transparent (and the drawer is displayed under it) or not - */ - public DrawerBuilder withTranslucentStatusBar(boolean translucentStatusBar) { - this.mTranslucentStatusBar = translucentStatusBar; - return this; - } - - // set if we want to display the specific Drawer below the statusBar - protected Boolean mDisplayBelowStatusBar; - - /** - * Sets that the slider of this Drawer should be displayed below the statusBar even with a translucentStatusBar - * - * @param displayBelowStatusBar sets wheter the slider of the drawer is displayed below the statusBar or not - */ - public DrawerBuilder withDisplayBelowStatusBar(boolean displayBelowStatusBar) { - this.mDisplayBelowStatusBar = displayBelowStatusBar; - return this; - } - - //defines if we want a inner shadow (used in with the MiniDrawer) - private boolean mInnerShadow = false; - - /** - * sets if the drawer should show an inner shadow or not - * - * @param innerShadow sets wheter the drawer should display an inner shadow or not - * @return - */ - public DrawerBuilder withInnerShadow(boolean innerShadow) { - this.mInnerShadow = innerShadow; - return this; - } - - // the toolbar of the activity - protected Toolbar mToolbar; - - /** - * Sets the toolbar which should be used in combination with the drawer - * This will handle the ActionBarDrawerToggle for you. - * Do not set this if you are in a sub activity and want to handle the back arrow on your own - * - * @param toolbar the toolbar which is used in combination with the drawer - */ - public DrawerBuilder withToolbar(@NonNull Toolbar toolbar) { - this.mToolbar = toolbar; - return this; - } - - // set non translucent NavigationBar mode - protected boolean mTranslucentNavigationBar = false; - - /** - * Set to true if you use a translucent NavigationBar - * - * @param translucentNavigationBar - * @return - */ - public DrawerBuilder withTranslucentNavigationBar(boolean translucentNavigationBar) { - this.mTranslucentNavigationBar = translucentNavigationBar; - - //if we disable the translucentNavigationBar it should be disabled at all - if (!translucentNavigationBar) { - this.mTranslucentNavigationBarProgrammatically = false; - } - - return this; - } - - // set to disable the translucent statusBar Programmatically - protected boolean mTranslucentNavigationBarProgrammatically = false; - - /** - * set this to true if you want a translucent navigation bar. - * - * @param translucentNavigationBarProgrammatically - * @return - */ - public DrawerBuilder withTranslucentNavigationBarProgrammatically(boolean translucentNavigationBarProgrammatically) { - this.mTranslucentNavigationBarProgrammatically = translucentNavigationBarProgrammatically; - //if we enable the programmatically translucent navigationBar we want also the normal navigationBar behavior - if (translucentNavigationBarProgrammatically) { - this.mTranslucentNavigationBar = true; - } - return this; - } - - - // set non translucent NavigationBar mode - protected boolean mFullscreen = false; - - /** - * Set to true if the used theme has a translucent statusBar - * and navigationBar and you want to manage the padding on your own. - * - * @param fullscreen - * @return - */ - public DrawerBuilder withFullscreen(boolean fullscreen) { - this.mFullscreen = fullscreen; - - if (fullscreen) { - withTranslucentStatusBar(true); - withTranslucentNavigationBar(false); - } - - return this; - } - - // set to no systemUI visible mode - protected boolean mSystemUIHidden = false; - - /** - * Set to true if you use your app in complete fullscreen mode - * with hidden statusBar and navigationBar - * - * @param systemUIHidden - * @return - */ - public DrawerBuilder withSystemUIHidden(boolean systemUIHidden) { - this.mSystemUIHidden = systemUIHidden; - - if (systemUIHidden) { - withFullscreen(systemUIHidden); - } - - return this; - } - - - // a custom view to be used instead of everything else - protected View mCustomView; - - /** - * Pass a custom view if you need a completely custom drawer - * content - * - * @param customView - * @return - */ - public DrawerBuilder withCustomView(@NonNull View customView) { - this.mCustomView = customView; - return this; - } - - // the drawerLayout to use - protected DrawerLayout mDrawerLayout; - protected ScrimInsetsRelativeLayout mSliderLayout; - - /** - * Pass a custom DrawerLayout which will be used. - * NOTE: This requires the same structure as the drawer.xml - * - * @param drawerLayout - * @return - */ - public DrawerBuilder withDrawerLayout(@NonNull DrawerLayout drawerLayout) { - this.mDrawerLayout = drawerLayout; - return this; - } - - /** - * Pass a custom DrawerLayout Resource which will be used. - * NOTE: This requires the same structure as the drawer.xml - * - * @param resLayout - * @return - */ - public DrawerBuilder withDrawerLayout(@LayoutRes int resLayout) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - if (resLayout != -1) { - this.mDrawerLayout = (DrawerLayout) mActivity.getLayoutInflater().inflate(resLayout, mRootView, false); - } else { - if (Build.VERSION.SDK_INT < 21) { - this.mDrawerLayout = (DrawerLayout) mActivity.getLayoutInflater().inflate(R.layout.material_drawer_fits_not, mRootView, false); - } else { - this.mDrawerLayout = (DrawerLayout) mActivity.getLayoutInflater().inflate(R.layout.material_drawer, mRootView, false); - } - } - - return this; - } - - //the background color for the slider - protected int mSliderBackgroundColor = 0; - protected int mSliderBackgroundColorRes = -1; - protected Drawable mSliderBackgroundDrawable = null; - protected int mSliderBackgroundDrawableRes = -1; - - /** - * Set the background color for the Slider. - * This is the view containing the list. - * - * @param sliderBackgroundColor - * @return - */ - public DrawerBuilder withSliderBackgroundColor(@ColorInt int sliderBackgroundColor) { - this.mSliderBackgroundColor = sliderBackgroundColor; - return this; - } - - /** - * Set the background color for the Slider from a Resource. - * This is the view containing the list. - * - * @param sliderBackgroundColorRes - * @return - */ - public DrawerBuilder withSliderBackgroundColorRes(@ColorRes int sliderBackgroundColorRes) { - this.mSliderBackgroundColorRes = sliderBackgroundColorRes; - return this; - } - - - /** - * Set the background drawable for the Slider. - * This is the view containing the list. - * - * @param sliderBackgroundDrawable - * @return - */ - public DrawerBuilder withSliderBackgroundDrawable(@NonNull Drawable sliderBackgroundDrawable) { - this.mSliderBackgroundDrawable = sliderBackgroundDrawable; - return this; - } - - - /** - * Set the background drawable for the Slider from a Resource. - * This is the view containing the list. - * - * @param sliderBackgroundDrawableRes - * @return - */ - public DrawerBuilder withSliderBackgroundDrawableRes(@DrawableRes int sliderBackgroundDrawableRes) { - this.mSliderBackgroundDrawableRes = sliderBackgroundDrawableRes; - return this; - } - - //the width of the drawer - protected int mDrawerWidth = -1; - - /** - * Set the DrawerBuilder width with a pixel value - * - * @param drawerWidthPx - * @return - */ - public DrawerBuilder withDrawerWidthPx(int drawerWidthPx) { - this.mDrawerWidth = drawerWidthPx; - return this; - } - - /** - * Set the DrawerBuilder width with a dp value - * - * @param drawerWidthDp - * @return - */ - public DrawerBuilder withDrawerWidthDp(int drawerWidthDp) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - this.mDrawerWidth = Utils.convertDpToPx(mActivity, drawerWidthDp); - return this; - } - - /** - * Set the DrawerBuilder width with a dimension resource - * - * @param drawerWidthRes - * @return - */ - public DrawerBuilder withDrawerWidthRes(@DimenRes int drawerWidthRes) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - this.mDrawerWidth = mActivity.getResources().getDimensionPixelSize(drawerWidthRes); - return this; - } - - //the gravity of the drawer - protected Integer mDrawerGravity = GravityCompat.START; - - /** - * Set the gravity for the drawer. START, LEFT | RIGHT, END - * - * @param gravity - * @return - */ - public DrawerBuilder withDrawerGravity(int gravity) { - this.mDrawerGravity = gravity; - return this; - } - - //the account selection header to use - protected AccountHeader mAccountHeader; - protected boolean mAccountHeaderSticky = false; - - /** - * Add a AccountSwitcherHeader which will be used in this drawer instance. - * NOTE: This will overwrite any set headerView. - * - * @param accountHeader - * @return - */ - public DrawerBuilder withAccountHeader(@NonNull AccountHeader accountHeader) { - return withAccountHeader(accountHeader, false); - } - - /** - * Add a AccountSwitcherHeader which will be used in this drawer instance. Pass true if it should be sticky - * NOTE: This will overwrite any set headerView or stickyHeaderView (depends on the boolean). - * - * @param accountHeader - * @param accountHeaderSticky - * @return - */ - public DrawerBuilder withAccountHeader(@NonNull AccountHeader accountHeader, boolean accountHeaderSticky) { - this.mAccountHeader = accountHeader; - this.mAccountHeaderSticky = accountHeaderSticky; - return this; - } - - // enable/disable the actionBarDrawerToggle animation - protected boolean mAnimateActionBarDrawerToggle = false; - - /** - * Set this to true if you want the ActionBarDrawerToggle to be animated. - * NOTE: This will only work if the built in ActionBarDrawerToggle is used. - * Enable it by setting withActionBarDrawerToggle to true - * - * @param actionBarDrawerToggleAnimated - * @return - */ - public DrawerBuilder withActionBarDrawerToggleAnimated(boolean actionBarDrawerToggleAnimated) { - this.mAnimateActionBarDrawerToggle = actionBarDrawerToggleAnimated; - return this; - } - - - // enable the drawer toggle / if withActionBarDrawerToggle we will autoGenerate it - protected boolean mActionBarDrawerToggleEnabled = true; - - /** - * Set this to false if you don't need the included ActionBarDrawerToggle - * - * @param actionBarDrawerToggleEnabled - * @return - */ - public DrawerBuilder withActionBarDrawerToggle(boolean actionBarDrawerToggleEnabled) { - this.mActionBarDrawerToggleEnabled = actionBarDrawerToggleEnabled; - return this; - } - - // drawer toggle - protected ActionBarDrawerToggle mActionBarDrawerToggle; - - /** - * Add a custom ActionBarDrawerToggle which will be used in combination with this drawer. - * - * @param actionBarDrawerToggle - * @return - */ - public DrawerBuilder withActionBarDrawerToggle(@NonNull ActionBarDrawerToggle actionBarDrawerToggle) { - this.mActionBarDrawerToggleEnabled = true; - this.mActionBarDrawerToggle = actionBarDrawerToggle; - return this; - } - - // defines if the drawer should scroll to top after click - protected boolean mScrollToTopAfterClick = false; - - /** - * defines if the drawer should scroll to top after click - * - * @param scrollToTopAfterClick - * @return - */ - public DrawerBuilder withScrollToTopAfterClick(boolean scrollToTopAfterClick) { - this.mScrollToTopAfterClick = scrollToTopAfterClick; - return this; - } - - - // header view - protected View mHeaderView; - protected boolean mHeaderDivider = true; - protected boolean mHeaderPadding = true; - protected DimenHolder mHeiderHeight = null; - - /** - * Add a header to the DrawerBuilder ListView. This can be any view - * - * @param headerView - * @return - */ - public DrawerBuilder withHeader(@NonNull View headerView) { - this.mHeaderView = headerView; - return this; - } - - /** - * Add a header to the DrawerBuilder ListView defined by a resource. - * - * @param headerViewRes - * @return - */ - public DrawerBuilder withHeader(@LayoutRes int headerViewRes) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - if (headerViewRes != -1) { - //i know there should be a root, bit i got none here - this.mHeaderView = mActivity.getLayoutInflater().inflate(headerViewRes, null, false); - } - - return this; - } - - /** - * Set this to false if you don't need the divider below the header - * - * @param headerDivider - * @return - */ - public DrawerBuilder withHeaderDivider(boolean headerDivider) { - this.mHeaderDivider = headerDivider; - return this; - } - - /** - * Set this to false if you don't need the padding below the header - * - * @param headerPadding - * @return - */ - public DrawerBuilder withHeaderPadding(boolean headerPadding) { - this.mHeaderPadding = headerPadding; - return this; - } - - /** - * Sets the header height for the header provided via `withHeader()` - * - * @param headerHeight the DimenHolder with the height we want to set for the header - * @return - */ - public DrawerBuilder withHeaderHeight(DimenHolder headerHeight) { - this.mHeiderHeight = headerHeight; - return this; - } - - // sticky view - protected View mStickyHeaderView; - // shadow shown on the top of the sticky header - protected boolean mStickyHeaderShadow = true; - - /** - * Add a sticky header below the DrawerBuilder ListView. This can be any view - * - * @param stickyHeader - * @return - */ - public DrawerBuilder withStickyHeader(@NonNull View stickyHeader) { - this.mStickyHeaderView = stickyHeader; - return this; - } - - /** - * Add a sticky header below the DrawerBuilder ListView defined by a resource. - * - * @param stickyHeaderRes - * @return - */ - public DrawerBuilder withStickyHeader(@LayoutRes int stickyHeaderRes) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - if (stickyHeaderRes != -1) { - //i know there should be a root, bit i got none here - this.mStickyHeaderView = mActivity.getLayoutInflater().inflate(stickyHeaderRes, null, false); - } - - return this; - } - - /** - * Set this to false if you don't want the shadow below the sticky header - * - * @param stickyHeaderShadow - * @return - */ - public DrawerBuilder withStickyHeaderShadow(boolean stickyHeaderShadow) { - this.mStickyHeaderShadow = stickyHeaderShadow; - return this; - } - - // footer view - protected View mFooterView; - protected boolean mFooterDivider = true; - protected boolean mFooterClickable = false; - - /** - * Add a footer to the DrawerBuilder ListView. This can be any view - * - * @param footerView - * @return - */ - public DrawerBuilder withFooter(@NonNull View footerView) { - this.mFooterView = footerView; - return this; - } - - /** - * Add a footer to the DrawerBuilder ListView defined by a resource. - * - * @param footerViewRes - * @return - */ - public DrawerBuilder withFooter(@LayoutRes int footerViewRes) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - if (footerViewRes != -1) { - //i know there should be a root, bit i got none here - this.mFooterView = mActivity.getLayoutInflater().inflate(footerViewRes, null, false); - } - - return this; - } - - /** - * Set this to true if you want the footer to be clickable - * - * @param footerClickable - * @return - */ - public DrawerBuilder withFooterClickable(boolean footerClickable) { - this.mFooterClickable = footerClickable; - return this; - } - - /** - * Set this to false if you don't need the divider above the footer - * - * @param footerDivider - * @return - */ - public DrawerBuilder withFooterDivider(boolean footerDivider) { - this.mFooterDivider = footerDivider; - return this; - } - - // sticky view - protected ViewGroup mStickyFooterView; - // divider shown on top of the sticky footer - protected boolean mStickyFooterDivider = false; - // sticky view - protected View mStickyFooterShadowView; - // shadow shown on the top of the sticky footer - protected boolean mStickyFooterShadow = true; - - /** - * Add a sticky footer below the DrawerBuilder ListView. This can be any view - * - * @param stickyFooter - * @return - */ - public DrawerBuilder withStickyFooter(@NonNull ViewGroup stickyFooter) { - this.mStickyFooterView = stickyFooter; - return this; - } - - /** - * Add a sticky footer below the DrawerBuilder ListView defined by a resource. - * - * @param stickyFooterRes - * @return - */ - public DrawerBuilder withStickyFooter(@LayoutRes int stickyFooterRes) { - if (mActivity == null) { - throw new RuntimeException("please pass an activity first to use this call"); - } - - if (stickyFooterRes != -1) { - //i know there should be a root, bit i got none here - this.mStickyFooterView = (ViewGroup) mActivity.getLayoutInflater().inflate(stickyFooterRes, null, false); - } - - return this; - } - - /** - * Set this to true if you want the divider above the sticky footer - * - * @param stickyFooterDivider - * @return - */ - public DrawerBuilder withStickyFooterDivider(boolean stickyFooterDivider) { - this.mStickyFooterDivider = stickyFooterDivider; - return this; - } - - /** - * Set this to false if you don't want the shadow on top of the sticky footer - * - * @param stickyFooterShadow - * @return - */ - public DrawerBuilder withStickyFooterShadow(boolean stickyFooterShadow) { - this.mStickyFooterShadow = stickyFooterShadow; - return this; - } - - // fire onClick after build - protected boolean mFireInitialOnClick = false; - - /** - * Set this to true if you love to get an initial onClick event after the build method is called - * - * @param fireOnInitialOnClick - * @return - */ - public DrawerBuilder withFireOnInitialOnClick(boolean fireOnInitialOnClick) { - this.mFireInitialOnClick = fireOnInitialOnClick; - return this; - } - - // if multiSelection is possible - protected boolean mMultiSelect = false; - - /** - * set this to true if you want to enable multiSelect mode inside the drawer. Note - * you will have to programmatically deselect if you want to remove all selections! - * You can disable this at a later time via .getAdapter().withMultiSelect(false) - * You can also modify all other settings of the FastAdapter via this method - * - * @param multiSelect true if multiSelect is enabled (default: false) - * @return this - */ - public DrawerBuilder withMultiSelect(boolean multiSelect) { - this.mMultiSelect = multiSelect; - return this; - } - - // item to select - protected int mSelectedItemPosition = 0; - - /** - * Set this to the index of the item, you would love to select upon start - * - * @param selectedItemPosition - * @return - */ - public DrawerBuilder withSelectedItemByPosition(int selectedItemPosition) { - this.mSelectedItemPosition = selectedItemPosition; - return this; - } - - // item to select - protected long mSelectedItemIdentifier = 0; - - /** - * Set this to the identifier of the item, you would love to select upon start - * - * @param selectedItemIdentifier - * @return - */ - public DrawerBuilder withSelectedItem(long selectedItemIdentifier) { - this.mSelectedItemIdentifier = selectedItemIdentifier; - return this; - } - - // an RecyclerView to use within the drawer :D - protected RecyclerView mRecyclerView; - - /** - * Define a custom RecyclerView which will be used in the drawer - * NOTE: this is not recommended - * - * @param recyclerView - * @return - */ - public DrawerBuilder withRecyclerView(@NonNull RecyclerView recyclerView) { - this.mRecyclerView = recyclerView; - return this; - } - - // if the adapter should enable hasStableIds to improve performance and allow animations - protected boolean mHasStableIds = false; - - /** - * define this if you want enable hasStableIds for the adapter which is generated. - * WARNING: only use this if you have set an identifer for all of your items else this could cause - * many weird things - * - * @param hasStableIds - * @return - */ - public DrawerBuilder withHasStableIds(boolean hasStableIds) { - this.mHasStableIds = hasStableIds; - if (mAdapter != null) { - mAdapter.setHasStableIds(hasStableIds); - } - return this; - } - - // an adapter to use for the list - protected FastAdapter mAdapter; - protected ModelAdapter mHeaderAdapter = new ItemAdapter<>().withIdDistributor(idDistributor); - protected ModelAdapter mItemAdapter = new ItemAdapter<>().withIdDistributor(idDistributor); - protected ModelAdapter mFooterAdapter = new ItemAdapter<>().withIdDistributor(idDistributor); - protected ExpandableExtension mExpandableExtension = new ExpandableExtension<>(); - - /** - * Define a custom Adapter which will be used in the drawer - * NOTE: this is not recommender - * WARNING: if you do this after adding items you will loose those! - * - * @param adapter the FastAdapter to use with this drawer - * @return this - */ - public DrawerBuilder withAdapter(@NonNull FastAdapter adapter) { - this.mAdapter = adapter; - //we have to rewrap as a different FastAdapter was provided - adapter.addAdapter(0, mHeaderAdapter); - adapter.addAdapter(1, mItemAdapter); - adapter.addAdapter(2, mFooterAdapter); - adapter.addExtension(mExpandableExtension); - return this; - } - - /** - * get the adapter (null safe) - * - * @return the FastAdapter used with this drawer - */ - protected FastAdapter getAdapter() { - if (mAdapter == null) { - mAdapter = FastAdapter.with(Arrays.asList(mHeaderAdapter, mItemAdapter, mFooterAdapter), Arrays.>asList(mExpandableExtension)); - mAdapter.withSelectable(true); - mAdapter.withMultiSelect(false); - mAdapter.withAllowDeselection(false); - mAdapter.setHasStableIds(mHasStableIds); - } - return mAdapter; - } - - protected IItemAdapter getItemAdapter() { - return mItemAdapter; - } - - protected IItemAdapter getHeaderAdapter() { - return mHeaderAdapter; - } - - protected IItemAdapter getFooterAdapter() { - return mFooterAdapter; - } - - // Defines a Adapter which wraps the main Adapter used in the RecyclerView to allow extended navigation and other stuff - protected RecyclerView.Adapter mAdapterWrapper; - - /** - * Defines a Adapter which wraps the main Adapter used in the RecyclerView to allow extended navigation and other stuff - * - * @param adapterWrapper - * @return - */ - public DrawerBuilder withAdapterWrapper(@NonNull RecyclerView.Adapter adapterWrapper) { - if (mAdapter == null) { - throw new RuntimeException("this adapter has to be set in conjunction to a normal adapter which is used inside this wrapper adapter"); - } - this.mAdapterWrapper = adapterWrapper; - return this; - } - - - //defines the itemAnimator to be used in conjunction with the RecyclerView - protected RecyclerView.ItemAnimator mItemAnimator = new DefaultItemAnimator(); - - /** - * defines the itemAnimator to be used in conjunction with the RecyclerView - * - * @param itemAnimator - * @return - */ - public DrawerBuilder withItemAnimator(RecyclerView.ItemAnimator itemAnimator) { - mItemAnimator = itemAnimator; - return this; - } - - /** - * Set the initial List of IDrawerItems for the Drawer - * - * @param drawerItems - * @return - */ - public DrawerBuilder withDrawerItems(@NonNull List drawerItems) { - this.getItemAdapter().set(drawerItems); - return this; - } - - /** - * Add a initial DrawerItem or a DrawerItem Array for the Drawer - * - * @param drawerItems - * @return - */ - public DrawerBuilder addDrawerItems(@NonNull IDrawerItem... drawerItems) { - this.getItemAdapter().add(drawerItems); - return this; - } - - // defines if we want to keep the sticky items visible, upon switching to the profiles - protected boolean mKeepStickyItemsVisible = false; - - /** - * Toggles if the sticky footer should stay visible upon switching to the profile list - * **WARNING** using this with stickyDrawerItems can lead to the selection not being updated correctly. Use with care - * - * @param keepStickyItemsVisible true if the sticky footer should stay visible - * @return this - */ - public DrawerBuilder withKeepStickyItemsVisible(boolean keepStickyItemsVisible) { - this.mKeepStickyItemsVisible = keepStickyItemsVisible; - return this; - } - - // always visible list in drawer - protected List mStickyDrawerItems = new ArrayList<>(); - - /** - * Set the initial List of IDrawerItems for the StickyDrawerFooter - * - * @param stickyDrawerItems - * @return - */ - public DrawerBuilder withStickyDrawerItems(@NonNull List stickyDrawerItems) { - this.mStickyDrawerItems = stickyDrawerItems; - return this; - } - - /** - * Add a initial DrawerItem or a DrawerItem Array for the StickyDrawerFooter - * - * @param stickyDrawerItems - * @return - */ - public DrawerBuilder addStickyDrawerItems(@NonNull IDrawerItem... stickyDrawerItems) { - if (this.mStickyDrawerItems == null) { - this.mStickyDrawerItems = new ArrayList<>(); - } - - Collections.addAll(this.mStickyDrawerItems, stickyDrawerItems); - - return this; - } - - /** - * Inflates the DrawerItems from a menu.xml - * - * @param menuRes - * @return - */ - public DrawerBuilder inflateMenu(@MenuRes int menuRes) { - MenuInflater menuInflater = new SupportMenuInflater(mActivity); - MenuBuilder mMenu = new MenuBuilder(mActivity); - - menuInflater.inflate(menuRes, mMenu); - - addMenuItems(mMenu, false); - - return this; - } - - /** - * helper method to init the drawerItems from a menu - * - * @param mMenu - * @param subMenu - */ - private void addMenuItems(Menu mMenu, boolean subMenu) { - int groupId = R.id.material_drawer_menu_default_group; - for (int i = 0; i < mMenu.size(); i++) { - MenuItem mMenuItem = mMenu.getItem(i); - IDrawerItem iDrawerItem; - if (!subMenu && mMenuItem.getGroupId() != groupId && mMenuItem.getGroupId() != 0) { - groupId = mMenuItem.getGroupId(); - iDrawerItem = new DividerDrawerItem(); - getItemAdapter().add(iDrawerItem); - } - if (mMenuItem.hasSubMenu()) { - iDrawerItem = new PrimaryDrawerItem() - .withName(mMenuItem.getTitle().toString()) - .withIcon(mMenuItem.getIcon()) - .withIdentifier(mMenuItem.getItemId()) - .withEnabled(mMenuItem.isEnabled()) - .withSelectable(false); - getItemAdapter().add(iDrawerItem); - addMenuItems(mMenuItem.getSubMenu(), true); - } else if (mMenuItem.getGroupId() != 0 || subMenu) { - iDrawerItem = new SecondaryDrawerItem() - .withName(mMenuItem.getTitle().toString()) - .withIcon(mMenuItem.getIcon()) - .withIdentifier(mMenuItem.getItemId()) - .withEnabled(mMenuItem.isEnabled()); - getItemAdapter().add(iDrawerItem); - } else { - iDrawerItem = new PrimaryDrawerItem() - .withName(mMenuItem.getTitle().toString()) - .withIcon(mMenuItem.getIcon()) - .withIdentifier(mMenuItem.getItemId()) - .withEnabled(mMenuItem.isEnabled()); - getItemAdapter().add(iDrawerItem); - } - } - } - - // close drawer on click - protected boolean mCloseOnClick = true; - - /** - * Set this to false if the drawer should stay opened after an item was clicked - * - * @param closeOnClick - * @return this - */ - public DrawerBuilder withCloseOnClick(boolean closeOnClick) { - this.mCloseOnClick = closeOnClick; - return this; - } - - // delay drawer close to prevent lag - protected int mDelayOnDrawerClose = 50; - - /** - * Define the delay for the drawer close operation after a click. - * This is a small trick to improve the speed (and remove lag) if you open a new activity after a DrawerItem - * was selected. - * NOTE: Disable this by passing -1 - * - * @param delayOnDrawerClose the delay in MS (-1 to disable) - * @return this - */ - public DrawerBuilder withDelayOnDrawerClose(int delayOnDrawerClose) { - this.mDelayOnDrawerClose = delayOnDrawerClose; - return this; - } - - // delay drawer click event to prevent lag (you should either choose DelayOnDrawerClose or this) - protected int mDelayDrawerClickEvent = 0; - - /** - * Define the delay for the drawer click event after a click. - * This can be used to improve performance and prevent lag, especially when you switch fragments inside the listener. - * This will ignore the boolean value you can return in the listener, as the listener is called after the drawer was closed. - * NOTE: Disable this to pass -1 - * - * @param delayDrawerClickEvent -1 to disable - * @return this - */ - public DrawerBuilder withDelayDrawerClickEvent(int delayDrawerClickEvent) { - this.mDelayDrawerClickEvent = delayDrawerClickEvent; - return this; - } - - // onDrawerListener - protected Drawer.OnDrawerListener mOnDrawerListener; - - /** - * Define a OnDrawerListener for this Drawer - * - * @param onDrawerListener - * @return this - */ - public DrawerBuilder withOnDrawerListener(@NonNull Drawer.OnDrawerListener onDrawerListener) { - this.mOnDrawerListener = onDrawerListener; - return this; - } - - // onDrawerItemClickListeners - protected Drawer.OnDrawerItemClickListener mOnDrawerItemClickListener; - - /** - * Define a OnDrawerItemClickListener for this Drawer - * - * @param onDrawerItemClickListener - * @return - */ - public DrawerBuilder withOnDrawerItemClickListener(@NonNull Drawer.OnDrawerItemClickListener onDrawerItemClickListener) { - this.mOnDrawerItemClickListener = onDrawerItemClickListener; - return this; - } - - // onDrawerItemClickListeners - protected Drawer.OnDrawerItemLongClickListener mOnDrawerItemLongClickListener; - - /** - * Define a OnDrawerItemLongClickListener for this Drawer - * - * @param onDrawerItemLongClickListener - * @return - */ - public DrawerBuilder withOnDrawerItemLongClickListener(@NonNull Drawer.OnDrawerItemLongClickListener onDrawerItemLongClickListener) { - this.mOnDrawerItemLongClickListener = onDrawerItemLongClickListener; - return this; - } - - // onDrawerListener - protected Drawer.OnDrawerNavigationListener mOnDrawerNavigationListener; - - /** - * Define a OnDrawerNavigationListener for this Drawer - * - * @param onDrawerNavigationListener - * @return this - */ - public DrawerBuilder withOnDrawerNavigationListener(@NonNull Drawer.OnDrawerNavigationListener onDrawerNavigationListener) { - this.mOnDrawerNavigationListener = onDrawerNavigationListener; - return this; - } - - //show the drawer on the first launch to show the user its there - protected boolean mShowDrawerOnFirstLaunch = false; - - /** - * define if the DrawerBuilder is shown on the first launch - * - * @param showDrawerOnFirstLaunch - * @return - */ - public DrawerBuilder withShowDrawerOnFirstLaunch(boolean showDrawerOnFirstLaunch) { - this.mShowDrawerOnFirstLaunch = showDrawerOnFirstLaunch; - return this; - } - - //show the drawer on launch to show the user its there, keep doing it until the user has dragged it open once - protected boolean mShowDrawerUntilDraggedOpened = false; - - /** - * define if the DrawerBuilder is shown until the user has dragged it open once - * - * @param showDrawerUntilDraggedOpened - * @return DrawerBuilder - */ - public DrawerBuilder withShowDrawerUntilDraggedOpened(boolean showDrawerUntilDraggedOpened) { - mShowDrawerUntilDraggedOpened = showDrawerUntilDraggedOpened; - return this; - } - - //also generate the MiniDrawer for this Drawer - protected boolean mGenerateMiniDrawer = false; - protected MiniDrawer mMiniDrawer = null; - - /** - * define if the DrawerBuilder should also generate a MiniDrawer for th - * - * @param generateMiniDrawer - * @return - */ - public DrawerBuilder withGenerateMiniDrawer(boolean generateMiniDrawer) { - this.mGenerateMiniDrawer = generateMiniDrawer; - return this; - } - - - // savedInstance to restore state - protected Bundle mSavedInstance; - - /** - * Set the Bundle (savedInstance) which is passed by the activity. - * No need to null-check everything is handled automatically - * - * @param savedInstance - * @return - */ - public DrawerBuilder withSavedInstance(Bundle savedInstance) { - this.mSavedInstance = savedInstance; - return this; - } - - // shared preferences to use for integrated functions - protected SharedPreferences mSharedPreferences; - - /** - * Set the {@link SharedPreferences} to use for the `showDrawerOnFirstLaunch` or the `ShowDrawerUntilDraggedOpened` - * - * @param sharedPreferences SharedPreference to use - * @return this - */ - public DrawerBuilder withSharedPreferences(SharedPreferences sharedPreferences) { - this.mSharedPreferences = sharedPreferences; - return this; - } - - /** - * helper method to handle when the drawer should be shown on launch - */ - private void handleShowOnLaunch() { - //check if it should be shown on launch (and we have a drawerLayout) - if (mActivity != null && mDrawerLayout != null) { - if (mShowDrawerOnFirstLaunch || mShowDrawerUntilDraggedOpened) { - final SharedPreferences preferences = mSharedPreferences != null ? mSharedPreferences : PreferenceManager.getDefaultSharedPreferences(mActivity); - - if (mShowDrawerOnFirstLaunch && !preferences.getBoolean(Drawer.PREF_USER_LEARNED_DRAWER, false)) { - //if it was not shown yet - //open the drawer - mDrawerLayout.openDrawer(mSliderLayout); - - //save that it showed up once ;) - SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean(Drawer.PREF_USER_LEARNED_DRAWER, true); - editor.apply(); - - } else if (mShowDrawerUntilDraggedOpened && !preferences.getBoolean(Drawer.PREF_USER_OPENED_DRAWER_BY_DRAGGING, false)) { - // open the drawer since the user has not dragged it open yet - mDrawerLayout.openDrawer(mSliderLayout); - - // add a listener to detect dragging - mDrawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { - boolean hasBeenDragged = false; - - @Override - public void onDrawerStateChanged(int newState) { - if (newState == DrawerLayout.STATE_DRAGGING) { - // save that the user was dragging - hasBeenDragged = true; - - } else if (newState == DrawerLayout.STATE_IDLE) { - // check if the user was dragging and if that resulted in an open drawer - if (hasBeenDragged && mDrawerLayout.isDrawerOpen(mDrawerGravity)) { - // Save that the user has dragged it open - SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean(Drawer.PREF_USER_OPENED_DRAWER_BY_DRAGGING, true); - editor.apply(); - } else { - // reset the drag boolean - hasBeenDragged = false; - } - } - } - }); - } - } - } - } - - /** - * Build and add the DrawerBuilder to your activity - * - * @return - */ - public Drawer build() { - if (mUsed) { - throw new RuntimeException("you must not reuse a DrawerBuilder builder"); - } - if (mActivity == null) { - throw new RuntimeException("please pass an activity"); - } - - //set that this builder was used. now you have to create a new one - mUsed = true; - - // if the user has not set a drawerLayout use the default one :D - if (mDrawerLayout == null) { - withDrawerLayout(-1); - } - - //some new Materialize magic ;) - mMaterialize = new MaterializeBuilder() - .withActivity(mActivity) - .withRootView(mRootView) - .withFullscreen(mFullscreen) - .withSystemUIHidden(mSystemUIHidden) - .withUseScrimInsetsLayout(false) - .withTransparentStatusBar(mTranslucentStatusBar) - .withTranslucentNavigationBarProgrammatically(mTranslucentNavigationBarProgrammatically) - .withContainer(mDrawerLayout) - .build(); - - //handle the navigation stuff of the ActionBarDrawerToggle and the drawer in general - handleDrawerNavigation(mActivity, false); - - //build the view which will be set to the drawer - Drawer result = buildView(); - - //define id for the sliderLayout - mSliderLayout.setId(R.id.material_drawer_slider_layout); - // add the slider to the drawer - mDrawerLayout.addView(mSliderLayout, 1); - - return result; - } - - /** - * Build and add the DrawerBuilder to your activity - * - * @return - */ - public Drawer buildForFragment() { - if (mUsed) { - throw new RuntimeException("you must not reuse a DrawerBuilder builder"); - } - if (mActivity == null) { - throw new RuntimeException("please pass an activity"); - } - if (mRootView == null) { - throw new RuntimeException("please pass the view which should host the DrawerLayout"); - } - - //set that this builder was used. now you have to create a new one - mUsed = true; - - // if the user has not set a drawerLayout use the default one :D - if (mDrawerLayout == null) { - withDrawerLayout(-1); - } - - //set the drawer here... - - View originalContentView = mRootView.getChildAt(0); - - boolean alreadyInflated = originalContentView.getId() == R.id.materialize_root; - - //only add the new layout if it wasn't done before - if (!alreadyInflated) { - // remove the contentView - mRootView.removeView(originalContentView); - } else { - //if it was already inflated we have to clean up again - mRootView.removeAllViews(); - } - - //create the layoutParams to use for the contentView - FrameLayout.LayoutParams layoutParamsContentView = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ); - - //add the drawer - mRootView.addView(mDrawerLayout, layoutParamsContentView); - - //set the id so we can check if it was already inflated - mDrawerLayout.setId(R.id.materialize_root); - - //handle the navigation stuff of the ActionBarDrawerToggle and the drawer in general - handleDrawerNavigation(mActivity, false); - - //build the view which will be set to the drawer - Drawer result = buildView(); - - // add the slider to the drawer - mDrawerLayout.addView(originalContentView, 0); - - //define id for the sliderLayout - mSliderLayout.setId(R.id.material_drawer_slider_layout); - // add the slider to the drawer - mDrawerLayout.addView(mSliderLayout, 1); - - return result; - } - - /** - * handles the different logics for the Drawer Navigation Listeners / Indications (ActionBarDrawertoggle) - */ - protected void handleDrawerNavigation(Activity activity, boolean recreateActionBarDrawerToggle) { - //set the navigationOnClickListener - final View.OnClickListener toolbarNavigationListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - boolean handled = false; - - if (mOnDrawerNavigationListener != null && (mActionBarDrawerToggle != null && !mActionBarDrawerToggle.isDrawerIndicatorEnabled())) { - handled = mOnDrawerNavigationListener.onNavigationClickListener(v); - } - if (!handled) { - if (mDrawerLayout.isDrawerOpen(mDrawerGravity)) { - mDrawerLayout.closeDrawer(mDrawerGravity); - } else { - mDrawerLayout.openDrawer(mDrawerGravity); - } - } - } - }; - - if (recreateActionBarDrawerToggle) { - mActionBarDrawerToggle = null; - } - - // create the ActionBarDrawerToggle if not set and enabled and if we have a toolbar - if (mActionBarDrawerToggleEnabled && mActionBarDrawerToggle == null && mToolbar != null) { - this.mActionBarDrawerToggle = new ActionBarDrawerToggle(activity, mDrawerLayout, mToolbar, R.string.material_drawer_open, R.string.material_drawer_close) { - @Override - public void onDrawerOpened(View drawerView) { - if (mOnDrawerListener != null) { - mOnDrawerListener.onDrawerOpened(drawerView); - } - super.onDrawerOpened(drawerView); - } - - @Override - public void onDrawerClosed(View drawerView) { - if (mOnDrawerListener != null) { - mOnDrawerListener.onDrawerClosed(drawerView); - } - super.onDrawerClosed(drawerView); - } - - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - if (mOnDrawerListener != null) { - mOnDrawerListener.onDrawerSlide(drawerView, slideOffset); - } - - if (!mAnimateActionBarDrawerToggle) { - super.onDrawerSlide(drawerView, 0); - } else { - super.onDrawerSlide(drawerView, slideOffset); - } - } - }; - this.mActionBarDrawerToggle.syncState(); - } - - //if we got a toolbar set a toolbarNavigationListener - //we also have to do this after setting the ActionBarDrawerToggle as this will overwrite this - if (mToolbar != null) { - this.mToolbar.setNavigationOnClickListener(toolbarNavigationListener); - } - - //handle the ActionBarDrawerToggle - if (mActionBarDrawerToggle != null) { - mActionBarDrawerToggle.setToolbarNavigationClickListener(toolbarNavigationListener); - mDrawerLayout.addDrawerListener(mActionBarDrawerToggle); - } else { - mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - if (mOnDrawerListener != null) { - mOnDrawerListener.onDrawerSlide(drawerView, slideOffset); - } - } - - @Override - public void onDrawerOpened(View drawerView) { - if (mOnDrawerListener != null) { - mOnDrawerListener.onDrawerOpened(drawerView); - } - } - - @Override - public void onDrawerClosed(View drawerView) { - if (mOnDrawerListener != null) { - mOnDrawerListener.onDrawerClosed(drawerView); - } - } - - @Override - public void onDrawerStateChanged(int newState) { - - } - }); - } - } - - /** - * build the drawers content only. This will still return a Result object, but only with the content set. No inflating of a DrawerLayout. - * - * @return Result object with only the content set - */ - public Drawer buildView() { - // get the slider view - mSliderLayout = (ScrimInsetsRelativeLayout) mActivity.getLayoutInflater().inflate(R.layout.material_drawer_slider, mDrawerLayout, false); - mSliderLayout.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(mActivity, R.attr.material_drawer_background, R.color.material_drawer_background)); - // get the layout params - DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) mSliderLayout.getLayoutParams(); - if (params != null) { - // if we've set a custom gravity set it - params.gravity = mDrawerGravity; - // if this is a drawer from the right, change the margins :D - params = DrawerUtils.processDrawerLayoutParams(this, params); - // set the new layout params - mSliderLayout.setLayoutParams(params); - } - - //create the content - createContent(); - - //create the result object - Drawer result = new Drawer(this); - //set the drawer for the accountHeader if set - if (mAccountHeader != null) { - mAccountHeader.setDrawer(result); - } - - //toggle selection list if we were previously on the account list - if (mSavedInstance != null && mSavedInstance.getBoolean(Drawer.BUNDLE_DRAWER_CONTENT_SWITCHED, false)) { - mAccountHeader.toggleSelectionList(mActivity); - } - - //handle if the drawer should be shown on launch - handleShowOnLaunch(); - - //we only want to hook a Drawer to the MiniDrawer if it is the main drawer, not the appended one - if (!mAppended && mGenerateMiniDrawer) { - // if we should create a MiniDrawer we have to do this now - mMiniDrawer = new MiniDrawer().withDrawer(result).withAccountHeader(mAccountHeader); - } - - //forget the reference to the activity - mActivity = null; - - return result; - } - - /** - * Call this method to append a new DrawerBuilder to a existing Drawer. - * - * @param result the Drawer.Result of an existing Drawer - * @return - */ - public Drawer append(@NonNull Drawer result) { - if (mUsed) { - throw new RuntimeException("you must not reuse a DrawerBuilder builder"); - } - if (mDrawerGravity == null) { - throw new RuntimeException("please set the gravity for the drawer"); - } - - //set that this builder was used. now you have to create a new one - mUsed = true; - mAppended = true; - - //get the drawer layout from the previous drawer - mDrawerLayout = result.getDrawerLayout(); - - // get the slider view - mSliderLayout = (ScrimInsetsRelativeLayout) mActivity.getLayoutInflater().inflate(R.layout.material_drawer_slider, mDrawerLayout, false); - mSliderLayout.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(mActivity, R.attr.material_drawer_background, R.color.material_drawer_background)); - // get the layout params - DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) mSliderLayout.getLayoutParams(); - // set the gravity of this drawerGravity - params.gravity = mDrawerGravity; - // if this is a drawer from the right, change the margins :D - params = DrawerUtils.processDrawerLayoutParams(this, params); - // set the new params - mSliderLayout.setLayoutParams(params); - //define id for the sliderLayout - mSliderLayout.setId(R.id.material_drawer_slider_layout); - // add the slider to the drawer - mDrawerLayout.addView(mSliderLayout, 1); - - //create the content - createContent(); - - //create the result object - Drawer appendedResult = new Drawer(this); - - //toggle selection list if we were previously on the account list - if (mSavedInstance != null && mSavedInstance.getBoolean(Drawer.BUNDLE_DRAWER_CONTENT_SWITCHED_APPENDED, false)) { - mAccountHeader.toggleSelectionList(mActivity); - } - - //forget the reference to the activity - mActivity = null; - - return appendedResult; - } - - /** - * the helper method to create the content for the drawer - */ - private void createContent() { - //if we have a customView use this - if (mCustomView != null) { - LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ); - contentParams.weight = 1f; - mSliderLayout.addView(mCustomView, contentParams); - return; - } - - //set the shadow for the drawer - if (Build.VERSION.SDK_INT < 21 && mDrawerLayout != null) { - if (ViewCompat.getLayoutDirection(mRootView) == ViewCompat.LAYOUT_DIRECTION_LTR) { - mDrawerLayout.setDrawerShadow(mDrawerGravity == GravityCompat.START ? R.drawable.material_drawer_shadow_right : R.drawable.material_drawer_shadow_left, mDrawerGravity); - } else { - mDrawerLayout.setDrawerShadow(mDrawerGravity == GravityCompat.START ? R.drawable.material_drawer_shadow_left : R.drawable.material_drawer_shadow_right, mDrawerGravity); - } - } - - // if we have an adapter (either by defining a custom one or the included one add a list :D - View contentView; - if (mRecyclerView == null) { - contentView = LayoutInflater.from(mActivity).inflate(R.layout.material_drawer_recycler_view, mSliderLayout, false); - mRecyclerView = (RecyclerView) contentView.findViewById(R.id.material_drawer_recycler_view); - //set the itemAnimator - mRecyclerView.setItemAnimator(mItemAnimator); - //some style improvements on older devices - mRecyclerView.setFadingEdgeLength(0); - - //set the drawing cache background to the same color as the slider to improve performance - //mRecyclerView.setDrawingCacheBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(mActivity, R.attr.material_drawer_background, R.color.material_drawer_background)); - mRecyclerView.setClipToPadding(false); - //additional stuff - mRecyclerView.setLayoutManager(mLayoutManager); - - int paddingTop = 0; - if ((mDisplayBelowStatusBar == null || mDisplayBelowStatusBar) && !mSystemUIHidden) { - paddingTop = UIUtils.getStatusBarHeight(mActivity); - } - int paddingBottom = 0; - int orientation = mActivity.getResources().getConfiguration().orientation; - if (((mTranslucentNavigationBar || mFullscreen) && Build.VERSION.SDK_INT >= 21) && !mSystemUIHidden - && (orientation == Configuration.ORIENTATION_PORTRAIT - || (orientation == Configuration.ORIENTATION_LANDSCAPE - && DrawerUIUtils.isSystemBarOnBottom(mActivity)))) { - paddingBottom = UIUtils.getNavigationBarHeight(mActivity); - } - - mRecyclerView.setPadding(0, paddingTop, 0, paddingBottom); - } else { - contentView = mRecyclerView; - } - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ); - params.weight = 1f; - mSliderLayout.addView(contentView, params); - - if (mInnerShadow) { - View innerShadow = mSliderLayout.findViewById(R.id.material_drawer_inner_shadow); - innerShadow.setVisibility(View.VISIBLE); - innerShadow.bringToFront(); - if (mDrawerGravity == GravityCompat.START) { - innerShadow.setBackgroundResource(R.drawable.material_drawer_shadow_left); - } else { - innerShadow.setBackgroundResource(R.drawable.material_drawer_shadow_right); - } - } - - // set the background - if (mSliderBackgroundColor != 0) { - mSliderLayout.setBackgroundColor(mSliderBackgroundColor); - } else if (mSliderBackgroundColorRes != -1) { - mSliderLayout.setBackgroundColor(ContextCompat.getColor(mActivity, mSliderBackgroundColorRes)); - } else if (mSliderBackgroundDrawable != null) { - UIUtils.setBackground(mSliderLayout, mSliderBackgroundDrawable); - } else if (mSliderBackgroundDrawableRes != -1) { - UIUtils.setBackground(mSliderLayout, mSliderBackgroundDrawableRes); - } - - //handle the header - DrawerUtils.handleHeaderView(this); - - //handle the footer - DrawerUtils.handleFooterView(this, new View.OnClickListener() { - @Override - public void onClick(View v) { - IDrawerItem drawerItem = (IDrawerItem) v.getTag(R.id.material_drawer_item); - DrawerUtils.onFooterDrawerItemClick(DrawerBuilder.this, drawerItem, v, true); - } - }); - - //if MultiSelect is possible - mAdapter.withMultiSelect(mMultiSelect); - if (mMultiSelect) { - mAdapter.withSelectOnLongClick(false); - mAdapter.withAllowDeselection(true); - } - - //set the adapter on the listView - if (mAdapterWrapper == null) { - mRecyclerView.setAdapter(mAdapter); - } else { - mRecyclerView.setAdapter(mAdapterWrapper); - } - - //predefine selection (should be the first element - if (mSelectedItemPosition == 0 && mSelectedItemIdentifier != 0L) { - mSelectedItemPosition = DrawerUtils.getPositionByIdentifier(this, mSelectedItemIdentifier); - } - if (mHeaderView != null && mSelectedItemPosition == 0) { - mSelectedItemPosition = 1; - } - mAdapter.deselect(); - mAdapter.select(mSelectedItemPosition); - - // add the onDrawerItemClickListener if set - mAdapter.withOnClickListener(new OnClickListener() { - @Override - public boolean onClick(final View view, IAdapter adapter, final IDrawerItem item, final int position) { - if (!(item != null && item instanceof Selectable && !item.isSelectable())) { - resetStickyFooterSelection(); - mCurrentStickyFooterSelection = -1; - } - - //call the listener - boolean consumed = false; - - //call the item specific listener - if (item instanceof AbstractDrawerItem && ((AbstractDrawerItem) item).getOnDrawerItemClickListener() != null) { - consumed = ((AbstractDrawerItem) item).getOnDrawerItemClickListener().onItemClick(view, position, item); - } - - //call the drawer listener - if (mOnDrawerItemClickListener != null) { - if (mDelayDrawerClickEvent > 0) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - mOnDrawerItemClickListener.onItemClick(view, position, item); - } - }, mDelayDrawerClickEvent); - } else { - consumed = mOnDrawerItemClickListener.onItemClick(view, position, item); - } - } - - //we have to notify the miniDrawer if existing, and if the event was not consumed yet - if (!consumed && mMiniDrawer != null) { - consumed = mMiniDrawer.onItemClick(item); - } - - //if we were a expandable item we consume the event closing makes no sense - if (item instanceof IExpandable && ((IExpandable) item).getSubItems() != null) { - //we consume the event and want no further handling - return true; - } - - - if (!consumed) { - //close the drawer after click - closeDrawerDelayed(); - } - - return consumed; - } - }); - // add the onDrawerItemLongClickListener if set - mAdapter.withOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View view, IAdapter adapter, final IDrawerItem item, final int position) { - if (mOnDrawerItemLongClickListener != null) { - return mOnDrawerItemLongClickListener.onItemLongClick(view, position, getDrawerItem(position)); - } - return false; - } - }); - - if (mRecyclerView != null) { - mRecyclerView.scrollToPosition(0); - } - - // try to restore all saved values again - if (mSavedInstance != null) { - if (!mAppended) { - mAdapter.deselect(); - mAdapter.withSavedInstanceState(mSavedInstance, Drawer.BUNDLE_SELECTION); - DrawerUtils.setStickyFooterSelection(this, mSavedInstance.getInt(Drawer.BUNDLE_STICKY_FOOTER_SELECTION, -1), null); - } else { - mAdapter.deselect(); - mAdapter.withSavedInstanceState(mSavedInstance, Drawer.BUNDLE_SELECTION_APPENDED); - DrawerUtils.setStickyFooterSelection(this, mSavedInstance.getInt(Drawer.BUNDLE_STICKY_FOOTER_SELECTION_APPENDED, -1), null); - } - } - - // call initial onClick event to allow the dev to init the first view - if (mFireInitialOnClick && mOnDrawerItemClickListener != null) { - int selection = mAdapter.getSelections().size() == 0 ? -1 : mAdapter.getSelections().iterator().next(); - mOnDrawerItemClickListener.onItemClick(null, selection, getDrawerItem(selection)); - } - } - - /** - * helper method to close the drawer delayed - */ - protected void closeDrawerDelayed() { - if (mCloseOnClick && mDrawerLayout != null) { - if (mDelayOnDrawerClose > -1) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - mDrawerLayout.closeDrawers(); - - if (mScrollToTopAfterClick) { - mRecyclerView.smoothScrollToPosition(0); - } - } - }, mDelayOnDrawerClose); - } else { - mDrawerLayout.closeDrawers(); - } - } - } - - /** - * get the drawerItem at a specific position - * - * @param position - * @return - */ - protected IDrawerItem getDrawerItem(int position) { - return (IDrawerItem) getAdapter().getItem(position); - } - - /** - * check if the item is within the bounds of the list - * - * @param position - * @param includeOffset - * @return - */ - protected boolean checkDrawerItem(int position, boolean includeOffset) { - return getAdapter().getItem(position) != null; - } - - /** - * simple helper method to reset the selection of the sticky footer - */ - protected void resetStickyFooterSelection() { - if (mStickyFooterView instanceof LinearLayout) { - for (int i = 0; i < (mStickyFooterView).getChildCount(); i++) { - (mStickyFooterView).getChildAt(i).setActivated(false); - (mStickyFooterView).getChildAt(i).setSelected(false); - } - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/DrawerUtils.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/DrawerUtils.java deleted file mode 100644 index 87f1f746..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/DrawerUtils.java +++ /dev/null @@ -1,447 +0,0 @@ -package com.mikepenz.materialdrawer; - -import android.content.Context; -import android.os.Build; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; - -import com.mikepenz.materialdrawer.model.AbstractDrawerItem; -import com.mikepenz.materialdrawer.model.ContainerDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.Selectable; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.List; - -import androidx.drawerlayout.widget.DrawerLayout; - -/** - * Created by mikepenz on 23.05.15. - */ -class DrawerUtils { - /** - * helper method to handle the onClick of the footer - * - * @param drawer - * @param drawerItem - * @param v - * @param fireOnClick true if we should call the listener, false if not, null to not call the listener and not close the drawer - */ - public static void onFooterDrawerItemClick(DrawerBuilder drawer, IDrawerItem drawerItem, View v, Boolean fireOnClick) { - boolean checkable = !(drawerItem != null && drawerItem instanceof Selectable && !drawerItem.isSelectable()); - if (checkable) { - drawer.resetStickyFooterSelection(); - - v.setActivated(true); - v.setSelected(true); - - //remove the selection in the list - drawer.getAdapter().deselect(); - - //find the position of the clicked footer item - if (drawer.mStickyFooterView != null && drawer.mStickyFooterView instanceof LinearLayout) { - LinearLayout footer = (LinearLayout) drawer.mStickyFooterView; - for (int i = 0; i < footer.getChildCount(); i++) { - if (footer.getChildAt(i) == v) { - drawer.mCurrentStickyFooterSelection = i; - break; - } - } - } - } - - - if (fireOnClick != null) { - boolean consumed = false; - - if (fireOnClick) { - if (drawerItem instanceof AbstractDrawerItem && ((AbstractDrawerItem) drawerItem).getOnDrawerItemClickListener() != null) { - consumed = ((AbstractDrawerItem) drawerItem).getOnDrawerItemClickListener().onItemClick(v, -1, drawerItem); - } - - if (drawer.mOnDrawerItemClickListener != null) { - consumed = drawer.mOnDrawerItemClickListener.onItemClick(v, -1, drawerItem); - } - } - - if (!consumed) { - //close the drawer after click - drawer.closeDrawerDelayed(); - } - } - } - - /** - * helper method to set the selection of the footer - * - * @param drawer - * @param position - * @param fireOnClick - */ - public static void setStickyFooterSelection(DrawerBuilder drawer, int position, Boolean fireOnClick) { - if (position > -1) { - if (drawer.mStickyFooterView != null && drawer.mStickyFooterView instanceof LinearLayout) { - LinearLayout footer = (LinearLayout) drawer.mStickyFooterView; - if (drawer.mStickyFooterDivider) { - position = position + 1; - } - if (footer.getChildCount() > position && position >= 0) { - IDrawerItem drawerItem = (IDrawerItem) footer.getChildAt(position).getTag(R.id.material_drawer_item); - onFooterDrawerItemClick(drawer, drawerItem, footer.getChildAt(position), fireOnClick); - } - } - } - } - - /** - * calculates the position of an drawerItem. searching by it's identifier - * - * @param identifier - * @return - */ - public static int getPositionByIdentifier(DrawerBuilder drawer, long identifier) { - if (identifier != -1) { - for (int i = 0; i < drawer.getAdapter().getItemCount(); i++) { - if (drawer.getAdapter().getItem(i).getIdentifier() == identifier) { - return i; - } - } - } - - return -1; - } - - /** - * gets the drawerItem with the specific identifier from a drawerItem list - * - * @param drawerItems - * @param identifier - * @return - */ - public static IDrawerItem getDrawerItem(List drawerItems, long identifier) { - if (identifier != -1) { - for (IDrawerItem drawerItem : drawerItems) { - if (drawerItem.getIdentifier() == identifier) { - return drawerItem; - } - } - } - return null; - } - - /** - * gets the drawerItem by a defined tag from a drawerItem list - * - * @param drawerItems - * @param tag - * @return - */ - public static IDrawerItem getDrawerItem(List drawerItems, Object tag) { - if (tag != null) { - for (IDrawerItem drawerItem : drawerItems) { - if (tag.equals(drawerItem.getTag())) { - return drawerItem; - } - } - } - return null; - } - - /** - * calculates the position of an drawerItem inside the footer. searching by it's identifier - * - * @param identifier - * @return - */ - public static int getStickyFooterPositionByIdentifier(DrawerBuilder drawer, long identifier) { - if (identifier != -1) { - if (drawer.mStickyFooterView != null && drawer.mStickyFooterView instanceof LinearLayout) { - LinearLayout footer = (LinearLayout) drawer.mStickyFooterView; - - int shadowOffset = 0; - for (int i = 0; i < footer.getChildCount(); i++) { - Object o = footer.getChildAt(i).getTag(R.id.material_drawer_item); - - //count up the shadowOffset to return the correct position of the given item - if (o == null && drawer.mStickyFooterDivider) { - shadowOffset = shadowOffset + 1; - } - - if (o != null && o instanceof IDrawerItem && ((IDrawerItem) o).getIdentifier() == identifier) { - return i - shadowOffset; - } - } - } - } - - return -1; - } - - /** - * helper method to handle the headerView - * - * @param drawer - */ - public static void handleHeaderView(DrawerBuilder drawer) { - //use the AccountHeader if set - if (drawer.mAccountHeader != null) { - if (drawer.mAccountHeaderSticky) { - drawer.mStickyHeaderView = drawer.mAccountHeader.getView(); - } else { - drawer.mHeaderView = drawer.mAccountHeader.getView(); - drawer.mHeaderDivider = drawer.mAccountHeader.mAccountHeaderBuilder.mDividerBelowHeader; - drawer.mHeaderPadding = drawer.mAccountHeader.mAccountHeaderBuilder.mPaddingBelowHeader; - } - } - - //sticky header view - if (drawer.mStickyHeaderView != null) { - //add the sticky footer view and align it to the bottom - RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, 1); - drawer.mStickyHeaderView.setId(R.id.material_drawer_sticky_header); - drawer.mSliderLayout.addView(drawer.mStickyHeaderView, 0, layoutParams); - - //now align the recyclerView below the stickyFooterView ;) - RelativeLayout.LayoutParams layoutParamsListView = (RelativeLayout.LayoutParams) drawer.mRecyclerView.getLayoutParams(); - layoutParamsListView.addRule(RelativeLayout.BELOW, R.id.material_drawer_sticky_header); - drawer.mRecyclerView.setLayoutParams(layoutParamsListView); - - //set a background color or the elevation will not work - drawer.mStickyHeaderView.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(drawer.mActivity, R.attr.material_drawer_background, R.color.material_drawer_background)); - - if (drawer.mStickyHeaderShadow) { - //add a shadow - if (Build.VERSION.SDK_INT >= 21) { - drawer.mStickyHeaderView.setElevation(UIUtils.convertDpToPixel(4, drawer.mActivity)); - } else { - View view = new View(drawer.mActivity); - view.setBackgroundResource(R.drawable.material_drawer_shadow_bottom); - drawer.mSliderLayout.addView(view, RelativeLayout.LayoutParams.MATCH_PARENT, (int) UIUtils.convertDpToPixel(4, drawer.mActivity)); - //now align the shadow below the stickyHeader ;) - RelativeLayout.LayoutParams lps = (RelativeLayout.LayoutParams) view.getLayoutParams(); - lps.addRule(RelativeLayout.BELOW, R.id.material_drawer_sticky_header); - view.setLayoutParams(lps); - } - } - - //remove the padding of the recyclerView again we have the header on top of it - drawer.mRecyclerView.setPadding(0, 0, 0, 0); - } - - // set the header (do this before the setAdapter because some devices will crash else - if (drawer.mHeaderView != null) { - if (drawer.mRecyclerView == null) { - throw new RuntimeException("can't use a headerView without a recyclerView"); - } - - if (drawer.mHeaderPadding) { - drawer.getHeaderAdapter().add(new ContainerDrawerItem().withView(drawer.mHeaderView).withHeight(drawer.mHeiderHeight).withDivider(drawer.mHeaderDivider).withViewPosition(ContainerDrawerItem.Position.TOP)); - } else { - drawer.getHeaderAdapter().add(new ContainerDrawerItem().withView(drawer.mHeaderView).withHeight(drawer.mHeiderHeight).withDivider(drawer.mHeaderDivider).withViewPosition(ContainerDrawerItem.Position.NONE)); - } - //set the padding on the top to 0 - drawer.mRecyclerView.setPadding(drawer.mRecyclerView.getPaddingLeft(), 0, drawer.mRecyclerView.getPaddingRight(), drawer.mRecyclerView.getPaddingBottom()); - } - } - - /** - * small helper to rebuild the FooterView - * - * @param drawer - */ - public static void rebuildStickyFooterView(final DrawerBuilder drawer) { - if (drawer.mSliderLayout != null) { - if (drawer.mStickyFooterView != null) { - drawer.mStickyFooterView.removeAllViews(); - - //create the divider - if (drawer.mStickyFooterDivider) { - addStickyFooterDivider(drawer.mStickyFooterView.getContext(), drawer.mStickyFooterView); - } - - //fill the footer with items - DrawerUtils.fillStickyDrawerItemFooter(drawer, drawer.mStickyFooterView, new View.OnClickListener() { - @Override - public void onClick(View v) { - IDrawerItem drawerItem = (IDrawerItem) v.getTag(R.id.material_drawer_item); - com.mikepenz.materialdrawer.DrawerUtils.onFooterDrawerItemClick(drawer, drawerItem, v, true); - } - }); - - drawer.mStickyFooterView.setVisibility(View.VISIBLE); - } else { - //there was no footer yet. now just create one - DrawerUtils.handleFooterView(drawer, new View.OnClickListener() { - @Override - public void onClick(View v) { - IDrawerItem drawerItem = (IDrawerItem) v.getTag(R.id.material_drawer_item); - DrawerUtils.onFooterDrawerItemClick(drawer, drawerItem, v, true); - } - }); - } - - setStickyFooterSelection(drawer, drawer.mCurrentStickyFooterSelection, false); - } - } - - /** - * helper method to handle the footerView - * - * @param drawer - */ - public static void handleFooterView(DrawerBuilder drawer, View.OnClickListener onClickListener) { - Context ctx = drawer.mSliderLayout.getContext(); - - //use the StickyDrawerItems if set - if (drawer.mStickyDrawerItems != null && drawer.mStickyDrawerItems.size() > 0) { - drawer.mStickyFooterView = DrawerUtils.buildStickyDrawerItemFooter(ctx, drawer, onClickListener); - } - - //sticky footer view - if (drawer.mStickyFooterView != null) { - //add the sticky footer view and align it to the bottom - RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1); - drawer.mStickyFooterView.setId(R.id.material_drawer_sticky_footer); - drawer.mSliderLayout.addView(drawer.mStickyFooterView, layoutParams); - - if ((drawer.mTranslucentNavigationBar || drawer.mFullscreen) && Build.VERSION.SDK_INT >= 19) { - drawer.mStickyFooterView.setPadding(0, 0, 0, UIUtils.getNavigationBarHeight(ctx)); - } - - //now align the recyclerView above the stickyFooterView ;) - RelativeLayout.LayoutParams layoutParamsListView = (RelativeLayout.LayoutParams) drawer.mRecyclerView.getLayoutParams(); - layoutParamsListView.addRule(RelativeLayout.ABOVE, R.id.material_drawer_sticky_footer); - drawer.mRecyclerView.setLayoutParams(layoutParamsListView); - - //handle shadow on top of the sticky footer - if (drawer.mStickyFooterShadow) { - drawer.mStickyFooterShadowView = new View(ctx); - drawer.mStickyFooterShadowView.setBackgroundResource(R.drawable.material_drawer_shadow_top); - drawer.mSliderLayout.addView(drawer.mStickyFooterShadowView, RelativeLayout.LayoutParams.MATCH_PARENT, ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_sticky_footer_elevation)); - //now align the shadow below the stickyHeader ;) - RelativeLayout.LayoutParams lps = (RelativeLayout.LayoutParams) drawer.mStickyFooterShadowView.getLayoutParams(); - lps.addRule(RelativeLayout.ABOVE, R.id.material_drawer_sticky_footer); - drawer.mStickyFooterShadowView.setLayoutParams(lps); - } - - //remove the padding of the recyclerView again we have the footer below it - drawer.mRecyclerView.setPadding(drawer.mRecyclerView.getPaddingLeft(), drawer.mRecyclerView.getPaddingTop(), drawer.mRecyclerView.getPaddingRight(), ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_padding)); - } - - // set the footer (do this before the setAdapter because some devices will crash else - if (drawer.mFooterView != null) { - if (drawer.mRecyclerView == null) { - throw new RuntimeException("can't use a footerView without a recyclerView"); - } - - if (drawer.mFooterDivider) { - drawer.getFooterAdapter().add(new ContainerDrawerItem().withView(drawer.mFooterView).withViewPosition(ContainerDrawerItem.Position.BOTTOM)); - } else { - drawer.getFooterAdapter().add(new ContainerDrawerItem().withView(drawer.mFooterView).withViewPosition(ContainerDrawerItem.Position.NONE)); - } - } - } - - - /** - * build the sticky footer item view - * - * @return - */ - public static ViewGroup buildStickyDrawerItemFooter(Context ctx, DrawerBuilder drawer, View.OnClickListener onClickListener) { - //create the container view - final LinearLayout linearLayout = new LinearLayout(ctx); - linearLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - linearLayout.setOrientation(LinearLayout.VERTICAL); - //set the background color to the drawer background color (if it has alpha the shadow won't be visible) - linearLayout.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(ctx, R.attr.material_drawer_background, R.color.material_drawer_background)); - - //create the divider - if (drawer.mStickyFooterDivider) { - addStickyFooterDivider(ctx, linearLayout); - } - - fillStickyDrawerItemFooter(drawer, linearLayout, onClickListener); - - return linearLayout; - } - - /** - * adds the shadow to the stickyFooter - * - * @param ctx - * @param footerView - */ - private static void addStickyFooterDivider(Context ctx, ViewGroup footerView) { - LinearLayout divider = new LinearLayout(ctx); - LinearLayout.LayoutParams dividerParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - divider.setMinimumHeight((int) UIUtils.convertDpToPixel(1, ctx)); - divider.setOrientation(LinearLayout.VERTICAL); - divider.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(ctx, R.attr.material_drawer_divider, R.color.material_drawer_divider)); - footerView.addView(divider, dividerParams); - } - - /** - * helper method to fill the sticky footer with it's elements - * - * @param drawer - * @param container - * @param onClickListener - */ - public static void fillStickyDrawerItemFooter(DrawerBuilder drawer, ViewGroup container, View.OnClickListener onClickListener) { - //add all drawer items - for (IDrawerItem drawerItem : drawer.mStickyDrawerItems) { - View view = drawerItem.generateView(container.getContext(), container); - view.setTag(drawerItem); - - if (drawerItem.isEnabled()) { - //UIUtils.setBackground(view, UIUtils.getSelectableBackground(container.getContext(), selected_color, drawerItem.isSelectedBackgroundAnimated())); - view.setOnClickListener(onClickListener); - } - - container.addView(view); - - //for android API 17 --> Padding not applied via xml - DrawerUIUtils.setDrawerVerticalPadding(view); - } - //and really. don't ask about this. it won't set the padding if i don't set the padding for the container - container.setPadding(0, 0, 0, 0); - } - - - /** - * helper to extend the layoutParams of the drawer - * - * @param params - * @return - */ - public static DrawerLayout.LayoutParams processDrawerLayoutParams(DrawerBuilder drawer, DrawerLayout.LayoutParams params) { - if (params != null) { - if (drawer.mDrawerGravity != null && (drawer.mDrawerGravity == Gravity.RIGHT || drawer.mDrawerGravity == Gravity.END)) { - params.rightMargin = 0; - if (Build.VERSION.SDK_INT >= 17) { - params.setMarginEnd(0); - } - - params.leftMargin = drawer.mActivity.getResources().getDimensionPixelSize(R.dimen.material_drawer_margin); - if (Build.VERSION.SDK_INT >= 17) { - params.setMarginEnd(drawer.mActivity.getResources().getDimensionPixelSize(R.dimen.material_drawer_margin)); - } - } - - if (drawer.mDrawerWidth > -1) { - params.width = drawer.mDrawerWidth; - } else { - params.width = DrawerUIUtils.getOptimalDrawerWidth(drawer.mActivity); - } - } - - return params; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/MiniDrawer.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/MiniDrawer.java deleted file mode 100644 index fc5a33f0..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/MiniDrawer.java +++ /dev/null @@ -1,544 +0,0 @@ -package com.mikepenz.materialdrawer; - -import android.content.Context; -import android.content.res.Configuration; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.IAdapter; -import com.mikepenz.fastadapter.adapters.ItemAdapter; -import com.mikepenz.fastadapter.listeners.OnClickListener; -import com.mikepenz.fastadapter.listeners.OnLongClickListener; -import com.mikepenz.materialdrawer.interfaces.ICrossfader; -import com.mikepenz.materialdrawer.model.MiniDrawerItem; -import com.mikepenz.materialdrawer.model.MiniProfileDrawerItem; -import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; -import com.mikepenz.materialdrawer.model.ProfileDrawerItem; -import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.List; - -/** - * Created by mikepenz on 15.07.15. - * Don't count this for real yet. it's just a quick try on creating a Gmail like panel - */ -public class MiniDrawer { - public static final int PROFILE = 1; - public static final int ITEM = 2; - - private LinearLayout mContainer; - private RecyclerView mRecyclerView; - protected FastAdapter mAdapter; - protected ItemAdapter mItemAdapter; - - private Drawer mDrawer; - - /** - * Provide the Drawer which will be used as dataSource for the drawerItems - * - * @param drawer - * @return - */ - public MiniDrawer withDrawer(@NonNull Drawer drawer) { - this.mDrawer = drawer; - return this; - } - - private AccountHeader mAccountHeader; - - /** - * Provide the AccountHeader which will be used as the dataSource for the profiles - * - * @param accountHeader - * @return - */ - public MiniDrawer withAccountHeader(@NonNull AccountHeader accountHeader) { - this.mAccountHeader = accountHeader; - return this; - } - - private ICrossfader mCrossFader; - - /** - * Provide the Crossfader implementation which is used with this MiniDrawer - * - * @param crossFader - * @return - */ - public MiniDrawer withCrossFader(@NonNull ICrossfader crossFader) { - this.mCrossFader = crossFader; - return this; - } - - private boolean mInnerShadow = false; - - /** - * set to true if you want to show the innerShadow on the MiniDrawer - * - * @param innerShadow - * @return - */ - public MiniDrawer withInnerShadow(boolean innerShadow) { - this.mInnerShadow = innerShadow; - return this; - } - - private boolean mInRTL = false; - - /** - * set to true if you want the MiniDrawer in RTL mode - * - * @param inRTL - * @return - */ - public MiniDrawer withInRTL(boolean inRTL) { - this.mInRTL = inRTL; - return this; - } - - private boolean mIncludeSecondaryDrawerItems = false; - - /** - * set to true if you also want to display secondaryDrawerItems - * - * @param includeSecondaryDrawerItems - * @return - */ - public MiniDrawer withIncludeSecondaryDrawerItems(boolean includeSecondaryDrawerItems) { - this.mIncludeSecondaryDrawerItems = includeSecondaryDrawerItems; - return this; - } - - private boolean mEnableSelectedMiniDrawerItemBackground = false; - - /** - * set to true if you want to display the background for the miniDrawerItem - * - * @param enableSelectedMiniDrawerItemBackground - * @return - */ - public MiniDrawer withEnableSelectedMiniDrawerItemBackground(boolean enableSelectedMiniDrawerItemBackground) { - this.mEnableSelectedMiniDrawerItemBackground = enableSelectedMiniDrawerItemBackground; - return this; - } - - private boolean mEnableProfileClick = true; - - /** - * set to false if you do not want the profile image to toggle to the normal drawers profile selection - * - * @param enableProfileClick - * @return this - */ - public MiniDrawer withEnableProfileClick(boolean enableProfileClick) { - this.mEnableProfileClick = enableProfileClick; - return this; - } - - private OnMiniDrawerItemClickListener mOnMiniDrawerItemClickListener; - - /** - * Define the onMiniDrawerItemClickListener called before any logic in the MiniDrawer is run, allows you to intercept the default behavior - * - * @param onMiniDrawerItemClickListener - * @return this - */ - public MiniDrawer withOnMiniDrawerItemClickListener(OnMiniDrawerItemClickListener onMiniDrawerItemClickListener) { - this.mOnMiniDrawerItemClickListener = onMiniDrawerItemClickListener; - return this; - } - - - private OnClickListener mOnMiniDrawerItemOnClickListener; - - /** - * Define an onClickListener for the MiniDrawer item adapter. WARNING: this will completely overwrite the default behavior - * You may want to check the `OnMiniDrawerItemClickListener` (withOnMiniDrawerItemClickListener) which just hooks into the default behavior - * - * @param onMiniDrawerItemOnClickListener - * @return this - */ - public MiniDrawer withOnMiniDrawerItemOnClickListener(OnClickListener onMiniDrawerItemOnClickListener) { - this.mOnMiniDrawerItemOnClickListener = onMiniDrawerItemOnClickListener; - return this; - } - - - private OnLongClickListener mOnMiniDrawerItemLongClickListener; - - /** - * Define an onLongClickListener for the MiniDrawer item adapter - * - * @param onMiniDrawerItemLongClickListener - * @return - */ - public MiniDrawer withOnMiniDrawerItemLongClickListener(OnLongClickListener onMiniDrawerItemLongClickListener) { - this.mOnMiniDrawerItemLongClickListener = onMiniDrawerItemLongClickListener; - return this; - } - - /** - * get the RecyclerView of this MiniDrawer - * - * @return - */ - public RecyclerView getRecyclerView() { - return mRecyclerView; - } - - /** - * get the FastAdapter of this MiniDrawer - * - * @return - */ - public FastAdapter getAdapter() { - return mAdapter; - } - - /** - * get the ItemAdapter of this MiniDrawer - * - * @return - */ - public ItemAdapter getItemAdapter() { - return mItemAdapter; - } - - /** - * get the Drawer used to fill this MiniDrawer - * - * @return - */ - public Drawer getDrawer() { - return mDrawer; - } - - /** - * get the AccountHeader used to fill the this MiniDrawer - * - * @return - */ - public AccountHeader getAccountHeader() { - return mAccountHeader; - } - - /** - * get the Crossfader used for this MiniDrawer - * - * @return - */ - public ICrossfader getCrossFader() { - return mCrossFader; - } - - - /** - * the defined FastAdapter.OnClickListener which completely replaces the original behavior - * - * @return - */ - public OnClickListener getOnMiniDrawerItemOnClickListener() { - return mOnMiniDrawerItemOnClickListener; - } - - /** - * @return - */ - public OnLongClickListener getOnMiniDrawerItemLongClickListener() { - return mOnMiniDrawerItemLongClickListener; - } - - - /** - * generates a MiniDrawerItem from a IDrawerItem - * - * @param drawerItem - * @return - */ - public IDrawerItem generateMiniDrawerItem(IDrawerItem drawerItem) { - if (drawerItem instanceof SecondaryDrawerItem) { - if (drawerItem.isExcludeFromMiniDrawer()) { - return null; - } - return mIncludeSecondaryDrawerItems ? new MiniDrawerItem((SecondaryDrawerItem) drawerItem).withEnableSelectedBackground(mEnableSelectedMiniDrawerItemBackground).withSelectedBackgroundAnimated(false) : null; - } else if (drawerItem instanceof PrimaryDrawerItem) { - if (drawerItem.isExcludeFromMiniDrawer()) { - return null; - } - return new MiniDrawerItem((PrimaryDrawerItem) drawerItem).withEnableSelectedBackground(mEnableSelectedMiniDrawerItemBackground).withSelectedBackgroundAnimated(false); - } else if (drawerItem instanceof ProfileDrawerItem) { - if (drawerItem.isExcludeFromMiniDrawer()) { - return null; - } - MiniProfileDrawerItem mpdi = new MiniProfileDrawerItem((ProfileDrawerItem) drawerItem); - mpdi.withEnabled(mEnableProfileClick); - return mpdi; - } - return null; - } - - /** - * gets the type of a IDrawerItem - * - * @param drawerItem - * @return - */ - public int getMiniDrawerType(IDrawerItem drawerItem) { - if (drawerItem instanceof MiniProfileDrawerItem) { - return PROFILE; - } else if (drawerItem instanceof MiniDrawerItem) { - return ITEM; - } - return -1; - } - - /** - * build the MiniDrawer - * - * @param ctx - * @return - */ - public View build(Context ctx) { - mContainer = new LinearLayout(ctx); - if (mInnerShadow) { - if (!mInRTL) { - mContainer.setBackgroundResource(R.drawable.material_drawer_shadow_left); - } else { - mContainer.setBackgroundResource(R.drawable.material_drawer_shadow_right); - } - } - - //create and append recyclerView - mRecyclerView = new RecyclerView(ctx); - mContainer.addView(mRecyclerView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - - //set the itemAnimator - mRecyclerView.setItemAnimator(new DefaultItemAnimator()); - //some style improvements on older devices - mRecyclerView.setFadingEdgeLength(0); - //set the drawing cache background to the same color as the slider to improve performance - //mRecyclerView.setDrawingCacheBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(mActivity, R.attr.material_drawer_background, R.color.material_drawer_background)); - mRecyclerView.setClipToPadding(false); - //additional stuff - mRecyclerView.setLayoutManager(new LinearLayoutManager(ctx)); - //adapter - mItemAdapter = new ItemAdapter<>(); - mAdapter = FastAdapter.with(mItemAdapter); - mAdapter.withSelectable(true); - mAdapter.withAllowDeselection(false); - mRecyclerView.setAdapter(mAdapter); - - //if the activity with the drawer should be fullscreen add the padding for the statusbar - if (mDrawer != null && mDrawer.mDrawerBuilder != null && (mDrawer.mDrawerBuilder.mFullscreen || mDrawer.mDrawerBuilder.mTranslucentStatusBar)) { - mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), UIUtils.getStatusBarHeight(ctx), mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingBottom()); - } - - //if the activity with the drawer should be fullscreen add the padding for the navigationBar - if (mDrawer != null && mDrawer.mDrawerBuilder != null && (mDrawer.mDrawerBuilder.mFullscreen || mDrawer.mDrawerBuilder.mTranslucentNavigationBar) && ctx.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - mRecyclerView.setPadding(mRecyclerView.getPaddingLeft(), mRecyclerView.getPaddingTop(), mRecyclerView.getPaddingRight(), UIUtils.getNavigationBarHeight(ctx)); - } - - //set the adapter with the items - createItems(); - - return mContainer; - } - - /** - * call this method to trigger the onProfileClick on the MiniDrawer - */ - public void onProfileClick() { - //crossfade if we are cross faded - if (mCrossFader != null) { - if (mCrossFader.isCrossfaded()) { - mCrossFader.crossfade(); - } - } - - //update the current profile - if (mAccountHeader != null) { - IProfile profile = mAccountHeader.getActiveProfile(); - if (profile instanceof IDrawerItem) { - mItemAdapter.set(0, generateMiniDrawerItem((IDrawerItem) profile)); - } - } - } - - /** - * call this method to trigger the onItemClick on the MiniDrawer - * - * @param selectedDrawerItem - * @return - */ - public boolean onItemClick(IDrawerItem selectedDrawerItem) { - //We only need to clear if the new item is selectable - if (selectedDrawerItem.isSelectable()) { - //crossfade if we are cross faded - if (mCrossFader != null) { - if (mCrossFader.isCrossfaded()) { - mCrossFader.crossfade(); - } - } - //update everything - setSelection(selectedDrawerItem.getIdentifier()); - - return false; - } else { - return true; - } - } - - /** - * set the selection of the MiniDrawer - * - * @param identifier the identifier of the item which should be selected (-1 for none) - */ - public void setSelection(long identifier) { - if (identifier == -1) { - mAdapter.deselect(); - } - int count = mAdapter.getItemCount(); - for (int i = 0; i < count; i++) { - IDrawerItem item = mAdapter.getItem(i); - if (item.getIdentifier() == identifier && !item.isSelected()) { - mAdapter.deselect(); - mAdapter.select(i); - } - } - } - - /** - * update a MiniDrawerItem (after updating the main Drawer) via its identifier - * - * @param identifier the identifier of the item which was updated - */ - public void updateItem(long identifier) { - if (mDrawer != null && mAdapter != null && mItemAdapter.getAdapterItems() != null && identifier != -1) { - IDrawerItem drawerItem = DrawerUtils.getDrawerItem(getDrawerItems(), identifier); - for (int i = 0; i < mItemAdapter.getAdapterItems().size(); i++) { - if (mItemAdapter.getAdapterItems().get(i).getIdentifier() == drawerItem.getIdentifier()) { - IDrawerItem miniDrawerItem = generateMiniDrawerItem(drawerItem); - if (miniDrawerItem != null) { - mItemAdapter.set(i, miniDrawerItem); - } - } - } - } - } - - /** - * creates the items for the MiniDrawer - */ - public void createItems() { - mItemAdapter.clear(); - - int profileOffset = 0; - if (mAccountHeader != null && mAccountHeader.getAccountHeaderBuilder().mProfileImagesVisible) { - IProfile profile = mAccountHeader.getActiveProfile(); - if (profile instanceof IDrawerItem) { - mItemAdapter.add(generateMiniDrawerItem((IDrawerItem) profile)); - profileOffset = 1; - } - } - - int select = -1; - if (mDrawer != null) { - if (getDrawerItems() != null) { - //migrate to miniDrawerItems - int length = getDrawerItems().size(); - - int position = 0; - for (int i = 0; i < length; i++) { - IDrawerItem miniDrawerItem = generateMiniDrawerItem(getDrawerItems().get(i)); - if (miniDrawerItem != null) { - if (miniDrawerItem.isSelected()) { - select = position; - } - mItemAdapter.add(miniDrawerItem); - position = position + 1; - } - } - - if (select >= 0) { - //+1 because of the profile - mAdapter.select(select + profileOffset); - } - } - } - - //listener - if (mOnMiniDrawerItemOnClickListener != null) { - mAdapter.withOnClickListener(mOnMiniDrawerItemOnClickListener); - } else { - mAdapter.withOnClickListener(new OnClickListener() { - @Override - public boolean onClick(View v, IAdapter adapter, final IDrawerItem item, final int position) { - int type = getMiniDrawerType(item); - - //if a listener is defined and we consume the event return - if (mOnMiniDrawerItemClickListener != null && mOnMiniDrawerItemClickListener.onItemClick(v, position, item, type)) { - return false; - } - - if (type == ITEM) { - //fire the onClickListener also if the specific drawerItem is not Selectable - if (item.isSelectable()) { - //make sure we are on the original drawerItemList - if (mAccountHeader != null && mAccountHeader.isSelectionListShown()) { - mAccountHeader.toggleSelectionList(v.getContext()); - } - IDrawerItem drawerItem = mDrawer.getDrawerItem(item.getIdentifier()); - if (drawerItem != null && !drawerItem.isSelected()) { - //set the selection - mDrawer.setSelection(item, true); - } - } else if (mDrawer.getOnDrawerItemClickListener() != null) { - //get the original `DrawerItem` from the Drawer as this one will contain all information - mDrawer.getOnDrawerItemClickListener().onItemClick(v, position, DrawerUtils.getDrawerItem(getDrawerItems(), item.getIdentifier())); - } - } else if (type == PROFILE) { - if (mAccountHeader != null && !mAccountHeader.isSelectionListShown()) { - mAccountHeader.toggleSelectionList(v.getContext()); - } - if (mCrossFader != null) { - mCrossFader.crossfade(); - } - } - return false; - } - }); - } - mAdapter.withOnLongClickListener(mOnMiniDrawerItemLongClickListener); - mRecyclerView.scrollToPosition(0); - } - - /** - * returns always the original drawerItems and not the switched content - * - * @return - */ - private List getDrawerItems() { - return mDrawer.getOriginalDrawerItems() != null ? mDrawer.getOriginalDrawerItems() : mDrawer.getDrawerItems(); - } - - - public interface OnMiniDrawerItemClickListener { - /** - * @param view - * @param position - * @param drawerItem - * @param type either MiniDrawer.PROFILE or MiniDrawer.ITEM - * @return true if the event was consumed - */ - boolean onItemClick(View view, int position, IDrawerItem drawerItem, int type); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/BadgeStyle.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/BadgeStyle.java deleted file mode 100644 index 7a754a47..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/BadgeStyle.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.mikepenz.materialdrawer.holder; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; -import android.widget.TextView; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.model.utils.BadgeDrawableBuilder; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DimenRes; -import androidx.annotation.Dimension; -import androidx.annotation.DrawableRes; -import androidx.core.view.ViewCompat; - -import static androidx.annotation.Dimension.DP; -import static androidx.annotation.Dimension.PX; - -/** - * Class to allow defining a BadgeStyle for the `BadgeDrawerItem` - */ -public class BadgeStyle { - private int mGradientDrawable = R.drawable.material_drawer_badge; - private Drawable mBadgeBackground; - private ColorHolder mColor; - private ColorHolder mColorPressed; - private ColorHolder mTextColor; - private ColorStateList mTextColorStateList; - private DimenHolder mCorners; - private DimenHolder mPaddingTopBottom = DimenHolder.fromDp(2); //2 looks best - private DimenHolder mPaddingLeftRight = DimenHolder.fromDp(3); //3 looks best - private DimenHolder mMinWidth = DimenHolder.fromDp(20); //20 looks nice - - public int getGradientDrawable() { - return mGradientDrawable; - } - - public BadgeStyle withGradientDrawable(@DrawableRes int gradientDrawable) { - this.mGradientDrawable = gradientDrawable; - this.mBadgeBackground = null; - return this; - } - - public Drawable getBadgeBackground() { - return mBadgeBackground; - } - - public BadgeStyle withBadgeBackground(Drawable badgeBackground) { - this.mBadgeBackground = badgeBackground; - this.mGradientDrawable = -1; - return this; - } - - public ColorHolder getColor() { - return mColor; - } - - public BadgeStyle withColor(@ColorInt int color) { - this.mColor = ColorHolder.fromColor(color); - return this; - } - - public BadgeStyle withColorRes(@ColorRes int color) { - this.mColor = ColorHolder.fromColorRes(color); - return this; - } - - public ColorHolder getColorPressed() { - return mColorPressed; - } - - public BadgeStyle withColorPressed(@ColorInt int colorPressed) { - this.mColorPressed = ColorHolder.fromColor(colorPressed); - return this; - } - - public BadgeStyle withColorPressedRes(@ColorRes int colorPressed) { - this.mColorPressed = ColorHolder.fromColorRes(colorPressed); - return this; - } - - public ColorHolder getTextColor() { - return mTextColor; - } - - public BadgeStyle withTextColor(@ColorInt int textColor) { - this.mTextColor = ColorHolder.fromColor(textColor); - return this; - } - - public BadgeStyle withTextColorRes(@ColorRes int textColor) { - this.mTextColor = ColorHolder.fromColorRes(textColor); - return this; - } - - public BadgeStyle withTextColorStateList(ColorStateList textColorStateList) { - this.mTextColor = null; - this.mTextColorStateList = textColorStateList; - return this; - } - - public DimenHolder getCorners() { - return mCorners; - } - - public BadgeStyle withCorners(@Dimension(unit = PX) int cornersPx) { - this.mCorners = DimenHolder.fromPixel(cornersPx); - return this; - } - - public BadgeStyle withCornersDp(@Dimension(unit = DP) int corners) { - this.mCorners = DimenHolder.fromDp(corners); - return this; - } - - public BadgeStyle withCorners(DimenHolder corners) { - this.mCorners = corners; - return this; - } - - public DimenHolder getPaddingLeftRight() { - return mPaddingLeftRight; - } - - public BadgeStyle withPaddingLeftRightPx(@Dimension(unit = PX) int paddingLeftRight) { - this.mPaddingLeftRight = DimenHolder.fromPixel(paddingLeftRight); - return this; - } - - public BadgeStyle withPaddingLeftRightDp(@Dimension(unit = DP) int paddingLeftRight) { - this.mPaddingLeftRight = DimenHolder.fromDp(paddingLeftRight); - return this; - } - - public BadgeStyle withPaddingLeftRightRes(@DimenRes int paddingLeftRight) { - this.mPaddingLeftRight = DimenHolder.fromResource(paddingLeftRight); - return this; - } - - public DimenHolder getPaddingTopBottom() { - return mPaddingTopBottom; - } - - public BadgeStyle withPaddingTopBottomPx(@Dimension(unit = PX) int paddingTopBottom) { - this.mPaddingTopBottom = DimenHolder.fromPixel(paddingTopBottom); - return this; - } - - public BadgeStyle withPaddingTopBottomDp(@Dimension(unit = DP) int paddingTopBottom) { - this.mPaddingTopBottom = DimenHolder.fromDp(paddingTopBottom); - return this; - } - - public BadgeStyle withPaddingTopBottomRes(@DimenRes int paddingTopBottom) { - this.mPaddingTopBottom = DimenHolder.fromResource(paddingTopBottom); - return this; - } - - public BadgeStyle withPadding(@Dimension(unit = PX) int padding) { - this.mPaddingLeftRight = DimenHolder.fromPixel(padding); - this.mPaddingTopBottom = DimenHolder.fromPixel(padding); - return this; - } - - public BadgeStyle withPadding(DimenHolder padding) { - this.mPaddingLeftRight = padding; - this.mPaddingTopBottom = padding; - return this; - } - - public DimenHolder getMinWidth() { - return mMinWidth; - } - - public BadgeStyle withMinWidth(@Dimension(unit = PX) int minWidth) { - this.mMinWidth = DimenHolder.fromPixel(minWidth); - return this; - } - - public BadgeStyle withMinWidth(DimenHolder minWidth) { - this.mMinWidth = minWidth; - return this; - } - - public BadgeStyle() { - } - - public BadgeStyle(@ColorInt int color, @ColorInt int colorPressed) { - this.mColor = ColorHolder.fromColor(color); - this.mColorPressed = ColorHolder.fromColor(colorPressed); - } - - public BadgeStyle(@DrawableRes int gradientDrawable, @ColorInt int color, @ColorInt int colorPressed, @ColorInt int textColor) { - this.mGradientDrawable = gradientDrawable; - this.mColor = ColorHolder.fromColor(color); - this.mColorPressed = ColorHolder.fromColor(colorPressed); - this.mTextColor = ColorHolder.fromColor(textColor); - } - - public void style(TextView badgeTextView) { - style(badgeTextView, null); - } - - public void style(TextView badgeTextView, ColorStateList colorStateList) { - Context ctx = badgeTextView.getContext(); - //set background for badge - if (mBadgeBackground == null) { - ViewCompat.setBackground(badgeTextView, new BadgeDrawableBuilder(this).build(ctx)); - } else { - ViewCompat.setBackground(badgeTextView, mBadgeBackground); - } - - //set the badge text color - if (mTextColor != null) { - ColorHolder.applyToOr(mTextColor, badgeTextView, null); - } else if (mTextColorStateList != null) { - badgeTextView.setTextColor(mTextColorStateList); - } else if (colorStateList != null) { - badgeTextView.setTextColor(colorStateList); - } - - //set the padding - int paddingLeftRight = mPaddingLeftRight.asPixel(ctx); - int paddingTopBottom = mPaddingTopBottom.asPixel(ctx); - badgeTextView.setPadding(paddingLeftRight, paddingTopBottom, paddingLeftRight, paddingTopBottom); - - //set the min width - badgeTextView.setMinWidth(mMinWidth.asPixel(ctx)); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/ColorHolder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/ColorHolder.java deleted file mode 100644 index 60f577f0..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/ColorHolder.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.mikepenz.materialdrawer.holder; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; - -/** - * Created by mikepenz on 13.07.15. - */ -public class ColorHolder extends com.mikepenz.materialize.holder.ColorHolder { - public ColorHolder() { - } - - public static ColorHolder fromColorRes(@ColorRes int colorRes) { - ColorHolder colorHolder = new ColorHolder(); - colorHolder.setColorRes(colorRes); - return colorHolder; - } - - public static ColorHolder fromColor(@ColorInt int colorInt) { - ColorHolder colorHolder = new ColorHolder(); - colorHolder.setColorInt(colorInt); - return colorHolder; - } - -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/DimenHolder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/DimenHolder.java deleted file mode 100644 index c946e5dc..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/DimenHolder.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.mikepenz.materialdrawer.holder; - -import androidx.annotation.DimenRes; -import androidx.annotation.Dimension; - -import static androidx.annotation.Dimension.DP; -import static androidx.annotation.Dimension.PX; - -/** - * Created by mikepenz on 13.07.15. - */ -public class DimenHolder extends com.mikepenz.materialize.holder.DimenHolder { - public DimenHolder() { - - } - - public static DimenHolder fromPixel(@Dimension(unit = PX) int pixel) { - DimenHolder dimenHolder = new DimenHolder(); - dimenHolder.setPixel(pixel); - return dimenHolder; - } - - public static DimenHolder fromDp(@Dimension(unit = DP) int dp) { - DimenHolder dimenHolder = new DimenHolder(); - dimenHolder.setDp(dp); - return dimenHolder; - } - - public static DimenHolder fromResource(@DimenRes int resource) { - DimenHolder dimenHolder = new DimenHolder(); - dimenHolder.setResource(resource); - return dimenHolder; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/ImageHolder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/ImageHolder.java deleted file mode 100644 index 02f3a035..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/ImageHolder.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.mikepenz.materialdrawer.holder; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import androidx.annotation.DrawableRes; -import androidx.appcompat.content.res.AppCompatResources; -import android.view.View; -import android.widget.ImageView; - -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.util.DrawerImageLoader; - -import java.io.FileNotFoundException; -import java.io.InputStream; - -/** - * Created by mikepenz on 13.07.15. - */ - -public class ImageHolder extends com.mikepenz.materialize.holder.ImageHolder { - private IIcon mIIcon; - - public ImageHolder(String url) { - super(url); - } - - public ImageHolder(Uri uri) { - super(uri); - } - - public ImageHolder(Drawable icon) { - super(icon); - } - - public ImageHolder(Bitmap bitmap) { - super(bitmap); - } - - public ImageHolder(@DrawableRes int iconRes) { - super(iconRes); - } - - public ImageHolder(IIcon iicon) { - super((Bitmap) null); - this.mIIcon = iicon; - } - - public IIcon getIIcon() { - return mIIcon; - } - - public void setIIcon(IIcon mIIcon) { - this.mIIcon = mIIcon; - } - - /** - * sets an existing image to the imageView - * - * @param imageView - * @param tag used to identify imageViews and define different placeholders - * @return true if an image was set - */ - @Override - public boolean applyTo(ImageView imageView, String tag) { - if (getUri() != null) { - boolean consumed = DrawerImageLoader.getInstance().setImage(imageView, getUri(), tag); - if (!consumed) { - imageView.setImageURI(getUri()); - } - } else if (getIcon() != null) { - imageView.setImageDrawable(getIcon()); - } else if (getBitmap() != null) { - imageView.setImageBitmap(getBitmap()); - } else if (getIconRes() != -1) { - imageView.setImageResource(getIconRes()); - } else if (mIIcon != null) { - imageView.setImageDrawable(new IconicsDrawable(imageView.getContext(), mIIcon).actionBar()); - } else { - imageView.setImageBitmap(null); - return false; - } - return true; - } - - /** - * this only handles Drawables - * - * @param ctx - * @param iconColor - * @param tint - * @return - */ - public Drawable decideIcon(Context ctx, int iconColor, boolean tint, int paddingDp) { - Drawable icon = getIcon(); - - if (mIIcon != null) { - icon = new IconicsDrawable(ctx, mIIcon).color(iconColor).sizeDp(24).paddingDp(paddingDp); - } else if (getIconRes() != -1) { - icon = AppCompatResources.getDrawable(ctx, getIconRes()); - } else if (getUri() != null) { - try { - InputStream inputStream = ctx.getContentResolver().openInputStream(getUri()); - icon = Drawable.createFromStream(inputStream, getUri().toString()); - } catch (FileNotFoundException e) { - //no need to handle this - } - } - - //if we got an icon AND we have auto tinting enabled AND it is no IIcon, tint it ;) - if (icon != null && tint && mIIcon == null) { - icon = icon.mutate(); - icon.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); - } - - return icon; - } - - /** - * a small static helper which catches nulls for us - * - * @param imageHolder - * @param ctx - * @param iconColor - * @param tint - * @return - */ - public static Drawable decideIcon(ImageHolder imageHolder, Context ctx, int iconColor, boolean tint, int paddingDp) { - if (imageHolder == null) { - return null; - } else { - return imageHolder.decideIcon(ctx, iconColor, tint, paddingDp); - } - } - - /** - * decides which icon to apply or hide this view - * - * @param imageHolder - * @param imageView - * @param iconColor - * @param tint - * @param paddingDp - */ - public static void applyDecidedIconOrSetGone(ImageHolder imageHolder, ImageView imageView, int iconColor, boolean tint, int paddingDp) { - if (imageHolder != null && imageView != null) { - Drawable drawable = ImageHolder.decideIcon(imageHolder, imageView.getContext(), iconColor, tint, paddingDp); - if (drawable != null) { - imageView.setImageDrawable(drawable); - imageView.setVisibility(View.VISIBLE); - } else if (imageHolder.getBitmap() != null) { - imageView.setImageBitmap(imageHolder.getBitmap()); - imageView.setVisibility(View.VISIBLE); - } else { - imageView.setVisibility(View.GONE); - } - } else if (imageView != null) { - imageView.setVisibility(View.GONE); - } - } - -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/StringHolder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/StringHolder.java deleted file mode 100644 index 49a4a356..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/holder/StringHolder.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mikepenz.materialdrawer.holder; - -import androidx.annotation.StringRes; - -/** - * Created by mikepenz on 13.07.15. - */ -public class StringHolder extends com.mikepenz.materialize.holder.StringHolder { - public StringHolder(CharSequence text) { - super(text); - } - - public StringHolder(@StringRes int textRes) { - super(textRes); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/icons/MaterialDrawerFont.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/icons/MaterialDrawerFont.java deleted file mode 100644 index 1e8edc50..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/icons/MaterialDrawerFont.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.mikepenz.materialdrawer.icons; - -import android.content.Context; -import android.graphics.Typeface; - -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.iconics.typeface.ITypeface; - -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; - -/** - * Created by mikepenz on 01.11.14. - */ -public class MaterialDrawerFont implements ITypeface { - private static final String TTF_FILE = "materialdrawerfont-font-v5.0.0.ttf"; - - private static Typeface typeface = null; - - private static HashMap mChars; - - @Override - public IIcon getIcon(String key) { - return Icon.valueOf(key); - } - - @Override - public HashMap getCharacters() { - if (mChars == null) { - HashMap aChars = new HashMap(); - for (Icon v : Icon.values()) { - aChars.put(v.name(), v.character); - } - mChars = aChars; - } - - return mChars; - } - - @Override - public String getMappingPrefix() { - return "mdf"; - } - - @Override - public String getFontName() { - return "MaterialDrawerFont"; - } - - @Override - public String getVersion() { - return "5.0.0"; - } - - @Override - public int getIconCount() { - return mChars.size(); - } - - @Override - public Collection getIcons() { - Collection icons = new LinkedList(); - - for (Icon value : Icon.values()) { - icons.add(value.name()); - } - - return icons; - } - - - @Override - public String getAuthor() { - return ""; - } - - @Override - public String getUrl() { - return ""; - } - - @Override - public String getDescription() { - return ""; - } - - @Override - public String getLicense() { - return ""; - } - - @Override - public String getLicenseUrl() { - return ""; - } - - @Override - public Typeface getTypeface(Context context) { - if (typeface == null) { - try { - typeface = Typeface.createFromAsset(context.getAssets(), "fonts/" + TTF_FILE); - } catch (Exception e) { - return null; - } - } - return typeface; - } - - public enum Icon implements IIcon { - mdf_arrow_drop_down('\ue5c5'), - mdf_arrow_drop_up('\ue5c7'), - mdf_expand_less('\ue5ce'), - mdf_expand_more('\ue5cf'), - mdf_person('\ue7fd'); - - char character; - - Icon(char character) { - this.character = character; - } - - public String getFormattedName() { - return "{" + name() + "}"; - } - - public char getCharacter() { - return character; - } - - public String getName() { - return name(); - } - - // remember the typeface so we can use it later - private static ITypeface typeface; - - public ITypeface getTypeface() { - if (typeface == null) { - typeface = new MaterialDrawerFont(); - } - return typeface; - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/interfaces/ICrossfader.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/interfaces/ICrossfader.java deleted file mode 100644 index 3ee11bfa..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/interfaces/ICrossfader.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.mikepenz.materialdrawer.interfaces; - -/** - * Created by mikepenz on 18.07.15. - */ -public interface ICrossfader { - void crossfade(); - - boolean isCrossfaded(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/interfaces/OnCheckedChangeListener.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/interfaces/OnCheckedChangeListener.java deleted file mode 100644 index b34e5373..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/interfaces/OnCheckedChangeListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.mikepenz.materialdrawer.interfaces; - -import android.widget.CompoundButton; - -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; - -/** - * Interface definition for a callback to be invoked when the checked state - * of a compound button changed. - */ -public interface OnCheckedChangeListener { - /** - * Called when the checked state of a compound button has changed. - * - * @param buttonView The compound button view whose state has changed. - * @param isChecked The new checked state of buttonView. - */ - void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView, boolean isChecked); -} \ No newline at end of file diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractBadgeableDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractBadgeableDrawerItem.java deleted file mode 100644 index 170fa8e9..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractBadgeableDrawerItem.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import androidx.annotation.LayoutRes; -import androidx.annotation.StringRes; -import android.view.View; -import android.widget.TextView; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.BadgeStyle; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public abstract class AbstractBadgeableDrawerItem extends BaseDescribeableDrawerItem implements ColorfulBadgeable { - protected StringHolder mBadge; - protected BadgeStyle mBadgeStyle = new BadgeStyle(); - - @Override - public Item withBadge(StringHolder badge) { - this.mBadge = badge; - return (Item) this; - } - - @Override - public Item withBadge(String badge) { - this.mBadge = new StringHolder(badge); - return (Item) this; - } - - @Override - public Item withBadge(@StringRes int badgeRes) { - this.mBadge = new StringHolder(badgeRes); - return (Item) this; - } - - @Override - public Item withBadgeStyle(BadgeStyle badgeStyle) { - this.mBadgeStyle = badgeStyle; - return (Item) this; - } - - public StringHolder getBadge() { - return mBadge; - } - - public BadgeStyle getBadgeStyle() { - return mBadgeStyle; - } - - @Override - public int getType() { - return R.id.material_drawer_item_primary;/*"PRIMARY_ITEM"*/ - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_primary; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - //bind the basic view parts - bindViewHelper(viewHolder); - - //set the text for the badge or hide - boolean badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge); - //style the badge if it is visible - if (badgeVisible) { - mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))); - viewHolder.badgeContainer.setVisibility(View.VISIBLE); - } else { - viewHolder.badgeContainer.setVisibility(View.GONE); - } - - //define the typeface for our textViews - if (getTypeface() != null) { - viewHolder.badge.setTypeface(getTypeface()); - } - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends BaseViewHolder { - private View badgeContainer; - private TextView badge; - - public ViewHolder(View view) { - super(view); - this.badgeContainer = view.findViewById(R.id.material_drawer_badge_container); - this.badge = (TextView) view.findViewById(R.id.material_drawer_badge); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractDrawerItem.java deleted file mode 100644 index eb5f7c43..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractDrawerItem.java +++ /dev/null @@ -1,449 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.OnPostBindViewListener; -import com.mikepenz.materialdrawer.model.interfaces.Selectable; -import com.mikepenz.materialdrawer.model.interfaces.Tagable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import androidx.annotation.CallSuper; -import androidx.recyclerview.widget.RecyclerView; - -/** - * Created by mikepenz on 14.07.15. - */ -public abstract class AbstractDrawerItem implements IDrawerItem, Selectable, Tagable { - - protected boolean mExcludeFromMiniDrawer = false; - - @Override - public boolean isExcludeFromMiniDrawer() { - return mExcludeFromMiniDrawer; - } - - // the identifier for this item - protected long mIdentifier = -1; - - /** - * set the identifier of this item - * - * @param identifier - * @return - */ - public T withIdentifier(long identifier) { - this.mIdentifier = identifier; - return (T) this; - } - - /** - * returns the identifier of this item - * -1 is the default not set state - * - * @return - */ - @Override - public long getIdentifier() { - return mIdentifier; - } - - // the tag for this item - protected Object mTag; - - /** - * set the tag of this item - * - * @param object - * @return - */ - public T withTag(Object object) { - this.mTag = object; - return (T) this; - } - - /** - * @return the tag of this item - */ - @Override - public Object getTag() { - return mTag; - } - - // defines if this item is enabled - protected boolean mEnabled = true; - - /** - * set if this item is enabled - * - * @param enabled true if this item is enabled - * @return - */ - public T withEnabled(boolean enabled) { - this.mEnabled = enabled; - return (T) this; - } - - /** - * @return if this item is enabled - */ - @Override - public boolean isEnabled() { - return mEnabled; - } - - // defines if the item is selected - protected boolean mSelected = false; - - /** - * set if this item is selected - * - * @param selected true if this item is selected - * @return - */ - @Override - public T withSetSelected(boolean selected) { - this.mSelected = selected; - return (T) this; - } - - /** - * @return if this item is selected - */ - @Override - public boolean isSelected() { - return mSelected; - } - - // defines if this item is selectable - protected boolean mSelectable = true; - - /** - * set if this item is selectable - * - * @param selectable true if this item is selectable - * @return - */ - @Override - public T withSelectable(boolean selectable) { - this.mSelectable = selectable; - return (T) this; - } - - /** - * @return if this item is selectable - */ - @Override - public boolean isSelectable() { - return mSelectable; - } - - // defines if the item's background' change should be animated when it is (de)selected - protected boolean mSelectedBackgroundAnimated = true; - - /** - * set if this item is selectable - * - * @param selectedBackgroundAnimated true if this item's background should fade when it is (de) selected - * @return - */ - public T withSelectedBackgroundAnimated(boolean selectedBackgroundAnimated) { - this.mSelectedBackgroundAnimated = selectedBackgroundAnimated; - return (T) this; - } - - /** - * @return if this item is selectable - */ - public boolean isSelectedBackgroundAnimated() { - return mSelectedBackgroundAnimated; - } - - public Drawer.OnDrawerItemClickListener mOnDrawerItemClickListener = null; - - public Drawer.OnDrawerItemClickListener getOnDrawerItemClickListener() { - return mOnDrawerItemClickListener; - } - - /** - * this listener is called when an item is clicked in the drawer. - * WARNING: don't overwrite this in the Switch / Toggle drawerItems if you want the toggle / switch to be selected - * if the item is clicked and the item is not selectable. - * - * @param onDrawerItemClickListener - * @return - */ - public T withOnDrawerItemClickListener(Drawer.OnDrawerItemClickListener onDrawerItemClickListener) { - this.mOnDrawerItemClickListener = onDrawerItemClickListener; - return (T) this; - } - - protected OnPostBindViewListener mOnPostBindViewListener = null; - - public OnPostBindViewListener getOnPostBindViewListener() { - return mOnPostBindViewListener; - } - - /** - * add this listener and hook in if you want to modify a drawerItems view without creating a custom drawer item - * - * @param onPostBindViewListener - * @return - */ - public T withPostOnBindViewListener(OnPostBindViewListener onPostBindViewListener) { - this.mOnPostBindViewListener = onPostBindViewListener; - return (T) this; - } - - /** - * is called after bindView to allow some post creation setps - * - * @param drawerItem the drawerItem which is bound to the view - * @param view the currently view which will be bound - */ - public void onPostBindView(IDrawerItem drawerItem, View view) { - if (mOnPostBindViewListener != null) { - mOnPostBindViewListener.onBindView(drawerItem, view); - } - } - - // the parent of this item - private IDrawerItem mParent; - - /** - * @return the parent of this item - */ - @Override - public IDrawerItem getParent() { - return mParent; - } - - /** - * the parent for this item - * - * @param parent it's parent - * @return this - */ - @Override - public IDrawerItem withParent(IDrawerItem parent) { - this.mParent = parent; - return this; - } - - // the subItems to expand for this item - protected List mSubItems; - - /** - * a list of subItems - * **WARNING** Make sure the subItems provided already have identifiers - * - * @param subItems - * @return - */ - public T withSubItems(List subItems) { - this.mSubItems = subItems; - for (IDrawerItem subItem : subItems) { - subItem.withParent(this); - } - return (T) this; - } - - /** - * an array of subItems - * **WARNING** Make sure the subItems provided already have identifiers - * - * @param subItems - * @return - */ - public T withSubItems(IDrawerItem... subItems) { - if (mSubItems == null) { - mSubItems = new ArrayList<>(); - } - for (IDrawerItem subItem : subItems) { - subItem.withParent(this); - } - Collections.addAll(mSubItems, subItems); - return (T) this; - } - - /** - * @return the subItems for this item - */ - @Override - public List getSubItems() { - return mSubItems; - } - - //if the this item is currently expanded - private boolean mExpanded = false; - - /** - * @param expanded defines if this item is now expanded or not - * @return this - */ - @Override - public T withIsExpanded(boolean expanded) { - mExpanded = expanded; - return (T) this; - } - - /** - * @return if this item is currently expaneded - */ - @Override - public boolean isExpanded() { - return mExpanded; - } - - - /** - * overwrite this method and return true if the item should auto expand on click, false if you want to disable this - * - * @return true if this item should auto expand in the adapter - */ - @Override - public boolean isAutoExpanding() { - return true; - } - - /** - * generates a view by the defined LayoutRes - * - * @param ctx - * @return - */ - @Override - public View generateView(Context ctx) { - VH viewHolder = getViewHolder(LayoutInflater.from(ctx).inflate(getLayoutRes(), null, false)); - bindView(viewHolder, Collections.emptyList()); - return viewHolder.itemView; - } - - /** - * generates a view by the defined LayoutRes and pass the LayoutParams from the parent - * - * @param ctx - * @param parent - * @return - */ - @Override - public View generateView(Context ctx, ViewGroup parent) { - VH viewHolder = getViewHolder(LayoutInflater.from(ctx).inflate(getLayoutRes(), parent, false)); - bindView(viewHolder, Collections.emptyList()); - return viewHolder.itemView; - } - - @CallSuper - @Override - public void bindView(VH holder, List payloads) { - holder.itemView.setTag(R.id.material_drawer_item, this); - } - - /** - * called when the view is unbound - * - * @param holder - */ - @Override - public void unbindView(VH holder) { - holder.itemView.clearAnimation(); - } - - /** - * View got attached to the window - * - * @param holder - */ - @Override - public void attachToWindow(VH holder) { - - } - - /** - * View got detached from the window - * - * @param holder - */ - @Override - public void detachFromWindow(VH holder) { - - } - - /** - * is called when the ViewHolder is in a transient state. return true if you want to reuse - * that view anyways - * - * @param holder the viewHolder for the view which failed to recycle - * @return true if we want to recycle anyways (false - it get's destroyed) - */ - @Override - public boolean failedToRecycle(VH holder) { - return false; - } - - /** - * This method returns the ViewHolder for our item, using the provided View. - * - * @param parent - * @return the ViewHolder for this Item - */ - @Override - public VH getViewHolder(ViewGroup parent) { - return getViewHolder(LayoutInflater.from(parent.getContext()).inflate(getLayoutRes(), parent, false)); - } - - /** - * This method returns the ViewHolder for our item, using the provided View. - * - * @param v - * @return the ViewHolder for this Item - */ - public abstract VH getViewHolder(View v); - - /** - * If this item equals to the given identifier - * - * @param id - * @return - */ - @Override - public boolean equals(long id) { - return id == mIdentifier; - } - - public boolean equals(int id) { - return id == mIdentifier; - } - - /** - * If this item equals to the given object - * - * @param o - * @return - */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractDrawerItem that = (AbstractDrawerItem) o; - return mIdentifier == that.mIdentifier; - } - - /** - * the hashCode implementation - * - * @return - */ - @Override - public int hashCode() { - return Long.valueOf(mIdentifier).hashCode(); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractSwitchableDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractSwitchableDrawerItem.java deleted file mode 100644 index a41ed90a..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractSwitchableDrawerItem.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import androidx.annotation.LayoutRes; -import androidx.appcompat.widget.SwitchCompat; -import android.view.View; -import android.widget.CompoundButton; - -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.interfaces.OnCheckedChangeListener; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public abstract class AbstractSwitchableDrawerItem extends BaseDescribeableDrawerItem { - - private boolean switchEnabled = true; - - private boolean checked = false; - private OnCheckedChangeListener onCheckedChangeListener = null; - - public Item withChecked(boolean checked) { - this.checked = checked; - return (Item) this; - } - - public Item withSwitchEnabled(boolean switchEnabled) { - this.switchEnabled = switchEnabled; - return (Item) this; - } - - public Item withOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) { - this.onCheckedChangeListener = onCheckedChangeListener; - return (Item) this; - } - - public Item withCheckable(boolean checkable) { - return withSelectable(checkable); - } - - public boolean isChecked() { - return checked; - } - - public boolean isSwitchEnabled() { - return switchEnabled; - } - - public OnCheckedChangeListener getOnCheckedChangeListener() { - return onCheckedChangeListener; - } - - @Override - public int getType() { - return R.id.material_drawer_item_primary_switch; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_switch; - } - - @Override - public void bindView(final ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - //bind the basic view parts - bindViewHelper(viewHolder); - - //handle the switch - viewHolder.switchView.setOnCheckedChangeListener(null); - viewHolder.switchView.setChecked(checked); - viewHolder.switchView.setOnCheckedChangeListener(checkedChangeListener); - viewHolder.switchView.setEnabled(switchEnabled); - - //add a onDrawerItemClickListener here to be able to check / uncheck if the drawerItem can't be selected - withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { - @Override - public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { - if (!isSelectable()) { - checked = !checked; - viewHolder.switchView.setChecked(checked); - } - - return false; - } - }); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends BaseViewHolder { - private SwitchCompat switchView; - - private ViewHolder(View view) { - super(view); - this.switchView = (SwitchCompat) view.findViewById(R.id.material_drawer_switch); - } - } - - private CompoundButton.OnCheckedChangeListener checkedChangeListener = new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isEnabled()) { - checked = isChecked; - if (getOnCheckedChangeListener() != null) { - getOnCheckedChangeListener().onCheckedChanged(AbstractSwitchableDrawerItem.this, buttonView, isChecked); - } - } else { - buttonView.setOnCheckedChangeListener(null); - buttonView.setChecked(!isChecked); - buttonView.setOnCheckedChangeListener(checkedChangeListener); - } - } - }; -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractToggleableDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractToggleableDrawerItem.java deleted file mode 100644 index eaccfd47..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/AbstractToggleableDrawerItem.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import androidx.annotation.LayoutRes; -import android.view.View; -import android.widget.CompoundButton; -import android.widget.ToggleButton; - -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.interfaces.OnCheckedChangeListener; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public class AbstractToggleableDrawerItem extends BaseDescribeableDrawerItem { - private boolean toggleEnabled = true; - - private boolean checked = false; - private OnCheckedChangeListener onCheckedChangeListener = null; - - public Item withChecked(boolean checked) { - this.checked = checked; - return (Item) this; - } - - public Item withToggleEnabled(boolean toggleEnabled) { - this.toggleEnabled = toggleEnabled; - return (Item) this; - } - - public Item withOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) { - this.onCheckedChangeListener = onCheckedChangeListener; - return (Item) this; - } - - public boolean isChecked() { - return checked; - } - - public void setChecked(boolean checked) { - this.checked = checked; - } - - public boolean isToggleEnabled() { - return toggleEnabled; - } - - public void setToggleEnabled(boolean toggleEnabled) { - this.toggleEnabled = toggleEnabled; - } - - public OnCheckedChangeListener getOnCheckedChangeListener() { - return onCheckedChangeListener; - } - - public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) { - this.onCheckedChangeListener = onCheckedChangeListener; - } - - @Override - public int getType() { - return R.id.material_drawer_item_primary_toggle; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_toggle; - } - - @Override - public void bindView(final ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - //bind the basic view parts - bindViewHelper(viewHolder); - - //handle the toggle - viewHolder.toggle.setOnCheckedChangeListener(null); - viewHolder.toggle.setChecked(checked); - viewHolder.toggle.setOnCheckedChangeListener(checkedChangeListener); - viewHolder.toggle.setEnabled(toggleEnabled); - - //add a onDrawerItemClickListener here to be able to check / uncheck if the drawerItem can't be selected - withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { - @Override - public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { - if (!isSelectable()) { - checked = !checked; - viewHolder.toggle.setChecked(checked); - } - - return false; - } - }); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends BaseViewHolder { - private ToggleButton toggle; - - private ViewHolder(View view) { - super(view); - this.toggle = (ToggleButton) view.findViewById(R.id.material_drawer_toggle); - } - } - - private CompoundButton.OnCheckedChangeListener checkedChangeListener = new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isEnabled()) { - checked = isChecked; - if (getOnCheckedChangeListener() != null) { - getOnCheckedChangeListener().onCheckedChanged(AbstractToggleableDrawerItem.this, buttonView, isChecked); - } - } else { - buttonView.setOnCheckedChangeListener(null); - buttonView.setChecked(!isChecked); - buttonView.setOnCheckedChangeListener(checkedChangeListener); - } - } - }; -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseDescribeableDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseDescribeableDrawerItem.java deleted file mode 100644 index da6c1799..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseDescribeableDrawerItem.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.drawable.Drawable; - -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.StringRes; - -import static com.mikepenz.materialdrawer.util.DrawerUIUtils.themeDrawerItem; - -/** - * Created by mikepenz on 03.02.15. - */ -public abstract class BaseDescribeableDrawerItem extends BaseDrawerItem { - private StringHolder description; - private ColorHolder descriptionTextColor; - - @Override - public boolean isExcludeFromMiniDrawer() { - return this.excludeFromMiniDrawer; - } - - public T withDescription(String description) { - this.description = new StringHolder(description); - return (T) this; - } - - public T withDescription(@StringRes int descriptionRes) { - this.description = new StringHolder(descriptionRes); - return (T) this; - } - - public T withDescriptionTextColor(@ColorInt int color) { - this.descriptionTextColor = ColorHolder.fromColor(color); - return (T) this; - } - - public T withDescriptionTextColorRes(@ColorRes int colorRes) { - this.descriptionTextColor = ColorHolder.fromColorRes(colorRes); - return (T) this; - } - - public StringHolder getDescription() { - return description; - } - - public ColorHolder getDescriptionTextColor() { - return descriptionTextColor; - } - - /** - * a helper method to have the logic for all secondaryDrawerItems only once - * - * @param viewHolder - */ - protected void bindViewHelper(BaseViewHolder viewHolder) { - Context ctx = viewHolder.itemView.getContext(); - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //set the item selected if it is - viewHolder.itemView.setSelected(isSelected()); - - //set the item enabled if it is - viewHolder.itemView.setEnabled(isEnabled()); - - //get the correct color for the background - int selectedColor = getSelectedColor(ctx); - //get the correct color for the text - int color = getColor(ctx); - ColorStateList selectedTextColor = getTextColorStateList(color, getSelectedTextColor(ctx)); - //get the correct color for the icon - int iconColor = getIconColor(ctx); - int selectedIconColor = getSelectedIconColor(ctx); - - //set the background for the item - themeDrawerItem(ctx, viewHolder.view, selectedColor, isSelectedBackgroundAnimated()); - //set the text for the name - StringHolder.applyTo(this.getName(), viewHolder.name); - //set the text for the description or hide - StringHolder.applyToOrHide(this.getDescription(), viewHolder.description); - - //set the colors for textViews - viewHolder.name.setTextColor(selectedTextColor); - //set the description text color - ColorHolder.applyToOr(getDescriptionTextColor(), viewHolder.description, selectedTextColor); - - //define the typeface for our textViews - if (getTypeface() != null) { - viewHolder.name.setTypeface(getTypeface()); - viewHolder.description.setTypeface(getTypeface()); - } - - //get the drawables for our icon and set it - Drawable icon = ImageHolder.decideIcon(getIcon(), ctx, iconColor, isIconTinted(), 1); - if (icon != null) { - Drawable selectedIcon = ImageHolder.decideIcon(getSelectedIcon(), ctx, selectedIconColor, isIconTinted(), 1); - ImageHolder.applyMultiIconTo(icon, iconColor, selectedIcon, selectedIconColor, isIconTinted(), viewHolder.icon); - } else { - ImageHolder.applyDecidedIconOrSetGone(getIcon(), viewHolder.icon, iconColor, isIconTinted(), 1); - } - - //for android API 17 --> Padding not applied via xml - DrawerUIUtils.setDrawerVerticalPadding(viewHolder.view, level); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseDrawerItem.java deleted file mode 100644 index 0550f2cb..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseDrawerItem.java +++ /dev/null @@ -1,356 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.Pair; - -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.Iconable; -import com.mikepenz.materialdrawer.model.interfaces.Nameable; -import com.mikepenz.materialdrawer.model.interfaces.Tagable; -import com.mikepenz.materialdrawer.model.interfaces.Typefaceable; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DrawableRes; -import androidx.annotation.StringRes; -import androidx.recyclerview.widget.RecyclerView; - -import static com.mikepenz.materialdrawer.util.DrawerUIUtils.getBooleanStyleable; - -/** - * Created by mikepenz on 03.02.15. - */ -public abstract class BaseDrawerItem extends AbstractDrawerItem implements Nameable, Iconable, Tagable, Typefaceable { - protected ImageHolder icon; - protected ImageHolder selectedIcon; - protected StringHolder name; - - protected boolean iconTinted = false; - - protected ColorHolder selectedColor; - protected ColorHolder textColor; - protected ColorHolder selectedTextColor; - protected ColorHolder disabledTextColor; - - protected ColorHolder iconColor; - protected ColorHolder selectedIconColor; - protected ColorHolder disabledIconColor; - - protected Typeface typeface = null; - - protected Pair colorStateList; - - protected int level = 1; - - protected boolean excludeFromMiniDrawer = false; - - public T withIcon(ImageHolder icon) { - this.icon = icon; - return (T) this; - } - - public T withIcon(Drawable icon) { - this.icon = new ImageHolder(icon); - return (T) this; - } - - public T withIcon(@DrawableRes int iconRes) { - this.icon = new ImageHolder(iconRes); - return (T) this; - } - - public T withSelectedIcon(Drawable selectedIcon) { - this.selectedIcon = new ImageHolder(selectedIcon); - return (T) this; - } - - public T withSelectedIcon(@DrawableRes int selectedIconRes) { - this.selectedIcon = new ImageHolder(selectedIconRes); - return (T) this; - } - - public T withIcon(IIcon iicon) { - this.icon = new ImageHolder(iicon); - //if we are on api 21 or higher we use the IconicsDrawable for selection too and color it with the correct color - //else we use just the one drawable and enable tinting on press - if (Build.VERSION.SDK_INT >= 21) { - this.selectedIcon = new ImageHolder(iicon); - } else { - this.withIconTintingEnabled(true); - } - - return (T) this; - } - - public T withName(StringHolder name) { - this.name = name; - return (T) this; - } - - public T withName(String name) { - this.name = new StringHolder(name); - return (T) this; - } - - public T withName(@StringRes int nameRes) { - this.name = new StringHolder(nameRes); - return (T) this; - } - - public T withSelectedColor(@ColorInt int selectedColor) { - this.selectedColor = ColorHolder.fromColor(selectedColor); - return (T) this; - } - - public T withSelectedColorRes(@ColorRes int selectedColorRes) { - this.selectedColor = ColorHolder.fromColorRes(selectedColorRes); - return (T) this; - } - - public T withTextColor(@ColorInt int textColor) { - this.textColor = ColorHolder.fromColor(textColor); - return (T) this; - } - - public T withTextColorRes(@ColorRes int textColorRes) { - this.textColor = ColorHolder.fromColorRes(textColorRes); - return (T) this; - } - - public T withSelectedTextColor(@ColorInt int selectedTextColor) { - this.selectedTextColor = ColorHolder.fromColor(selectedTextColor); - return (T) this; - } - - public T withSelectedTextColorRes(@ColorRes int selectedColorRes) { - this.selectedTextColor = ColorHolder.fromColorRes(selectedColorRes); - return (T) this; - } - - public T withDisabledTextColor(@ColorInt int disabledTextColor) { - this.disabledTextColor = ColorHolder.fromColor(disabledTextColor); - return (T) this; - } - - public T withDisabledTextColorRes(@ColorRes int disabledTextColorRes) { - this.disabledTextColor = ColorHolder.fromColorRes(disabledTextColorRes); - return (T) this; - } - - public T withIconColor(@ColorInt int iconColor) { - this.iconColor = ColorHolder.fromColor(iconColor); - return (T) this; - } - - public T withIconColorRes(@ColorRes int iconColorRes) { - this.iconColor = ColorHolder.fromColorRes(iconColorRes); - return (T) this; - } - - public T withSelectedIconColor(@ColorInt int selectedIconColor) { - this.selectedIconColor = ColorHolder.fromColor(selectedIconColor); - return (T) this; - } - - public T withSelectedIconColorRes(@ColorRes int selectedColorRes) { - this.selectedIconColor = ColorHolder.fromColorRes(selectedColorRes); - return (T) this; - } - - public T withDisabledIconColor(@ColorInt int disabledIconColor) { - this.disabledIconColor = ColorHolder.fromColor(disabledIconColor); - return (T) this; - } - - public T withDisabledIconColorRes(@ColorRes int disabledIconColorRes) { - this.disabledIconColor = ColorHolder.fromColorRes(disabledIconColorRes); - return (T) this; - } - - /** - * will tint the icon with the default (or set) colors - * (default and selected state) - * - * @param iconTintingEnabled - * @return - */ - public T withIconTintingEnabled(boolean iconTintingEnabled) { - this.iconTinted = iconTintingEnabled; - return (T) this; - } - - @Deprecated - public T withIconTinted(boolean iconTinted) { - this.iconTinted = iconTinted; - return (T) this; - } - - /** - * for backwards compatibility - withIconTinted.. - * - * @param iconTinted - * @return - */ - @Deprecated - public T withTintSelectedIcon(boolean iconTinted) { - return withIconTintingEnabled(iconTinted); - } - - public T withTypeface(Typeface typeface) { - this.typeface = typeface; - return (T) this; - } - - public T withLevel(int level) { - this.level = level; - return (T) this; - } - - public T withExcludeFromMiniDrawer(boolean excludeFromMiniDrawer) { - this.excludeFromMiniDrawer = excludeFromMiniDrawer; - return (T) this; - } - - - public ColorHolder getSelectedColor() { - return selectedColor; - } - - public ColorHolder getTextColor() { - return textColor; - } - - public ColorHolder getSelectedTextColor() { - return selectedTextColor; - } - - public ColorHolder getDisabledTextColor() { - return disabledTextColor; - } - - public boolean isIconTinted() { - return iconTinted; - } - - public ImageHolder getIcon() { - return icon; - } - - public ImageHolder getSelectedIcon() { - return selectedIcon; - } - - public StringHolder getName() { - return name; - } - - public ColorHolder getDisabledIconColor() { - return disabledIconColor; - } - - public ColorHolder getSelectedIconColor() { - return selectedIconColor; - } - - public ColorHolder getIconColor() { - return iconColor; - } - - public Typeface getTypeface() { - return typeface; - } - - public int getLevel() { - return level; - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getSelectedColor(Context ctx) { - if (getBooleanStyleable(ctx, R.styleable.MaterialDrawer_material_drawer_legacy_style, false)) { - return ColorHolder.color(getSelectedColor(), ctx, R.attr.material_drawer_selected_legacy, R.color.material_drawer_selected_legacy); - } else { - return ColorHolder.color(getSelectedColor(), ctx, R.attr.material_drawer_selected, R.color.material_drawer_selected); - } - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getColor(Context ctx) { - int color; - if (this.isEnabled()) { - color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_primary_text, R.color.material_drawer_primary_text); - } else { - color = ColorHolder.color(getDisabledTextColor(), ctx, R.attr.material_drawer_hint_text, R.color.material_drawer_hint_text); - } - return color; - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getSelectedTextColor(Context ctx) { - return ColorHolder.color(getSelectedTextColor(), ctx, R.attr.material_drawer_selected_text, R.color.material_drawer_selected_text); - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - public int getIconColor(Context ctx) { - int iconColor; - if (this.isEnabled()) { - iconColor = ColorHolder.color(getIconColor(), ctx, R.attr.material_drawer_primary_icon, R.color.material_drawer_primary_icon); - } else { - iconColor = ColorHolder.color(getDisabledIconColor(), ctx, R.attr.material_drawer_hint_icon, R.color.material_drawer_hint_icon); - } - return iconColor; - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getSelectedIconColor(Context ctx) { - return ColorHolder.color(getSelectedIconColor(), ctx, R.attr.material_drawer_selected_text, R.color.material_drawer_selected_text); - } - - /** - * helper to get the ColorStateList for the text and remembering it so we do not have to recreate it all the time - * - * @param color - * @param selectedTextColor - * @return - */ - protected ColorStateList getTextColorStateList(@ColorInt int color, @ColorInt int selectedTextColor) { - if (colorStateList == null || color + selectedTextColor != colorStateList.first) { - colorStateList = new Pair<>(color + selectedTextColor, DrawerUIUtils.getTextColorStateList(color, selectedTextColor)); - } - - return colorStateList.second; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseViewHolder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseViewHolder.java deleted file mode 100644 index a49e3a4d..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/BaseViewHolder.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.mikepenz.materialdrawer.R; - -import androidx.recyclerview.widget.RecyclerView; - -public class BaseViewHolder extends RecyclerView.ViewHolder { - protected View view; - protected ImageView icon; - protected TextView name; - protected TextView description; - - public BaseViewHolder(View view) { - super(view); - - this.view = view; - this.icon = view.findViewById(R.id.material_drawer_icon); - this.name = view.findViewById(R.id.material_drawer_name); - this.description = view.findViewById(R.id.material_drawer_description); - } -} \ No newline at end of file diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ContainerDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ContainerDrawerItem.java deleted file mode 100644 index c636dbf8..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ContainerDrawerItem.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import androidx.annotation.LayoutRes; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.DimenHolder; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public class ContainerDrawerItem extends AbstractDrawerItem { - - private DimenHolder mHeight; - - public ContainerDrawerItem withHeight(DimenHolder height) { - mHeight = height; - return this; - } - - public DimenHolder getHeight() { - return mHeight; - } - - private View mView; - - public ContainerDrawerItem withView(View view) { - this.mView = view; - return this; - } - - public View getView() { - return mView; - } - - public enum Position { - TOP, - BOTTOM, - NONE; - } - - private Position mViewPosition = Position.TOP; - - public ContainerDrawerItem withViewPosition(Position position) { - this.mViewPosition = position; - return this; - } - - private boolean mDivider = true; - - public ContainerDrawerItem withDivider(boolean divider) { - this.mDivider = divider; - return this; - } - - public Position getViewPosition() { - return mViewPosition; - } - - @Override - public int getType() { - return R.id.material_drawer_item_container; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_container; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //define how the divider should look like - viewHolder.view.setEnabled(false); - - //make sure our view is not used in another parent - if (mView.getParent() != null) { - ((ViewGroup) mView.getParent()).removeView(mView); - } - - //set the height - int height = ViewGroup.LayoutParams.WRAP_CONTENT; - if (mHeight != null) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) viewHolder.view.getLayoutParams(); - height = mHeight.asPixel(ctx); - lp.height = height; - viewHolder.view.setLayoutParams(lp); - } - - //make sure the header view is empty - ((ViewGroup) viewHolder.view).removeAllViews(); - - int dividerHeight = 0; - if (mDivider) { - dividerHeight = 1; - } - - View divider = new View(ctx); - divider.setMinimumHeight(dividerHeight); - divider.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(ctx, R.attr.material_drawer_divider, R.color.material_drawer_divider)); - - LinearLayout.LayoutParams dividerParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) UIUtils.convertDpToPixel(dividerHeight, ctx)); - LinearLayout.LayoutParams viewParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeight != null ? height - (int) UIUtils.convertDpToPixel(dividerHeight, ctx) : height); - - //depending on the position we add the view - if (mViewPosition == Position.TOP) { - ((ViewGroup) viewHolder.view).addView(mView, viewParams); - dividerParams.bottomMargin = ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_padding); - ((ViewGroup) viewHolder.view).addView(divider, dividerParams); - } else if (mViewPosition == Position.BOTTOM) { - dividerParams.topMargin = ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_padding); - ((ViewGroup) viewHolder.view).addView(divider, dividerParams); - ((ViewGroup) viewHolder.view).addView(mView, viewParams); - } else { - ((ViewGroup) viewHolder.view).addView(mView, viewParams); - } - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private View view; - - private ViewHolder(View view) { - super(view); - this.view = view; - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/DividerDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/DividerDrawerItem.java deleted file mode 100644 index e7429d1e..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/DividerDrawerItem.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import androidx.annotation.LayoutRes; -import androidx.core.view.ViewCompat; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public class DividerDrawerItem extends AbstractDrawerItem { - @Override - public int getType() { - return R.id.material_drawer_item_divider; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_divider; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //define how the divider should look like - viewHolder.view.setClickable(false); - viewHolder.view.setEnabled(false); - viewHolder.view.setMinimumHeight(1); - ViewCompat.setImportantForAccessibility(viewHolder.view, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO); - - //set the color for the divider - viewHolder.divider.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(ctx, R.attr.material_drawer_divider, R.color.material_drawer_divider)); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private View view; - private View divider; - - private ViewHolder(View view) { - super(view); - this.view = view; - this.divider = view.findViewById(R.id.material_drawer_divider); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ExpandableBadgeDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ExpandableBadgeDrawerItem.java deleted file mode 100644 index f481a741..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ExpandableBadgeDrawerItem.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.graphics.Color; -import androidx.annotation.LayoutRes; -import androidx.annotation.StringRes; -import androidx.core.view.ViewCompat; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.BadgeStyle; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.icons.MaterialDrawerFont; -import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - * NOTE: The arrow will just animate (and rotate) on APIs higher than 11 as the ViewCompat will skip this on API 10 - */ -public class ExpandableBadgeDrawerItem extends BaseDescribeableDrawerItem - implements ColorfulBadgeable { - - private Drawer.OnDrawerItemClickListener mOnDrawerItemClickListener; - - protected ColorHolder arrowColor; - - protected int arrowRotationAngleStart = 0; - - protected int arrowRotationAngleEnd = 180; - - protected StringHolder mBadge; - protected BadgeStyle mBadgeStyle = new BadgeStyle(); - - @Override - public int getType() { - return R.id.material_drawer_item_expandable_badge; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_expandable_badge; - } - - @Override - public void bindView(ExpandableBadgeDrawerItem.ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - //bind the basic view parts - bindViewHelper(viewHolder); - - //set the text for the badge or hide - boolean badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge); - //style the badge if it is visible - if (true) { - mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))); - viewHolder.badgeContainer.setVisibility(View.VISIBLE); - } else { - viewHolder.badgeContainer.setVisibility(View.GONE); - } - - //define the typeface for our textViews - if (getTypeface() != null) { - viewHolder.badge.setTypeface(getTypeface()); - } - - //make sure all animations are stopped - if (viewHolder.arrow.getDrawable() instanceof IconicsDrawable) { - ((IconicsDrawable) viewHolder.arrow.getDrawable()).color(this.arrowColor != null ? this.arrowColor.color(ctx) : getIconColor(ctx)); - } - viewHolder.arrow.clearAnimation(); - if (!isExpanded()) { - viewHolder.arrow.setRotation(this.arrowRotationAngleStart); - } else { - viewHolder.arrow.setRotation(this.arrowRotationAngleEnd); - } - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ExpandableBadgeDrawerItem withOnDrawerItemClickListener(Drawer.OnDrawerItemClickListener onDrawerItemClickListener) { - mOnDrawerItemClickListener = onDrawerItemClickListener; - return this; - } - - @Override - public Drawer.OnDrawerItemClickListener getOnDrawerItemClickListener() { - return mOnArrowDrawerItemClickListener; - } - - /** - * our internal onDrawerItemClickListener which will handle the arrow animation - */ - private Drawer.OnDrawerItemClickListener mOnArrowDrawerItemClickListener = new Drawer.OnDrawerItemClickListener() { - @Override - public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { - if (drawerItem instanceof AbstractDrawerItem && drawerItem.isEnabled()) { - if (((AbstractDrawerItem) drawerItem).getSubItems() != null) { - if (((AbstractDrawerItem) drawerItem).isExpanded()) { - ViewCompat.animate(view.findViewById(R.id.material_drawer_arrow)).rotation(ExpandableBadgeDrawerItem.this.arrowRotationAngleEnd).start(); - } else { - ViewCompat.animate(view.findViewById(R.id.material_drawer_arrow)) - .rotation(ExpandableBadgeDrawerItem.this.arrowRotationAngleStart) - .start(); - } - } - } - - return mOnDrawerItemClickListener != null && mOnDrawerItemClickListener.onItemClick(view, position, drawerItem); - } - }; - - @Override - public ExpandableBadgeDrawerItem withBadge(StringHolder badge) { - this.mBadge = badge; - return (ExpandableBadgeDrawerItem) this; - } - - @Override - public ExpandableBadgeDrawerItem withBadge(String badge) { - this.mBadge = new StringHolder(badge); - return (ExpandableBadgeDrawerItem) this; - } - - @Override - public ExpandableBadgeDrawerItem withBadge(@StringRes int badgeRes) { - this.mBadge = new StringHolder(badgeRes); - return (ExpandableBadgeDrawerItem) this; - } - - @Override - public ExpandableBadgeDrawerItem withBadgeStyle(BadgeStyle badgeStyle) { - this.mBadgeStyle = badgeStyle; - return (ExpandableBadgeDrawerItem) this; - } - - public StringHolder getBadge() { - return mBadge; - } - - public BadgeStyle getBadgeStyle() { - return mBadgeStyle; - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends BaseViewHolder { - public ImageView arrow; - public View badgeContainer; - public TextView badge; - - public ViewHolder(View view) { - super(view); - badgeContainer = view.findViewById(R.id.material_drawer_badge_container); - badge = view.findViewById(R.id.material_drawer_badge); - arrow = view.findViewById(R.id.material_drawer_arrow); - arrow.setImageDrawable(new IconicsDrawable(view.getContext(), MaterialDrawerFont.Icon.mdf_expand_more).sizeDp(16).paddingDp(2).color(Color.BLACK)); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ExpandableDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ExpandableDrawerItem.java deleted file mode 100644 index f457d131..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ExpandableDrawerItem.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.graphics.Color; -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.LayoutRes; -import androidx.core.view.ViewCompat; -import android.view.View; -import android.widget.ImageView; - -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.icons.MaterialDrawerFont; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - * NOTE: The arrow will just animate (and rotate) on APIs higher than 11 as the ViewCompat will skip this on API 10 - */ -public class ExpandableDrawerItem extends BaseDescribeableDrawerItem { - - private Drawer.OnDrawerItemClickListener mOnDrawerItemClickListener; - - protected ColorHolder arrowColor; - - protected int arrowRotationAngleStart = 0; - - protected int arrowRotationAngleEnd = 180; - - public ExpandableDrawerItem withArrowColor(@ColorInt int arrowColor) { - this.arrowColor = ColorHolder.fromColor(arrowColor); - return this; - } - - public ExpandableDrawerItem withArrowColorRes(@ColorRes int arrowColorRes) { - this.arrowColor = ColorHolder.fromColorRes(arrowColorRes); - return this; - } - - public ExpandableDrawerItem withArrowRotationAngleStart(int angle) { - this.arrowRotationAngleStart = angle; - return this; - } - - public ExpandableDrawerItem withArrowRotationAngleEnd(int angle) { - this.arrowRotationAngleEnd = angle; - return this; - } - - @Override - public int getType() { - return R.id.material_drawer_item_expandable; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_expandable; - } - - @Override - public ExpandableDrawerItem withOnDrawerItemClickListener(Drawer.OnDrawerItemClickListener onDrawerItemClickListener) { - mOnDrawerItemClickListener = onDrawerItemClickListener; - return this; - } - - @Override - public Drawer.OnDrawerItemClickListener getOnDrawerItemClickListener() { - return mOnArrowDrawerItemClickListener; - } - - /** - * our internal onDrawerItemClickListener which will handle the arrow animation - */ - private Drawer.OnDrawerItemClickListener mOnArrowDrawerItemClickListener = new Drawer.OnDrawerItemClickListener() { - @Override - public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { - if (drawerItem instanceof AbstractDrawerItem && drawerItem.isEnabled()) { - if (((AbstractDrawerItem) drawerItem).getSubItems() != null) { - if (((AbstractDrawerItem) drawerItem).isExpanded()) { - ViewCompat.animate(view.findViewById(R.id.material_drawer_arrow)).rotation(ExpandableDrawerItem.this.arrowRotationAngleEnd).start(); - } else { - ViewCompat.animate(view.findViewById(R.id.material_drawer_arrow)).rotation(ExpandableDrawerItem.this.arrowRotationAngleStart).start(); - } - } - } - - return mOnDrawerItemClickListener != null && mOnDrawerItemClickListener.onItemClick(view, position, drawerItem); - } - }; - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - //bind the basic view parts - bindViewHelper(viewHolder); - - //make sure all animations are stopped - if (viewHolder.arrow.getDrawable() instanceof IconicsDrawable) { - ((IconicsDrawable) viewHolder.arrow.getDrawable()).color(this.arrowColor != null ? this.arrowColor.color(ctx) : getIconColor(ctx)); - } - viewHolder.arrow.clearAnimation(); - if (!isExpanded()) { - viewHolder.arrow.setRotation(this.arrowRotationAngleStart); - } else { - viewHolder.arrow.setRotation(this.arrowRotationAngleEnd); - } - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends BaseViewHolder { - public ImageView arrow; - - public ViewHolder(View view) { - super(view); - arrow = view.findViewById(R.id.material_drawer_arrow); - arrow.setImageDrawable(new IconicsDrawable(view.getContext(), MaterialDrawerFont.Icon.mdf_expand_more).sizeDp(16).paddingDp(2).color(Color.BLACK)); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/MiniDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/MiniDrawerItem.java deleted file mode 100644 index 6303b2bb..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/MiniDrawerItem.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.BadgeStyle; -import com.mikepenz.materialdrawer.holder.DimenHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; - -import java.util.List; - -import androidx.annotation.DimenRes; -import androidx.annotation.LayoutRes; -import androidx.recyclerview.widget.RecyclerView; - -import static com.mikepenz.materialdrawer.util.DrawerUIUtils.themeDrawerItem; - -/** - * Created by mikepenz on 03.02.15. - */ -public class MiniDrawerItem extends BaseDrawerItem { - private StringHolder mBadge; - private BadgeStyle mBadgeStyle = new BadgeStyle(); - - private boolean mEnableSelectedBackground = false; - protected DimenHolder mCustomHeight; - - public MiniDrawerItem() { - - } - - public MiniDrawerItem(PrimaryDrawerItem primaryDrawerItem) { - this.mIdentifier = primaryDrawerItem.mIdentifier; - this.mTag = primaryDrawerItem.mTag; - - this.mBadge = primaryDrawerItem.mBadge; - this.mBadgeStyle = primaryDrawerItem.mBadgeStyle; - - this.mEnabled = primaryDrawerItem.mEnabled; - this.mSelectable = primaryDrawerItem.mSelectable; - this.mSelected = primaryDrawerItem.mSelected; - - this.icon = primaryDrawerItem.icon; - this.selectedIcon = primaryDrawerItem.selectedIcon; - - this.iconTinted = primaryDrawerItem.iconTinted; - this.selectedColor = primaryDrawerItem.selectedColor; - - this.iconColor = primaryDrawerItem.iconColor; - this.selectedIconColor = primaryDrawerItem.selectedIconColor; - this.disabledIconColor = primaryDrawerItem.disabledIconColor; - } - - public MiniDrawerItem(SecondaryDrawerItem secondaryDrawerItem) { - this.mIdentifier = secondaryDrawerItem.mIdentifier; - this.mTag = secondaryDrawerItem.mTag; - - this.mBadge = secondaryDrawerItem.mBadge; - this.mBadgeStyle = secondaryDrawerItem.mBadgeStyle; - - this.mEnabled = secondaryDrawerItem.mEnabled; - this.mSelectable = secondaryDrawerItem.mSelectable; - this.mSelected = secondaryDrawerItem.mSelected; - - this.icon = secondaryDrawerItem.icon; - this.selectedIcon = secondaryDrawerItem.selectedIcon; - - this.iconTinted = secondaryDrawerItem.iconTinted; - this.selectedColor = secondaryDrawerItem.selectedColor; - - this.iconColor = secondaryDrawerItem.iconColor; - this.selectedIconColor = secondaryDrawerItem.selectedIconColor; - this.disabledIconColor = secondaryDrawerItem.disabledIconColor; - } - - - public MiniDrawerItem withCustomHeightRes(@DimenRes int customHeightRes) { - this.mCustomHeight = DimenHolder.fromResource(customHeightRes); - return this; - } - - public MiniDrawerItem withCustomHeightDp(int customHeightDp) { - this.mCustomHeight = DimenHolder.fromDp(customHeightDp); - return this; - } - - public MiniDrawerItem withCustomHeightPx(int customHeightPx) { - this.mCustomHeight = DimenHolder.fromPixel(customHeightPx); - return this; - } - - public MiniDrawerItem withCustomHeight(DimenHolder customHeight) { - this.mCustomHeight = customHeight; - return this; - } - - public MiniDrawerItem withEnableSelectedBackground(boolean enableSelectedBackground) { - this.mEnableSelectedBackground = enableSelectedBackground; - return this; - } - - @Override - public int getType() { - return R.id.material_drawer_item_mini; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_mini; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - - //set a different height for this item - if (mCustomHeight != null) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) viewHolder.itemView.getLayoutParams(); - lp.height = mCustomHeight.asPixel(ctx); - viewHolder.itemView.setLayoutParams(lp); - } - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //set the item enabled if it is - viewHolder.itemView.setEnabled(isEnabled()); - - //set the item selected if it is - viewHolder.itemView.setSelected(isSelected()); - - // - viewHolder.itemView.setTag(this); - - //get the correct color for the icon - int iconColor = getIconColor(ctx); - int selectedIconColor = getSelectedIconColor(ctx); - - if (mEnableSelectedBackground) { - //get the correct color for the background - int selectedColor = getSelectedColor(ctx); - //set the background for the item - themeDrawerItem(ctx, viewHolder.view, selectedColor, isSelectedBackgroundAnimated()); - } - - //set the text for the badge or hide - boolean badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge); - //style the badge if it is visible - if (badgeVisible) { - mBadgeStyle.style(viewHolder.badge); - } - - //get the drawables for our icon and set it - Drawable icon = ImageHolder.decideIcon(getIcon(), ctx, iconColor, isIconTinted(), 1); - Drawable selectedIcon = ImageHolder.decideIcon(getSelectedIcon(), ctx, selectedIconColor, isIconTinted(), 1); - ImageHolder.applyMultiIconTo(icon, iconColor, selectedIcon, selectedIconColor, isIconTinted(), viewHolder.icon); - - //for android API 17 --> Padding not applied via xml - int verticalPadding = ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_padding); - int topBottomPadding = ctx.getResources().getDimensionPixelSize(R.dimen.material_mini_drawer_item_padding); - viewHolder.itemView.setPadding(verticalPadding, topBottomPadding, verticalPadding, topBottomPadding); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private View view; - private ImageView icon; - private TextView badge; - - public ViewHolder(View view) { - super(view); - - this.view = view; - this.icon = (ImageView) view.findViewById(R.id.material_drawer_icon); - this.badge = (TextView) view.findViewById(R.id.material_drawer_badge); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/MiniProfileDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/MiniProfileDrawerItem.java deleted file mode 100644 index c4a09653..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/MiniProfileDrawerItem.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import androidx.annotation.DimenRes; -import androidx.annotation.DrawableRes; -import androidx.annotation.LayoutRes; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import android.widget.ImageView; - -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.DimenHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public class MiniProfileDrawerItem extends AbstractDrawerItem implements IProfile { - protected ImageHolder icon; - - protected DimenHolder customHeight; - - public MiniProfileDrawerItem() { - withSelectable(false); - } - - public MiniProfileDrawerItem(ProfileDrawerItem profile) { - this.icon = profile.icon; - this.mEnabled = profile.mEnabled; - withSelectable(false); - } - - @Override - public MiniProfileDrawerItem withName(CharSequence name) { - return null; - } - - @Override - public StringHolder getName() { - return null; - } - - @Override - public MiniProfileDrawerItem withEmail(String email) { - return null; - } - - @Override - public StringHolder getEmail() { - return null; - } - - @Override - public MiniProfileDrawerItem withIcon(Drawable icon) { - this.icon = new ImageHolder(icon); - return this; - } - - @Override - public MiniProfileDrawerItem withIcon(@DrawableRes int iconRes) { - this.icon = new ImageHolder(iconRes); - return this; - } - - @Override - public MiniProfileDrawerItem withIcon(Bitmap iconBitmap) { - this.icon = new ImageHolder(iconBitmap); - return this; - } - - @Override - public MiniProfileDrawerItem withIcon(String url) { - this.icon = new ImageHolder(url); - return this; - } - - @Override - public MiniProfileDrawerItem withIcon(Uri uri) { - this.icon = new ImageHolder(uri); - return this; - } - - @Override - public MiniProfileDrawerItem withIcon(IIcon icon) { - this.icon = new ImageHolder(icon); - return this; - } - - public MiniProfileDrawerItem withCustomHeightRes(@DimenRes int customHeightRes) { - this.customHeight = DimenHolder.fromResource(customHeightRes); - return this; - } - - public MiniProfileDrawerItem withCustomHeightDp(int customHeightDp) { - this.customHeight = DimenHolder.fromDp(customHeightDp); - return this; - } - - public MiniProfileDrawerItem withCustomHeightPx(int customHeightPx) { - this.customHeight = DimenHolder.fromPixel(customHeightPx); - return this; - } - - public MiniProfileDrawerItem withCustomHeight(DimenHolder customHeight) { - this.customHeight = customHeight; - return this; - } - - @Override - public ImageHolder getIcon() { - return icon; - } - - @Override - public int getType() { - return R.id.material_drawer_item_mini_profile; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_mini_profile; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - if (customHeight != null) { - RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) viewHolder.itemView.getLayoutParams(); - lp.height = customHeight.asPixel(viewHolder.itemView.getContext()); - viewHolder.itemView.setLayoutParams(lp); - } - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //set the item enabled if it is - viewHolder.itemView.setEnabled(isEnabled()); - - //set the icon - ImageHolder.applyToOrSetInvisible(getIcon(), viewHolder.icon); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private ImageView icon; - - public ViewHolder(View view) { - super(view); - this.icon = (ImageView) view.findViewById(R.id.material_drawer_icon); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/PrimaryDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/PrimaryDrawerItem.java deleted file mode 100644 index accf6f41..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/PrimaryDrawerItem.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -/** - * Created by mikepenz on 03.02.15. - */ -public class PrimaryDrawerItem extends AbstractBadgeableDrawerItem { - -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ProfileDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ProfileDrawerItem.java deleted file mode 100644 index 492d19af..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ProfileDrawerItem.java +++ /dev/null @@ -1,365 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.util.Pair; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; -import com.mikepenz.materialdrawer.model.interfaces.Tagable; -import com.mikepenz.materialdrawer.model.interfaces.Typefaceable; -import com.mikepenz.materialdrawer.util.DrawerImageLoader; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; - -import java.util.List; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DrawableRes; -import androidx.annotation.LayoutRes; -import androidx.annotation.StringRes; -import androidx.recyclerview.widget.RecyclerView; - -import static com.mikepenz.materialdrawer.util.DrawerUIUtils.getBooleanStyleable; -import static com.mikepenz.materialdrawer.util.DrawerUIUtils.themeDrawerItem; - -/** - * Created by mikepenz on 03.02.15. - */ -public class ProfileDrawerItem extends AbstractDrawerItem implements IProfile, Tagable, Typefaceable { - protected boolean nameShown = false; - - protected ImageHolder icon; - - protected StringHolder name; - protected StringHolder email; - - protected ColorHolder selectedColor; - protected ColorHolder textColor; - protected ColorHolder selectedTextColor; - protected ColorHolder disabledTextColor; - - protected Typeface typeface = null; - - @Override - public ProfileDrawerItem withIcon(Drawable icon) { - this.icon = new ImageHolder(icon); - return this; - } - - @Override - public ProfileDrawerItem withIcon(@DrawableRes int iconRes) { - this.icon = new ImageHolder(iconRes); - return this; - } - - @Override - public ProfileDrawerItem withIcon(Bitmap iconBitmap) { - this.icon = new ImageHolder(iconBitmap); - return this; - } - - @Override - public ProfileDrawerItem withIcon(IIcon icon) { - this.icon = new ImageHolder(icon); - return this; - } - - @Override - public ProfileDrawerItem withIcon(String url) { - this.icon = new ImageHolder(url); - return this; - } - - @Override - public ProfileDrawerItem withIcon(Uri uri) { - this.icon = new ImageHolder(uri); - return this; - } - - public ProfileDrawerItem withName(CharSequence name) { - this.name = new StringHolder(name); - return this; - } - - public ProfileDrawerItem withName(@StringRes int nameRes) { - this.name = new StringHolder(nameRes); - return this; - } - - public ProfileDrawerItem withEmail(String email) { - this.email = new StringHolder(email); - return this; - } - - public ProfileDrawerItem withEmail(@StringRes int emailRes) { - this.email = new StringHolder(emailRes); - return this; - } - - /** - * Whether to show the profile name in the account switcher. - * - * @param nameShown show name in switcher - * @return the {@link ProfileDrawerItem} - */ - public ProfileDrawerItem withNameShown(boolean nameShown) { - this.nameShown = nameShown; - return this; - } - - public ProfileDrawerItem withSelectedColor(@ColorInt int selectedColor) { - this.selectedColor = ColorHolder.fromColor(selectedColor); - return this; - } - - public ProfileDrawerItem withSelectedColorRes(@ColorRes int selectedColorRes) { - this.selectedColor = ColorHolder.fromColorRes(selectedColorRes); - return this; - } - - public ProfileDrawerItem withTextColor(@ColorInt int textColor) { - this.textColor = ColorHolder.fromColor(textColor); - return this; - } - - public ProfileDrawerItem withTextColorRes(@ColorRes int textColorRes) { - this.textColor = ColorHolder.fromColorRes(textColorRes); - return this; - } - - public ProfileDrawerItem withSelectedTextColor(@ColorInt int selectedTextColor) { - this.selectedTextColor = ColorHolder.fromColor(selectedTextColor); - return this; - } - - public ProfileDrawerItem withSelectedTextColorRes(@ColorRes int selectedColorRes) { - this.selectedTextColor = ColorHolder.fromColorRes(selectedColorRes); - return this; - } - - public ProfileDrawerItem withDisabledTextColor(@ColorInt int disabledTextColor) { - this.disabledTextColor = ColorHolder.fromColor(disabledTextColor); - return this; - } - - public ProfileDrawerItem withDisabledTextColorRes(@ColorRes int disabledTextColorRes) { - this.disabledTextColor = ColorHolder.fromColorRes(disabledTextColorRes); - return this; - } - - public ProfileDrawerItem withTypeface(Typeface typeface) { - this.typeface = typeface; - return this; - } - - public boolean isNameShown() { - return nameShown; - } - - public ColorHolder getSelectedColor() { - return selectedColor; - } - - public ColorHolder getTextColor() { - return textColor; - } - - public ColorHolder getSelectedTextColor() { - return selectedTextColor; - } - - public ColorHolder getDisabledTextColor() { - return disabledTextColor; - } - - @Override - public Typeface getTypeface() { - return typeface; - } - - public ImageHolder getIcon() { - return icon; - } - - @Override - public StringHolder getName() { - return name; - } - - public StringHolder getEmail() { - return email; - } - - @Override - public int getType() { - return R.id.material_drawer_item_profile; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_profile; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //set the item enabled if it is - viewHolder.itemView.setEnabled(isEnabled()); - - //set the item selected if it is - viewHolder.itemView.setSelected(isSelected()); - - //get the correct color for the background - int selectedColor = getSelectedColor(ctx); - //get the correct color for the text - int color = getColor(ctx); - int secondaryColor = getSecondaryColor(ctx); - int selectedTextColor = getSelectedTextColor(ctx); - - //set the background for the item - themeDrawerItem(ctx, viewHolder.view, selectedColor, isSelectedBackgroundAnimated()); - - if (nameShown) { - viewHolder.name.setVisibility(View.VISIBLE); - StringHolder.applyTo(this.getName(), viewHolder.name); - } else { - viewHolder.name.setVisibility(View.GONE); - } - //the MaterialDrawer follows the Google Apps. those only show the e-mail - //within the profile switcher. The problem this causes some confusion for - //some developers. And if you only set the name, the item would be empty - //so here's a small fallback which will prevent this issue of empty items ;) - if (!nameShown && this.getEmail() == null && this.getName() != null) { - StringHolder.applyTo(this.getName(), viewHolder.email); - } else { - StringHolder.applyTo(this.getEmail(), viewHolder.email); - } - - if (getTypeface() != null) { - viewHolder.name.setTypeface(getTypeface()); - viewHolder.email.setTypeface(getTypeface()); - } - - if (nameShown) { - viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor)); - } - viewHolder.email.setTextColor(getTextColorStateList(secondaryColor, selectedTextColor)); - - //cancel previous started image loading processes - DrawerImageLoader.getInstance().cancelImage(viewHolder.profileIcon); - //set the icon - ImageHolder.applyToOrSetInvisible(getIcon(), viewHolder.profileIcon, DrawerImageLoader.Tags.PROFILE_DRAWER_ITEM.name()); - - //for android API 17 --> Padding not applied via xml - DrawerUIUtils.setDrawerVerticalPadding(viewHolder.view); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private View view; - private ImageView profileIcon; - private TextView name; - private TextView email; - - private ViewHolder(View view) { - super(view); - this.view = view; - this.profileIcon = view.findViewById(R.id.material_drawer_profileIcon); - this.name = view.findViewById(R.id.material_drawer_name); - this.email = view.findViewById(R.id.material_drawer_email); - } - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getSelectedColor(Context ctx) { - if (getBooleanStyleable(ctx, R.styleable.MaterialDrawer_material_drawer_legacy_style, false)) { - return ColorHolder.color(getSelectedColor(), ctx, R.attr.material_drawer_selected_legacy, R.color.material_drawer_selected_legacy); - } else { - return ColorHolder.color(getSelectedColor(), ctx, R.attr.material_drawer_selected, R.color.material_drawer_selected); - } - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getColor(Context ctx) { - int color; - if (this.isEnabled()) { - color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_primary_text, R.color.material_drawer_primary_text); - } else { - color = ColorHolder.color(getDisabledTextColor(), ctx, R.attr.material_drawer_hint_text, R.color.material_drawer_hint_text); - } - return color; - } - - protected int getSecondaryColor(Context ctx) { - int color; - if (this.isEnabled()) { - color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_secondary_text, R.color.material_drawer_secondary_text); - } else { - color = ColorHolder.color(getDisabledTextColor(), ctx, R.attr.material_drawer_hint_text, R.color.material_drawer_hint_text); - } - return color; - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getSelectedTextColor(Context ctx) { - return ColorHolder.color(getSelectedTextColor(), ctx, R.attr.material_drawer_selected_text, R.color.material_drawer_selected_text); - } - - protected Pair colorStateList; - - /** - * helper to get the ColorStateList for the text and remembering it so we do not have to recreate it all the time - * - * @param color - * @param selectedTextColor - * @return - */ - protected ColorStateList getTextColorStateList(@ColorInt int color, @ColorInt int selectedTextColor) { - if (colorStateList == null || color + selectedTextColor != colorStateList.first) { - colorStateList = new Pair<>(color + selectedTextColor, DrawerUIUtils.getTextColorStateList(color, selectedTextColor)); - } - - return colorStateList.second; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ProfileSettingDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ProfileSettingDrawerItem.java deleted file mode 100644 index 12a8013c..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ProfileSettingDrawerItem.java +++ /dev/null @@ -1,318 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; -import com.mikepenz.materialdrawer.model.interfaces.Tagable; -import com.mikepenz.materialdrawer.model.interfaces.Typefaceable; -import com.mikepenz.materialdrawer.util.DrawerUIUtils; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.List; - -import androidx.annotation.ColorInt; -import androidx.annotation.ColorRes; -import androidx.annotation.DrawableRes; -import androidx.annotation.LayoutRes; -import androidx.annotation.StringRes; -import androidx.core.view.ViewCompat; -import androidx.recyclerview.widget.RecyclerView; - -import static com.mikepenz.materialdrawer.util.DrawerUIUtils.getBooleanStyleable; - -/** - * Created by mikepenz on 03.02.15. - */ -public class ProfileSettingDrawerItem extends AbstractDrawerItem implements IProfile, Tagable, Typefaceable { - private ImageHolder icon; - - private StringHolder name; - private StringHolder description; - - private boolean iconTinted = false; - - private ColorHolder selectedColor; - private ColorHolder textColor; - private ColorHolder iconColor; - private ColorHolder descriptionTextColor; - - private Typeface typeface = null; - - private boolean selectable = false; - - @Override - public ProfileSettingDrawerItem withIcon(Drawable icon) { - this.icon = new ImageHolder(icon); - return this; - } - - @Override - public ProfileSettingDrawerItem withIcon(@DrawableRes int iconRes) { - this.icon = new ImageHolder(iconRes); - return this; - } - - @Override - public ProfileSettingDrawerItem withIcon(Bitmap icon) { - this.icon = new ImageHolder(icon); - return this; - } - - @Override - public ProfileSettingDrawerItem withIcon(IIcon iicon) { - this.icon = new ImageHolder(iicon); - return this; - } - - @Override - public ProfileSettingDrawerItem withIcon(String url) { - this.icon = new ImageHolder(url); - return this; - } - - @Override - public ProfileSettingDrawerItem withIcon(Uri uri) { - this.icon = new ImageHolder(uri); - return this; - } - - public ProfileSettingDrawerItem withName(CharSequence name) { - this.name = new StringHolder(name); - return this; - } - - public ProfileSettingDrawerItem withName(@StringRes int nameRes) { - this.name = new StringHolder(nameRes); - return this; - } - - public ProfileSettingDrawerItem withDescription(String description) { - this.description = new StringHolder(description); - return this; - } - - public ProfileSettingDrawerItem withDescription(@StringRes int descriptionRes) { - this.description = new StringHolder(descriptionRes); - return this; - } - - //NOTE we reuse the IProfile here to allow custom items within the AccountSwitcher. There is an alias method withDescription for this - public ProfileSettingDrawerItem withEmail(String email) { - this.description = new StringHolder(email); - return this; - } - - public ProfileSettingDrawerItem withSelectedColor(@ColorInt int selectedColor) { - this.selectedColor = ColorHolder.fromColor(selectedColor); - return this; - } - - public ProfileSettingDrawerItem withSelectedColorRes(@ColorRes int selectedColorRes) { - this.selectedColor = ColorHolder.fromColorRes(selectedColorRes); - return this; - } - - public ProfileSettingDrawerItem withTextColor(@ColorInt int textColor) { - this.textColor = ColorHolder.fromColor(textColor); - return this; - } - - public ProfileSettingDrawerItem withTextColorRes(@ColorRes int textColorRes) { - this.textColor = ColorHolder.fromColorRes(textColorRes); - return this; - } - - public ProfileSettingDrawerItem withDescriptionTextColor(@ColorInt int descriptionColor) { - this.descriptionTextColor = ColorHolder.fromColor(descriptionColor); - return this; - } - - public ProfileSettingDrawerItem withDescriptionTextColorRes(@ColorRes int descriptionColorRes) { - this.descriptionTextColor = ColorHolder.fromColorRes(descriptionColorRes); - return this; - } - - public ProfileSettingDrawerItem withIconColor(@ColorInt int iconColor) { - this.iconColor = ColorHolder.fromColor(iconColor); - return this; - } - - public ProfileSettingDrawerItem withIconColorRes(@ColorRes int iconColorRes) { - this.iconColor = ColorHolder.fromColorRes(iconColorRes); - return this; - } - - public ProfileSettingDrawerItem withTypeface(Typeface typeface) { - this.typeface = typeface; - return this; - } - - public ProfileSettingDrawerItem withIconTinted(boolean iconTinted) { - this.iconTinted = iconTinted; - return this; - } - - public ColorHolder getSelectedColor() { - return selectedColor; - } - - public ColorHolder getTextColor() { - return textColor; - } - - public ColorHolder getDescriptionTextColor() { - return descriptionTextColor; - } - - public ColorHolder getIconColor() { - return iconColor; - } - - - public ImageHolder getIcon() { - return icon; - } - - public boolean isIconTinted() { - return iconTinted; - } - - public void setIconTinted(boolean iconTinted) { - this.iconTinted = iconTinted; - } - - @Override - public Typeface getTypeface() { - return typeface; - } - - @Override - public StringHolder getName() { - return name; - } - - public StringHolder getEmail() { - return description; - } - - public StringHolder getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = new StringHolder(description); - } - - @Override - public boolean isSelectable() { - return selectable; - } - - public ProfileSettingDrawerItem withSelectable(boolean selectable) { - this.selectable = selectable; - return this; - } - - @Override - public int getType() { - return R.id.material_drawer_item_profile_setting; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_profile_setting; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - //get the context - Context ctx = viewHolder.itemView.getContext(); - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //set the item enabled if it is - viewHolder.itemView.setEnabled(isEnabled()); - - //set the item selected if it is - viewHolder.itemView.setSelected(isSelected()); - - //get the correct color for the background - int selectedColor = getSelectedColor(ctx); - //get the correct color for the text - int color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_primary_text, R.color.material_drawer_primary_text); - int iconColor = ColorHolder.color(getIconColor(), ctx, R.attr.material_drawer_primary_icon, R.color.material_drawer_primary_icon); - int descriptionColor = ColorHolder.color(getDescriptionTextColor(), ctx, R.attr.material_drawer_primary_text, R.color.material_drawer_primary_text); - - ViewCompat.setBackground(viewHolder.view, UIUtils.getSelectableBackground(ctx, selectedColor, isSelectedBackgroundAnimated())); - - StringHolder.applyTo(this.getName(), viewHolder.name); - viewHolder.name.setTextColor(color); - - StringHolder.applyToOrHide(this.getDescription(), viewHolder.description); - viewHolder.description.setTextColor(descriptionColor); - - if (getTypeface() != null) { - viewHolder.name.setTypeface(getTypeface()); - viewHolder.description.setTypeface(getTypeface()); - } - - //set the correct icon - ImageHolder.applyDecidedIconOrSetGone(icon, viewHolder.icon, iconColor, isIconTinted(), 2); - - //for android API 17 --> Padding not applied via xml - DrawerUIUtils.setDrawerVerticalPadding(viewHolder.view); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - /** - * helper method to decide for the correct color - * - * @param ctx - * @return - */ - protected int getSelectedColor(Context ctx) { - if (getBooleanStyleable(ctx, R.styleable.MaterialDrawer_material_drawer_legacy_style, false)) { - return ColorHolder.color(getSelectedColor(), ctx, R.attr.material_drawer_selected_legacy, R.color.material_drawer_selected_legacy); - } else { - return ColorHolder.color(getSelectedColor(), ctx, R.attr.material_drawer_selected, R.color.material_drawer_selected); - } - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private View view; - private ImageView icon; - private TextView name; - private TextView description; - - private ViewHolder(View view) { - super(view); - this.view = view; - this.icon = (ImageView) view.findViewById(R.id.material_drawer_icon); - this.name = (TextView) view.findViewById(R.id.material_drawer_name); - this.description = (TextView) view.findViewById(R.id.material_drawer_description); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondaryDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondaryDrawerItem.java deleted file mode 100644 index 8f22290b..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondaryDrawerItem.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import androidx.annotation.LayoutRes; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public class SecondaryDrawerItem extends AbstractBadgeableDrawerItem { - - @Override - public int getType() { - return R.id.material_drawer_item_secondary; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_secondary; - } - - /** - * helper method to decide for the correct color - * OVERWRITE to get the correct secondary color - * - * @param ctx - * @return - */ - @Override - protected int getColor(Context ctx) { - int color; - if (this.isEnabled()) { - color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_secondary_text, R.color.material_drawer_secondary_text); - } else { - color = ColorHolder.color(getDisabledTextColor(), ctx, R.attr.material_drawer_hint_text, R.color.material_drawer_hint_text); - } - return color; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondarySwitchDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondarySwitchDrawerItem.java deleted file mode 100644 index 22b5862a..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondarySwitchDrawerItem.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import androidx.annotation.LayoutRes; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public class SecondarySwitchDrawerItem extends AbstractSwitchableDrawerItem { - - @Override - public int getType() { - return R.id.material_drawer_item_secondary_switch; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_secondary_switch; - } - - /** - * helper method to decide for the correct color - * OVERWRITE to get the correct secondary color - * - * @param ctx - * @return - */ - @Override - protected int getColor(Context ctx) { - int color; - if (this.isEnabled()) { - color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_secondary_text, R.color.material_drawer_secondary_text); - } else { - color = ColorHolder.color(getDisabledTextColor(), ctx, R.attr.material_drawer_hint_text, R.color.material_drawer_hint_text); - } - return color; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondaryToggleDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondaryToggleDrawerItem.java deleted file mode 100644 index 30024be3..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SecondaryToggleDrawerItem.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import androidx.annotation.LayoutRes; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public class SecondaryToggleDrawerItem extends AbstractToggleableDrawerItem { - - @Override - public int getType() { - return R.id.material_drawer_item_secondary_toggle; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_secondary_toggle; - } - - /** - * helper method to decide for the correct color - * OVERWRITE to get the correct secondary color - * - * @param ctx - * @return - */ - @Override - protected int getColor(Context ctx) { - int color; - if (this.isEnabled()) { - color = ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_secondary_text, R.color.material_drawer_secondary_text); - } else { - color = ColorHolder.color(getDisabledTextColor(), ctx, R.attr.material_drawer_hint_text, R.color.material_drawer_hint_text); - } - return color; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SectionDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SectionDrawerItem.java deleted file mode 100644 index 4a336024..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SectionDrawerItem.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -import android.content.Context; -import android.graphics.Typeface; -import androidx.annotation.LayoutRes; -import androidx.annotation.StringRes; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import android.widget.TextView; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.model.interfaces.Nameable; -import com.mikepenz.materialdrawer.model.interfaces.Typefaceable; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public class SectionDrawerItem extends AbstractDrawerItem implements Nameable, Typefaceable { - - private StringHolder name; - private boolean divider = true; - - private ColorHolder textColor; - - private Typeface typeface = null; - - public SectionDrawerItem withName(StringHolder name) { - this.name = name; - return this; - } - - public SectionDrawerItem withName(String name) { - this.name = new StringHolder(name); - return this; - } - - public SectionDrawerItem withName(@StringRes int nameRes) { - this.name = new StringHolder(nameRes); - return this; - } - - public SectionDrawerItem withDivider(boolean divider) { - this.divider = divider; - return this; - } - - public SectionDrawerItem withTextColor(int textColor) { - this.textColor = ColorHolder.fromColor(textColor); - return this; - } - - public SectionDrawerItem withTextColorRes(int textColorRes) { - this.textColor = ColorHolder.fromColorRes(textColorRes); - return this; - } - - public SectionDrawerItem withTypeface(Typeface typeface) { - this.typeface = typeface; - return this; - } - - public boolean hasDivider() { - return divider; - } - - public ColorHolder getTextColor() { - return textColor; - } - - public StringHolder getName() { - return name; - } - - @Override - public boolean isEnabled() { - return false; - } - - @Override - public boolean isSelected() { - return false; - } - - @Override - public int getType() { - return R.id.material_drawer_item_section; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.material_drawer_item_section; - } - - @Override - public Typeface getTypeface() { - return typeface; - } - - @Override - public void bindView(ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - - //set the identifier from the drawerItem here. It can be used to run tests - viewHolder.itemView.setId(hashCode()); - - //define this item to be not clickable nor enabled - viewHolder.view.setClickable(false); - viewHolder.view.setEnabled(false); - - //define the text color - viewHolder.name.setTextColor(ColorHolder.color(getTextColor(), ctx, R.attr.material_drawer_secondary_text, R.color.material_drawer_secondary_text)); - - //set the text for the name - StringHolder.applyTo(this.getName(), viewHolder.name); - - //define the typeface for our textViews - if (getTypeface() != null) { - viewHolder.name.setTypeface(getTypeface()); - } - - //hide the divider if we do not need one - if (this.hasDivider()) { - viewHolder.divider.setVisibility(View.VISIBLE); - } else { - viewHolder.divider.setVisibility(View.GONE); - } - - //set the color for the divider - viewHolder.divider.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(ctx, R.attr.material_drawer_divider, R.color.material_drawer_divider)); - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - public static class ViewHolder extends RecyclerView.ViewHolder { - private View view; - private View divider; - private TextView name; - - private ViewHolder(View view) { - super(view); - this.view = view; - this.divider = view.findViewById(R.id.material_drawer_divider); - this.name = (TextView) view.findViewById(R.id.material_drawer_name); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SwitchDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SwitchDrawerItem.java deleted file mode 100644 index f6ffe57a..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/SwitchDrawerItem.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -/** - * Created by mikepenz on 03.02.15. - */ -public class SwitchDrawerItem extends AbstractSwitchableDrawerItem { - -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ToggleDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ToggleDrawerItem.java deleted file mode 100644 index 407dc43c..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/ToggleDrawerItem.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.mikepenz.materialdrawer.model; - -/** - * Created by mikepenz on 03.02.15. - */ -public class ToggleDrawerItem extends AbstractToggleableDrawerItem { - -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Badgeable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Badgeable.java deleted file mode 100644 index 9a1b5ec3..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Badgeable.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import com.mikepenz.materialdrawer.holder.StringHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface Badgeable { - T withBadge(String badge); - - T withBadge(int badgeRes); - - T withBadge(StringHolder badgeRes); - - StringHolder getBadge(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/ColorfulBadgeable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/ColorfulBadgeable.java deleted file mode 100644 index 84e7418c..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/ColorfulBadgeable.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import com.mikepenz.materialdrawer.holder.BadgeStyle; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface ColorfulBadgeable extends Badgeable { - T withBadgeStyle(BadgeStyle badgeStyle); - - BadgeStyle getBadgeStyle(); - -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/IDrawerItem.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/IDrawerItem.java deleted file mode 100644 index 35dc0d07..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/IDrawerItem.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import android.content.Context; -import androidx.recyclerview.widget.RecyclerView; -import android.view.View; -import android.view.ViewGroup; - -import com.mikepenz.fastadapter.IExpandable; -import com.mikepenz.fastadapter.IItem; -import com.mikepenz.fastadapter.ISubItem; - -import java.util.List; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface IDrawerItem extends IItem, IExpandable, ISubItem { - - Object getTag(); - - boolean isEnabled(); - - boolean isSelected(); - - T withSetSelected(boolean selected); - - boolean isSelectable(); - - boolean isExcludeFromMiniDrawer(); - - T withSelectable(boolean selectable); - - int getType(); - - int getLayoutRes(); - - View generateView(Context ctx); - - View generateView(Context ctx, ViewGroup parent); - - VH getViewHolder(ViewGroup parent); - - void unbindView(VH holder); - - void bindView(VH holder, List payloads); - - boolean equals(long id); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/IProfile.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/IProfile.java deleted file mode 100644 index bb1edc64..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/IProfile.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import androidx.annotation.DrawableRes; - -import com.mikepenz.fastadapter.IIdentifyable; -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface IProfile extends IIdentifyable { - T withName(CharSequence name); - - StringHolder getName(); - - T withEmail(String email); - - StringHolder getEmail(); - - T withIcon(Drawable icon); - - T withIcon(Bitmap bitmap); - - T withIcon(@DrawableRes int iconRes); - - T withIcon(String url); - - T withIcon(Uri uri); - - T withIcon(IIcon icon); - - ImageHolder getIcon(); - - T withSelectable(boolean selectable); - - boolean isSelectable(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Iconable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Iconable.java deleted file mode 100644 index e574dc9c..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Iconable.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import android.graphics.drawable.Drawable; - -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.materialdrawer.holder.ImageHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface Iconable { - T withIcon(Drawable icon); - - T withIcon(IIcon iicon); - - T withIcon(ImageHolder icon); - - ImageHolder getIcon(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Nameable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Nameable.java deleted file mode 100644 index 8193fd6f..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Nameable.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import com.mikepenz.materialdrawer.holder.StringHolder; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface Nameable { - T withName(String name); - - T withName(int nameRes); - - T withName(StringHolder name); - - StringHolder getName(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/OnPostBindViewListener.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/OnPostBindViewListener.java deleted file mode 100644 index b360a4dd..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/OnPostBindViewListener.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import android.view.View; - -/** - * Created by mikepenz on 21.08.15. - */ -public interface OnPostBindViewListener { - /** - * allows you to hook in the BindView method and modify the view after binding - * - * @param drawerItem the drawerItem used for this view - * @param view the view which will be set - */ - void onBindView(IDrawerItem drawerItem, View view); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Selectable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Selectable.java deleted file mode 100644 index 6ed9a800..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Selectable.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface Selectable { - T withSelectable(boolean selectable); - - boolean isSelectable(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Tagable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Tagable.java deleted file mode 100644 index 7ecf8c12..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Tagable.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface Tagable { - T withTag(Object tag); - - Object getTag(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Typefaceable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Typefaceable.java deleted file mode 100644 index beba2cea..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/interfaces/Typefaceable.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.mikepenz.materialdrawer.model.interfaces; - -import android.graphics.Typeface; - -/** - * Created by mikepenz on 03.02.15. - */ -public interface Typefaceable { - T withTypeface(Typeface typeface); - - Typeface getTypeface(); -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/utils/BadgeDrawableBuilder.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/utils/BadgeDrawableBuilder.java deleted file mode 100644 index a132624c..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/model/utils/BadgeDrawableBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.mikepenz.materialdrawer.model.utils; - -import android.content.Context; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.StateListDrawable; -import androidx.appcompat.content.res.AppCompatResources; -import android.util.StateSet; - -import com.mikepenz.materialdrawer.holder.BadgeStyle; -import com.mikepenz.materialdrawer.holder.ColorHolder; - -/** - * Created by mikepenz on 02.07.15. - */ -public class BadgeDrawableBuilder { - private BadgeStyle mStyle; - - public BadgeDrawableBuilder(BadgeStyle style) { - this.mStyle = style; - } - - public StateListDrawable build(Context ctx) { - StateListDrawable stateListDrawable = new StateListDrawable(); - GradientDrawable normal = (GradientDrawable) AppCompatResources.getDrawable(ctx, mStyle.getGradientDrawable()); - GradientDrawable selected = (GradientDrawable) normal.getConstantState().newDrawable().mutate(); - - ColorHolder.applyToOrTransparent(mStyle.getColor(), ctx, normal); - if (mStyle.getColorPressed() == null) { - ColorHolder.applyToOrTransparent(mStyle.getColor(), ctx, selected); - } else { - ColorHolder.applyToOrTransparent(mStyle.getColorPressed(), ctx, selected); - } - - if (mStyle.getCorners() != null) { - normal.setCornerRadius(mStyle.getCorners().asPixel(ctx)); - selected.setCornerRadius(mStyle.getCorners().asPixel(ctx)); - } - - stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, selected); - stateListDrawable.addState(StateSet.WILD_CARD, normal); - - return stateListDrawable; - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/AbstractDrawerImageLoader.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/AbstractDrawerImageLoader.java deleted file mode 100644 index efd0f069..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/AbstractDrawerImageLoader.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.mikepenz.materialdrawer.util; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.util.Log; -import android.widget.ImageView; - -public abstract class AbstractDrawerImageLoader implements DrawerImageLoader.IDrawerImageLoader { - @Override - public void set(ImageView imageView, Uri uri, Drawable placeholder) { - } - - @Override - public void set(ImageView imageView, Uri uri, Drawable placeholder, String tag) { - //for backwards compatibility call the method without tag too - set(imageView, uri, placeholder); - //this won't do anything - Log.i("MaterialDrawer", "You have not specified a ImageLoader implementation through the DrawerImageLoader.init() method, or you are still overriding the deprecated method set(ImageView iv, Uri u, Drawable d) instead of set(ImageView iv, Uri u, Drawable d, String tag)"); - } - @Override - public void cancel(ImageView imageView) { - } - - @Override - public Drawable placeholder(Context ctx) { - return DrawerUIUtils.getPlaceHolder(ctx); - } - - @Override - public Drawable placeholder(Context ctx, String tag) { - return placeholder(ctx); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerImageLoader.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerImageLoader.java deleted file mode 100644 index 1c46a611..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerImageLoader.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.mikepenz.materialdrawer.util; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.widget.ImageView; - -/** - * Created by mikepenz on 24.03.15. - */ -public class DrawerImageLoader { - public enum Tags { - PROFILE, - PROFILE_DRAWER_ITEM, - ACCOUNT_HEADER - } - - private static DrawerImageLoader SINGLETON = null; - - private IDrawerImageLoader imageLoader; - private boolean mHandleAllUris = false; - - private DrawerImageLoader(IDrawerImageLoader loaderImpl) { - imageLoader = loaderImpl; - } - - public static DrawerImageLoader init(IDrawerImageLoader loaderImpl) { - SINGLETON = new DrawerImageLoader(loaderImpl); - return SINGLETON; - } - - public static DrawerImageLoader getInstance() { - if (SINGLETON == null) { - SINGLETON = new DrawerImageLoader(new AbstractDrawerImageLoader() { - }); - } - return SINGLETON; - } - - public DrawerImageLoader withHandleAllUris(boolean handleAllUris) { - this.mHandleAllUris = handleAllUris; - return this; - } - - /** - * @param imageView - * @param uri - * @param tag - * @return false if not consumed - */ - public boolean setImage(ImageView imageView, Uri uri, String tag) { - //if we do not handle all uris and are not http or https we keep the original behavior - if (mHandleAllUris || "http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) { - if (imageLoader != null) { - Drawable placeHolder = imageLoader.placeholder(imageView.getContext(), tag); - imageLoader.set(imageView, uri, placeHolder, tag); - } - return true; - } - return false; - } - - public void cancelImage(ImageView imageView) { - if (imageLoader != null) { - imageLoader.cancel(imageView); - } - } - - public IDrawerImageLoader getImageLoader() { - return imageLoader; - } - - public void setImageLoader(IDrawerImageLoader imageLoader) { - this.imageLoader = imageLoader; - } - - public interface IDrawerImageLoader { - @Deprecated - void set(ImageView imageView, Uri uri, Drawable placeholder); - - void set(ImageView imageView, Uri uri, Drawable placeholder, String tag); - - void cancel(ImageView imageView); - - Drawable placeholder(Context ctx); - - /** - * @param ctx - * @param tag current possible tags: "profile", "profileDrawerItem", "accountHeader" - * @return - */ - Drawable placeholder(Context ctx, String tag); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerItemViewHelper.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerItemViewHelper.java deleted file mode 100644 index 3a28fe6f..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerItemViewHelper.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.mikepenz.materialdrawer.util; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialize.util.UIUtils; - -import java.util.ArrayList; -import java.util.Collections; - -/** - * Created by mikepenz on 27.03.15. - */ -public class DrawerItemViewHelper { - - private Context mContext; - - public DrawerItemViewHelper(Context context) { - this.mContext = context; - } - - private ArrayList mDrawerItems = new ArrayList<>(); - - public DrawerItemViewHelper withDrawerItems(ArrayList drawerItems) { - this.mDrawerItems = drawerItems; - return this; - } - - public DrawerItemViewHelper withDrawerItems(IDrawerItem... drawerItems) { - Collections.addAll(this.mDrawerItems, drawerItems); - return this; - } - - private boolean mDivider = true; - - public DrawerItemViewHelper withDivider(boolean divider) { - this.mDivider = divider; - return this; - } - - private OnDrawerItemClickListener mOnDrawerItemClickListener = null; - - public DrawerItemViewHelper withOnDrawerItemClickListener(OnDrawerItemClickListener onDrawerItemClickListener) { - mOnDrawerItemClickListener = onDrawerItemClickListener; - return this; - } - - public View build() { - //create the container view - LinearLayout linearLayout = new LinearLayout(mContext); - linearLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - //create the divider - if (mDivider) { - LinearLayout divider = new LinearLayout(mContext); - divider.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - divider.setMinimumHeight((int) UIUtils.convertDpToPixel(1, mContext)); - divider.setOrientation(LinearLayout.VERTICAL); - divider.setBackgroundColor(UIUtils.getThemeColorFromAttrOrRes(mContext, R.attr.material_drawer_divider, R.color.material_drawer_divider)); - linearLayout.addView(divider); - } - - //add all drawer items - for (IDrawerItem drawerItem : mDrawerItems) { - View view = drawerItem.generateView(mContext); - view.setTag(drawerItem); - - if (drawerItem.isEnabled()) { - view.setBackgroundResource(UIUtils.getSelectableBackgroundRes(mContext)); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mOnDrawerItemClickListener != null) { - mOnDrawerItemClickListener.onItemClick(v, (IDrawerItem) v.getTag(R.id.material_drawer_item)); - } - } - }); - } - - linearLayout.addView(view); - } - - return linearLayout; - } - - - public interface OnDrawerItemClickListener { - public void onItemClick(View view, IDrawerItem drawerItem); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerUIUtils.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerUIUtils.java deleted file mode 100644 index faa1494b..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/DrawerUIUtils.java +++ /dev/null @@ -1,242 +0,0 @@ -package com.mikepenz.materialdrawer.util; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.InsetDrawable; -import android.graphics.drawable.RippleDrawable; -import android.graphics.drawable.StateListDrawable; -import android.os.Build; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.WindowManager; - -import androidx.annotation.StyleableRes; -import androidx.core.view.ViewCompat; - -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.icons.MaterialDrawerFont; -import com.mikepenz.materialize.util.UIUtils; - -/** - * Created by mikepenz on 15.03.14. - */ -@SuppressLint("InlinedApi") -public class DrawerUIUtils { - - /** - * Get the boolean value of a given styleable. - * - * @param ctx - * @param styleable - * @param def - * @return - */ - public static boolean getBooleanStyleable(Context ctx, @StyleableRes int styleable, boolean def) { - TypedArray ta = ctx.getTheme().obtainStyledAttributes(R.styleable.MaterialDrawer); - return ta.getBoolean(styleable, def); - } - - /** - * Util method to theme the drawer item view's background (and foreground if possible) - * - * @param ctx the context to use - * @param view the view to theme - * @param selected_color the selected color to use - * @param animate true if we want to animate the StateListDrawable - */ - public static void themeDrawerItem(Context ctx, View view, int selected_color, boolean animate) { - boolean legacyStyle = getBooleanStyleable(ctx, R.styleable.MaterialDrawer_material_drawer_legacy_style, false); - - Drawable selected; - Drawable unselected; - - if (legacyStyle) { - // Material 1.0 styling - selected = new ColorDrawable(selected_color); - unselected = UIUtils.getSelectableBackground(ctx); - } else { - // Material 2.0 styling - int cornerRadius = ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_item_corner_radius); - int paddingTopBottom = ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_item_background_padding_top_bottom); - int paddingStartEnd = ctx.getResources().getDimensionPixelSize(R.dimen.material_drawer_item_background_padding_start_end); - - // define normal selected background - GradientDrawable gradientDrawable = new GradientDrawable(); - gradientDrawable.setColor(selected_color); - gradientDrawable.setCornerRadius(cornerRadius); - selected = new InsetDrawable(gradientDrawable, paddingStartEnd, paddingTopBottom, paddingStartEnd, paddingTopBottom); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - // define mask for ripple - GradientDrawable gradientMask = new GradientDrawable(); - gradientMask.setColor(Color.BLACK); - gradientMask.setCornerRadius(cornerRadius); - Drawable mask = new InsetDrawable(gradientMask, paddingStartEnd, paddingTopBottom, paddingStartEnd, paddingTopBottom); - - unselected = new RippleDrawable(new ColorStateList(new int[][]{new int[]{}}, new int[]{UIUtils.getThemeColor(ctx, R.attr.colorControlHighlight)}), null, mask); - } else { - // define touch drawable - GradientDrawable touchDrawable = new GradientDrawable(); - touchDrawable.setColor(UIUtils.getThemeColor(ctx, R.attr.colorControlHighlight)); - touchDrawable.setCornerRadius(cornerRadius); - Drawable touchInsetDrawable = new InsetDrawable(touchDrawable, paddingStartEnd, paddingTopBottom, paddingStartEnd, paddingTopBottom); - - StateListDrawable unselectedStates = new StateListDrawable(); - //if possible and wanted we enable animating across states - if (animate) { - int duration = ctx.getResources().getInteger(android.R.integer.config_shortAnimTime); - unselectedStates.setEnterFadeDuration(duration); - unselectedStates.setExitFadeDuration(duration); - } - unselectedStates.addState(new int[]{android.R.attr.state_pressed}, touchInsetDrawable); - unselectedStates.addState(new int[]{}, new ColorDrawable(Color.TRANSPARENT)); - unselected = unselectedStates; - } - } - - StateListDrawable states = new StateListDrawable(); - - //if possible and wanted we enable animating across states - if (animate) { - int duration = ctx.getResources().getInteger(android.R.integer.config_shortAnimTime); - states.setEnterFadeDuration(duration); - states.setExitFadeDuration(duration); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - states.addState(new int[]{android.R.attr.state_selected}, selected); - states.addState(new int[]{}, new ColorDrawable(Color.TRANSPARENT)); - - ViewCompat.setBackground(view, states); - view.setForeground(unselected); - } else { - states.addState(new int[]{android.R.attr.state_selected}, selected); - states.addState(new int[]{}, unselected); - - ViewCompat.setBackground(view, states); - } - } - - /** - * helper to create a colorStateList for the text - * - * @param text_color - * @param selected_text_color - * @return - */ - public static ColorStateList getTextColorStateList(int text_color, int selected_text_color) { - return new ColorStateList( - new int[][]{ - new int[]{android.R.attr.state_selected}, - new int[]{} - }, - new int[]{ - selected_text_color, - text_color - } - ); - } - - /** - * helper to create a stateListDrawable for the icon - * - * @param icon - * @param selectedIcon - * @return - */ - public static StateListDrawable getIconStateList(Drawable icon, Drawable selectedIcon) { - StateListDrawable iconStateListDrawable = new StateListDrawable(); - iconStateListDrawable.addState(new int[]{android.R.attr.state_selected}, selectedIcon); - iconStateListDrawable.addState(new int[]{}, icon); - return iconStateListDrawable; - } - - /** - * helper to create a StateListDrawable for the drawer item background - * - * @param selected_color - * @return - */ - public static StateListDrawable getDrawerItemBackground(int selected_color) { - ColorDrawable clrActive = new ColorDrawable(selected_color); - StateListDrawable states = new StateListDrawable(); - states.addState(new int[]{android.R.attr.state_selected}, clrActive); - return states; - } - - /** - * helper to calculate the optimal drawer width - * - * @param context - * @return - */ - public static int getOptimalDrawerWidth(Context context) { - int possibleMinDrawerWidth = UIUtils.getScreenWidth(context) - UIUtils.getActionBarHeight(context); - int maxDrawerWidth = context.getResources().getDimensionPixelSize(R.dimen.material_drawer_width); - return Math.min(possibleMinDrawerWidth, maxDrawerWidth); - } - - - /** - * helper method to get a person placeHolder drawable - * - * @param ctx - * @return - */ - public static Drawable getPlaceHolder(Context ctx) { - return new IconicsDrawable(ctx, MaterialDrawerFont.Icon.mdf_person).colorRes(R.color.accent).backgroundColorRes(R.color.primary).sizeDp(56).paddingDp(16); - } - - /** - * helper to set the vertical padding to the DrawerItems - * this is required because on API Level 17 the padding is ignored which is set via the XML - * - * @param v - */ - public static void setDrawerVerticalPadding(View v) { - int verticalPadding = v.getContext().getResources().getDimensionPixelSize(R.dimen.material_drawer_vertical_padding); - v.setPadding(verticalPadding, 0, verticalPadding, 0); - } - - /** - * helper to set the vertical padding including the extra padding for deeper item hirachy level to the DrawerItems - * this is required because on API Level 17 the padding is ignored which is set via the XML - * - * @param v - * @param level - */ - public static void setDrawerVerticalPadding(View v, int level) { - int verticalPadding = v.getContext().getResources().getDimensionPixelSize(R.dimen.material_drawer_vertical_padding); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - v.setPaddingRelative(verticalPadding * level, 0, verticalPadding, 0); - } else { - v.setPadding(verticalPadding * level, 0, verticalPadding, 0); - } - } - - /** - * helper to check if the system bar is on the bottom of the screen - * - * @param ctx - * @return - */ - public static boolean isSystemBarOnBottom(Context ctx) { - WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); - DisplayMetrics metrics = new DisplayMetrics(); - wm.getDefaultDisplay().getMetrics(metrics); - Configuration cfg = ctx.getResources().getConfiguration(); - boolean canMove = (metrics.widthPixels != metrics.heightPixels && - cfg.smallestScreenWidthDp < 600); - - return (!canMove || metrics.widthPixels < metrics.heightPixels); - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/KeyboardUtil.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/KeyboardUtil.java deleted file mode 100644 index 6ee03fad..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/KeyboardUtil.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015 Mike Penz All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.mikepenz.materialdrawer.util; - -import android.app.Activity; -import android.graphics.Rect; -import android.os.Build; -import android.view.View; -import android.view.ViewTreeObserver; -import android.view.inputmethod.InputMethodManager; - -/** - * Created by mikepenz on 14.03.15. - * This class implements a hack to change the layout padding on bottom if the keyboard is shown - * to allow long lists with editTextViews - * Basic idea for this solution found here: http://stackoverflow.com/a/9108219/325479 - * @deprecated Do not use this anymore, the MaterialDrawer uses the `fitsSystemWindows` now correctly so it should not be required. (it would only be required for cases with the fullscreen flag) - */ -@Deprecated -public class KeyboardUtil { - private View decorView; - private View contentView; - - public KeyboardUtil(Activity act, View contentView) { - this.decorView = act.getWindow().getDecorView(); - this.contentView = contentView; - - //only required on newer android versions. it was working on API level 19 - if (Build.VERSION.SDK_INT >= 19) { - decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); - } - } - - public void enable() { - if (Build.VERSION.SDK_INT >= 19) { - decorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener); - } - } - - public void disable() { - if (Build.VERSION.SDK_INT >= 19) { - decorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener); - } - } - - - //a small helper to allow showing the editText focus - ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - Rect r = new Rect(); - //r will be populated with the coordinates of your view that area still visible. - decorView.getWindowVisibleDisplayFrame(r); - - //get screen height and calculate the difference with the useable area from the r - int height = decorView.getContext().getResources().getDisplayMetrics().heightPixels; - int diff = height - r.bottom; - - //if it could be a keyboard add the padding to the view - if (diff != 0) { - // if the use-able screen height differs from the total screen height we assume that it shows a keyboard now - //check if the padding is 0 (if yes set the padding for the keyboard) - if (contentView.getPaddingBottom() != diff) { - //set the padding of the contentView for the keyboard - contentView.setPadding(0, 0, 0, diff); - } - } else { - //check if the padding is != 0 (if yes reset the padding) - if (contentView.getPaddingBottom() != 0) { - //reset the padding of the contentView - contentView.setPadding(0, 0, 0, 0); - } - } - } - }; - - - /** - * Helper to hide the keyboard - * - * @param act - */ - public static void hideKeyboard(Activity act) { - if (act != null && act.getCurrentFocus() != null) { - InputMethodManager inputMethodManager = (InputMethodManager) act.getSystemService(Activity.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(act.getCurrentFocus().getWindowToken(), 0); - } - } -} diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/PressedEffectStateListDrawable.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/PressedEffectStateListDrawable.java deleted file mode 100644 index 546b6c32..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/util/PressedEffectStateListDrawable.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.mikepenz.materialdrawer.util; - -import android.annotation.SuppressLint; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; - -/** - * http://stackoverflow.com/questions/7979440/android-cloning-a-drawable-in-order-to-make-a-statelistdrawable-with-filters - * http://stackoverflow.com/users/2075875/malachiasz - */ - -@SuppressLint("InlinedApi") -public class PressedEffectStateListDrawable extends StateListDrawable { - - private int color; - private int selectionColor; - - public PressedEffectStateListDrawable(Drawable drawable, int color, int selectionColor) { - super(); - - drawable = drawable.mutate(); - - addState(new int[]{android.R.attr.state_selected}, drawable); - addState(new int[]{}, drawable); - - this.color = color; - this.selectionColor = selectionColor; - } - - @Override - protected boolean onStateChange(int[] states) { - boolean isStatePressedInArray = false; - for (int state : states) { - if (state == android.R.attr.state_selected) { - isStatePressedInArray = true; - } - } - if (isStatePressedInArray) { - super.setColorFilter(selectionColor, PorterDuff.Mode.SRC_IN); - } else { - super.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - return super.onStateChange(states); - } - - @Override - public boolean isStateful() { - return true; - } -} \ No newline at end of file diff --git a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/view/BezelImageView.java b/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/view/BezelImageView.java deleted file mode 100644 index 502f9df9..00000000 --- a/MaterialDrawer/src/main/java/com/mikepenz/materialdrawer/view/BezelImageView.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright 2014 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * The original is from Google you can find here: - * https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/BezelImageView.java - * - * Modified and improved with additional functionality by Mike Penz - */ - -package com.mikepenz.materialdrawer.view; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; - -import androidx.appcompat.widget.AppCompatImageView; -import androidx.core.view.ViewCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.ImageView; - -import com.mikepenz.materialdrawer.R; -import com.mikepenz.materialdrawer.util.DrawerImageLoader; - - -/** - * An {@link android.widget.ImageView} that draws its contents inside a mask and draws a border - * drawable on top. This is useful for applying a beveled look to image contents, but is also - * flexible enough for use with other desired aesthetics. - */ -public class BezelImageView extends AppCompatImageView { - private Paint mBlackPaint; - private Paint mMaskedPaint; - - private Rect mBounds; - private RectF mBoundsF; - - private Drawable mMaskDrawable; - private boolean mDrawCircularShadow = true; - - private ColorMatrixColorFilter mDesaturateColorFilter; - - private int mSelectorAlpha = 150; - private int mSelectorColor; - private ColorFilter mSelectorFilter; - - private boolean mCacheValid = false; - private Bitmap mCacheBitmap; - private int mCachedWidth; - private int mCachedHeight; - - private boolean isPressed = false; - private boolean isSelected; - - public BezelImageView(Context context) { - this(context, null); - } - - public BezelImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BezelImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - // Attribute initialization - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BezelImageView, defStyle, R.style.BezelImageView); - - mMaskDrawable = a.getDrawable(R.styleable.BezelImageView_biv_maskDrawable); - if (mMaskDrawable != null) { - mMaskDrawable.setCallback(this); - } - - mDrawCircularShadow = a.getBoolean(R.styleable.BezelImageView_biv_drawCircularShadow, true); - - mSelectorColor = a.getColor(R.styleable.BezelImageView_biv_selectorOnPress, 0); - - a.recycle(); - - // Other initialization - mBlackPaint = new Paint(); - mBlackPaint.setColor(0xff000000); - - mMaskedPaint = new Paint(); - mMaskedPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - - // Always want a cache allocated. - mCacheBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); - - // Create a desaturate color filter for pressed state. - ColorMatrix cm = new ColorMatrix(); - cm.setSaturation(0); - mDesaturateColorFilter = new ColorMatrixColorFilter(cm); - - //create a selectorFilter if we already have a color - if (mSelectorColor != 0) { - this.mSelectorFilter = new PorterDuffColorFilter(Color.argb(mSelectorAlpha, Color.red(mSelectorColor), Color.green(mSelectorColor), Color.blue(mSelectorColor)), PorterDuff.Mode.SRC_ATOP); - } - } - - @Override - protected void onSizeChanged(int w, int h, int old_w, int old_h) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (mDrawCircularShadow) { - setOutlineProvider(new CustomOutline(w, h)); - } - } - } - - @TargetApi(21) - private class CustomOutline extends ViewOutlineProvider { - - int width; - int height; - - CustomOutline(int width, int height) { - this.width = width; - this.height = height; - } - - @Override - public void getOutline(View view, Outline outline) { - outline.setOval(0, 0, width, height); - } - } - - @Override - protected boolean setFrame(int l, int t, int r, int b) { - final boolean changed = super.setFrame(l, t, r, b); - mBounds = new Rect(0, 0, r - l, b - t); - mBoundsF = new RectF(mBounds); - - if (mMaskDrawable != null) { - mMaskDrawable.setBounds(mBounds); - } - - if (changed) { - mCacheValid = false; - } - - return changed; - } - - @Override - protected void onDraw(Canvas canvas) { - if (mBounds == null) { - return; - } - - int width = mBounds.width(); - int height = mBounds.height(); - - if (width == 0 || height == 0) { - return; - } - - if (!mCacheValid || width != mCachedWidth || height != mCachedHeight || isSelected != isPressed) { - // Need to redraw the cache - if (width == mCachedWidth && height == mCachedHeight) { - // Have a correct-sized bitmap cache already allocated. Just erase it. - mCacheBitmap.eraseColor(0); - } else { - // Allocate a new bitmap with the correct dimensions. - mCacheBitmap.recycle(); - //noinspection AndroidLintDrawAllocation - mCacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCachedWidth = width; - mCachedHeight = height; - } - - Canvas cacheCanvas = new Canvas(mCacheBitmap); - if (mMaskDrawable != null) { - int sc = cacheCanvas.save(); - mMaskDrawable.draw(cacheCanvas); - if (isSelected) { - if (mSelectorFilter != null) { - mMaskedPaint.setColorFilter(mSelectorFilter); - } else { - mMaskedPaint.setColorFilter(mDesaturateColorFilter); - - } - } else { - mMaskedPaint.setColorFilter(null); - } - cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.ALL_SAVE_FLAG); - super.onDraw(cacheCanvas); - cacheCanvas.restoreToCount(sc); - } else if (isSelected) { - int sc = cacheCanvas.save(); - cacheCanvas.drawRect(0, 0, mCachedWidth, mCachedHeight, mBlackPaint); - if (mSelectorFilter != null) { - mMaskedPaint.setColorFilter(mSelectorFilter); - } else { - mMaskedPaint.setColorFilter(mDesaturateColorFilter); - } - cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.ALL_SAVE_FLAG); - super.onDraw(cacheCanvas); - cacheCanvas.restoreToCount(sc); - } else { - super.onDraw(cacheCanvas); - } - } - - // Draw from cache - canvas.drawBitmap(mCacheBitmap, mBounds.left, mBounds.top, null); - - //remember the previous press state - isPressed = isPressed(); - } - - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - // Check for clickable state and do nothing if disabled - if (!this.isClickable()) { - this.isSelected = false; - return super.onTouchEvent(event); - } - - // Set selected state based on Motion Event - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - this.isSelected = true; - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_SCROLL: - case MotionEvent.ACTION_OUTSIDE: - case MotionEvent.ACTION_CANCEL: - this.isSelected = false; - break; - } - - // Redraw image and return super type - this.invalidate(); - return super.dispatchTouchEvent(event); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (mMaskDrawable != null && mMaskDrawable.isStateful()) { - mMaskDrawable.setState(getDrawableState()); - } - if (isDuplicateParentStateEnabled()) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - @Override - public void invalidateDrawable(Drawable who) { - if (who == mMaskDrawable) { - invalidate(); - } else { - super.invalidateDrawable(who); - } - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return who == mMaskDrawable || super.verifyDrawable(who); - } - - - /** - * Sets the color of the selector to be draw over the - * CircularImageView. Be sure to provide some opacity. - * - * @param selectorColor The color (including alpha) to set for the selector overlay. - */ - public void setSelectorColor(int selectorColor) { - this.mSelectorColor = selectorColor; - this.mSelectorFilter = new PorterDuffColorFilter(Color.argb(mSelectorAlpha, Color.red(mSelectorColor), Color.green(mSelectorColor), Color.blue(mSelectorColor)), PorterDuff.Mode.SRC_ATOP); - this.invalidate(); - } - - - @Override - public void setImageDrawable(Drawable drawable) { - super.setImageDrawable(drawable); - } - - @Override - public void setImageResource(int resId) { - super.setImageResource(resId); - } - - @Override - public void setImageBitmap(Bitmap bm) { - super.setImageBitmap(bm); - } - - @Override - public void setImageURI(Uri uri) { - if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) { - DrawerImageLoader.getInstance().setImage(this, uri, null); - } else { - super.setImageURI(uri); - } - } - - private ColorMatrixColorFilter mTempDesaturateColorFilter; - private ColorFilter mTempSelectorFilter; - - public void disableTouchFeedback(boolean disable) { - if (disable) { - mTempDesaturateColorFilter = this.mDesaturateColorFilter; - mTempSelectorFilter = this.mSelectorFilter; - this.mSelectorFilter = null; - this.mDesaturateColorFilter = null; - } else { - if (mTempDesaturateColorFilter != null) { - this.mDesaturateColorFilter = mTempDesaturateColorFilter; - } - if (mTempSelectorFilter != null) { - this.mSelectorFilter = mTempSelectorFilter; - } - } - } -} diff --git a/MaterialDrawer/src/main/res/drawable/material_drawer_circle_mask.xml b/MaterialDrawer/src/main/res/drawable/material_drawer_circle_mask.xml deleted file mode 100644 index b0f0dc8b..00000000 --- a/MaterialDrawer/src/main/res/drawable/material_drawer_circle_mask.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_bottom.xml b/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_bottom.xml deleted file mode 100644 index 178b097e..00000000 --- a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_bottom.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_left.9.png b/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_left.9.png deleted file mode 100644 index 1b5763a8..00000000 Binary files a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_left.9.png and /dev/null differ diff --git a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_right.9.png b/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_right.9.png deleted file mode 100644 index 224cc4ff..00000000 Binary files a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_right.9.png and /dev/null differ diff --git a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_top.xml b/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_top.xml deleted file mode 100644 index a8dbbb49..00000000 --- a/MaterialDrawer/src/main/res/drawable/material_drawer_shadow_top.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer.xml b/MaterialDrawer/src/main/res/layout/material_drawer.xml deleted file mode 100644 index 857b8a2f..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_compact_header.xml b/MaterialDrawer/src/main/res/layout/material_drawer_compact_header.xml deleted file mode 100644 index fea09e69..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_compact_header.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_fits_not.xml b/MaterialDrawer/src/main/res/layout/material_drawer_fits_not.xml deleted file mode 100644 index 0fa02287..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_fits_not.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_header.xml b/MaterialDrawer/src/main/res/layout/material_drawer_header.xml deleted file mode 100644 index 04ae7d80..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_header.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_container.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_container.xml deleted file mode 100644 index 087928db..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_container.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_divider.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_divider.xml deleted file mode 100644 index 2409ff9d..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_divider.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_expandable.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_expandable.xml deleted file mode 100644 index e4d19a5f..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_expandable.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_expandable_badge.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_expandable_badge.xml deleted file mode 100644 index 0b8ef1f2..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_expandable_badge.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_mini.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_mini.xml deleted file mode 100644 index fd5b3afb..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_mini.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_mini_profile.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_mini_profile.xml deleted file mode 100644 index 539d1ddf..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_mini_profile.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_primary.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_primary.xml deleted file mode 100644 index 15244f47..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_primary.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_profile.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_profile.xml deleted file mode 100644 index efe87870..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_profile.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_profile_setting.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_profile_setting.xml deleted file mode 100644 index c03f9718..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_profile_setting.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary.xml deleted file mode 100644 index da51fc8d..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary_switch.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary_switch.xml deleted file mode 100644 index 03b1736e..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary_switch.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary_toggle.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary_toggle.xml deleted file mode 100644 index b94919c9..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_secondary_toggle.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_section.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_section.xml deleted file mode 100644 index 86530a5d..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_section.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_switch.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_switch.xml deleted file mode 100644 index af15a74e..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_switch.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_item_toggle.xml b/MaterialDrawer/src/main/res/layout/material_drawer_item_toggle.xml deleted file mode 100644 index ea5779a0..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_item_toggle.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_recycler_view.xml b/MaterialDrawer/src/main/res/layout/material_drawer_recycler_view.xml deleted file mode 100644 index 9f23362f..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_recycler_view.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/layout/material_drawer_slider.xml b/MaterialDrawer/src/main/res/layout/material_drawer_slider.xml deleted file mode 100644 index a903073d..00000000 --- a/MaterialDrawer/src/main/res/layout/material_drawer_slider.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/values-sw600dp/dimens.xml b/MaterialDrawer/src/main/res/values-sw600dp/dimens.xml deleted file mode 100644 index e8811a92..00000000 --- a/MaterialDrawer/src/main/res/values-sw600dp/dimens.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 24dp - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/values/attrs.xml b/MaterialDrawer/src/main/res/values/attrs.xml deleted file mode 100644 index 2f769a02..00000000 --- a/MaterialDrawer/src/main/res/values/attrs.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/values/colors.xml b/MaterialDrawer/src/main/res/values/colors.xml deleted file mode 100644 index 42ef4d3b..00000000 --- a/MaterialDrawer/src/main/res/values/colors.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - @color/materialize_primary - @color/materialize_primary_dark - @color/materialize_primary_light - @color/materialize_accent - - - @color/md_white_1000 - - @color/md_light_primary_text - - @color/md_light_secondary - @color/md_light_secondary - @color/md_light_disabled - @color/md_light_disabled - @color/md_light_dividers - - #1F2196F3 - #E8E8E8 - @color/material_drawer_primary - - @color/material_drawer_primary_text - @color/material_drawer_secondary_text - - - @color/md_dark_background - - @color/md_dark_primary_text - - @color/md_dark_primary_icon - @color/md_dark_secondary - @color/md_dark_disabled - @color/md_dark_disabled - @color/md_dark_dividers - - #1F2196F3 - #202020 - @color/material_drawer_primary - - - @color/material_drawer_dark_primary_text - - @color/material_drawer_dark_secondary_text - diff --git a/MaterialDrawer/src/main/res/values/dimens.xml b/MaterialDrawer/src/main/res/values/dimens.xml deleted file mode 100644 index bc3fb3ef..00000000 --- a/MaterialDrawer/src/main/res/values/dimens.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - 320dp - - 0dp - - 8dp - - 16dp - - - 4dp - - - 160dp - 72dp - 16dp - 4dp - 56dp - 40dp - 56dp - 20sp - 13sp - 80dp - 22dp - 5dp - 18dp - 56dp - - - 8dp - 4dp - 8dp - 4dp - - - 48dp - 56dp - 12dp - 32dp - 14sp - 14sp - 12sp - - 42dp - 56dp - 12dp - 36dp - 14sp - 12sp - - 72dp - 40dp - 48dp - 16dp - 8dp - 14sp - 14sp - - 24dp - - 72dp - 4dp - 8dp - 56dp - 16dp - 12sp - 40dp - 16dp - - - 14sp - diff --git a/MaterialDrawer/src/main/res/values/donottranslate_library_materialdrawer_strings.xml b/MaterialDrawer/src/main/res/values/donottranslate_library_materialdrawer_strings.xml deleted file mode 100644 index b730bb0c..00000000 --- a/MaterialDrawer/src/main/res/values/donottranslate_library_materialdrawer_strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/MaterialDrawer/src/main/res/values/ids.xml b/MaterialDrawer/src/main/res/values/ids.xml deleted file mode 100644 index e9cce5b7..00000000 --- a/MaterialDrawer/src/main/res/values/ids.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MaterialDrawer/src/main/res/values/strings.xml b/MaterialDrawer/src/main/res/values/strings.xml deleted file mode 100644 index c26a8b87..00000000 --- a/MaterialDrawer/src/main/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - Open - Close - diff --git a/MaterialDrawer/src/main/res/values/styles.xml b/MaterialDrawer/src/main/res/values/styles.xml deleted file mode 100644 index b4e22ebb..00000000 --- a/MaterialDrawer/src/main/res/values/styles.xml +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/agendacalendarview/build.gradle b/agendacalendarview/build.gradle index 28cbb26d..92731fb9 100644 --- a/agendacalendarview/build.gradle +++ b/agendacalendarview/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' //apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk android { lintOptions { @@ -12,7 +12,7 @@ android { defaultConfig { minSdkVersion 14 - targetSdkVersion rootProject.ext.targetSdkVersion + targetSdkVersion setup.targetSdk versionCode 1 versionName "1.0" } @@ -43,12 +43,12 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Google libraries - implementation "androidx.appcompat:appcompat:${androidXAppCompat}" - implementation "androidx.recyclerview:recyclerview:${androidXRecyclerView}" - implementation "com.google.android.material:material:${googleMaterial}" + implementation "androidx.appcompat:appcompat:${versions.appcompat}" + implementation "androidx.recyclerview:recyclerview:${versions.recyclerView}" + implementation "com.google.android.material:material:${versions.material}" // other libraries //implementation 'se.emilsjolander:stickylistheaders:2.7.0' - implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT' + implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT@aar' implementation 'io.reactivex:rxjava:1.1.1' } diff --git a/app/build.gradle b/app/build.gradle index 3abf1031..ca5c9fcd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,14 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.gms.google-services' apply plugin: 'io.fabric' android { signingConfigs { } - compileSdkVersion rootProject.ext.compileSdkVersion + compileSdkVersion setup.compileSdk defaultConfig { applicationId 'pl.szczodrzynski.edziennik' minSdkVersion setup.minSdk @@ -15,6 +16,12 @@ android { versionCode release.versionCode versionName release.versionName multiDexEnabled true + + externalNativeBuild { + cmake { + cppFlags "-std=c++11" + } + } } buildTypes { applicationVariants.all { variant -> @@ -62,6 +69,12 @@ android { packagingOptions { exclude 'META-INF/library-core_release.kotlin_module' } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.10.2" + } + } } /*task finalizeBundleDebug(type: Copy) { @@ -91,7 +104,7 @@ tasks.whenTaskAdded { task -> dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - annotationProcessor "androidx.room:room-compiler:${versions.room}" + kapt "androidx.room:room-compiler:${versions.room}" debugImplementation "com.amitshekhar.android:debug-db:1.0.5" implementation "android.arch.navigation:navigation-fragment-ktx:${versions.navigationFragment}" @@ -115,6 +128,7 @@ dependencies { implementation "com.mikepenz:iconics-core:${versions.iconics}" implementation "com.mikepenz:iconics-views:${versions.iconics}" implementation "com.mikepenz:community-material-typeface:${versions.font_cmd}@aar" + implementation "com.mikepenz:materialize:1.2.1" implementation "com.github.kuba2k2:NavLib:${versions.navlib}" @@ -131,7 +145,7 @@ dependencies { implementation("com.github.ozodrukh:CircularReveal:2.0.1@aar") {transitive = true} implementation "com.heinrichreimersoftware:material-intro:1.5.8" // do not update implementation "com.jaredrummler:colorpicker:1.0.2" - implementation "com.squareup.okhttp3:okhttp:3.12.0" + implementation "com.squareup.okhttp3:okhttp:3.12.2" implementation "com.theartofdev.edmodo:android-image-cropper:2.8.0" // do not update implementation "com.wdullaer:materialdatetimepicker:4.1.2" implementation "com.yuyh.json:jsonviewer:1.0.6" @@ -142,7 +156,7 @@ dependencies { implementation "org.jsoup:jsoup:1.10.1" implementation "pl.droidsonroids.gif:android-gif-drawable:1.2.15" //implementation "se.emilsjolander:stickylistheaders:2.7.0" - implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT' + implementation 'com.github.edisonw:StickyListHeaders:master-SNAPSHOT@aar' implementation "uk.co.samuelwall:material-tap-target-prompt:2.14.0" implementation project(":agendacalendarview") @@ -152,6 +166,33 @@ dependencies { implementation project(":nachos") //implementation project(":Navigation") implementation project(":szkolny-font") + + implementation "com.github.ChuckerTeam.Chucker:library:3.0.1" + //releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:3.0.1" + + //implementation 'com.github.wulkanowy:uonet-request-signer:master-SNAPSHOT' + //implementation 'com.github.kuba2k2.uonet-request-signer:android:master-63f094b14a-1' + + //implementation "org.redundent:kotlin-xml-builder:1.5.3" + + implementation "io.github.wulkanowy:signer-android:0.1.1" + + implementation "androidx.work:work-runtime-ktx:${versions.work}" + + implementation 'com.hypertrack:hyperlog:0.0.10' + + implementation 'com.github.kuba2k2:RecyclerTabLayout:700f980584' + + implementation 'com.github.kuba2k2:Tachyon:551943a6b5' + + implementation "com.squareup.retrofit2:retrofit:${versions.retrofit}" + implementation "com.squareup.retrofit2:converter-gson:${versions.retrofit}" + + implementation 'com.github.jetradarmobile:android-snowfall:1.2.0' + + implementation "io.coil-kt:coil:0.9.2" + + implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76' } repositories { mavenCentral() diff --git a/app/debug/output.json b/app/debug/output.json deleted file mode 100644 index db263901..00000000 --- a/app/debug/output.json +++ /dev/null @@ -1 +0,0 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":2600,"versionName":"2.6","enabled":true,"outputFile":"Edziennik_2600_debug.apk","fullName":"debug","baseName":"debug"},"path":"Edziennik_2600_debug.apk","properties":{}}] \ No newline at end of file diff --git a/app/libs/java-json.jar b/app/libs/java-json.jar new file mode 100644 index 00000000..2f211e36 Binary files /dev/null and b/app/libs/java-json.jar differ diff --git a/app/proguard/android-job.pro b/app/proguard/android-job.pro new file mode 100644 index 00000000..3f1a67be --- /dev/null +++ b/app/proguard/android-job.pro @@ -0,0 +1,14 @@ +-dontwarn com.evernote.android.job.gcm.** +-dontwarn com.evernote.android.job.GcmAvailableHelper +-dontwarn com.evernote.android.job.work.** +-dontwarn com.evernote.android.job.WorkManagerAvailableHelper + +-keep public class com.evernote.android.job.v21.PlatformJobService +-keep public class com.evernote.android.job.v14.PlatformAlarmService +-keep public class com.evernote.android.job.v14.PlatformAlarmReceiver +-keep public class com.evernote.android.job.JobBootReceiver +-keep public class com.evernote.android.job.JobRescheduleService +-keep public class com.evernote.android.job.gcm.PlatformGcmService +-keep public class com.evernote.android.job.work.PlatformWorker + +-keep class com.evernote.android.job.** { *; } \ No newline at end of file diff --git a/app/proguard/app.pro b/app/proguard/app.pro new file mode 100644 index 00000000..711a62e4 --- /dev/null +++ b/app/proguard/app.pro @@ -0,0 +1,68 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-keep class android.support.v7.widget.** { *; } + +-keep class pl.szczodrzynski.edziennik.utils.models.** { *; } +-keep class pl.szczodrzynski.edziennik.data.db.entity.Event { *; } +-keep class pl.szczodrzynski.edziennik.data.db.full.EventFull { *; } +-keep class pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage { *; } +-keep class pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel { *; } +-keepclassmembers class pl.szczodrzynski.edziennik.ui.widgets.WidgetConfig { public *; } +-keepnames class pl.szczodrzynski.edziennik.ui.widgets.timetable.WidgetTimetableProvider +-keepnames class pl.szczodrzynski.edziennik.ui.widgets.notifications.WidgetNotificationsProvider +-keepnames class pl.szczodrzynski.edziennik.ui.widgets.luckynumber.WidgetLuckyNumberProvider + +-keepnames class androidx.appcompat.view.menu.MenuBuilder { setHeaderTitleInt(java.lang.CharSequence); } +-keepclassmembernames class androidx.appcompat.view.menu.StandardMenuPopup { private *; } +-keepnames class androidx.appcompat.view.menu.MenuPopupHelper { showPopup(int, int, boolean, boolean); } + +-keepclassmembernames class com.mikepenz.materialdrawer.widget.MiniDrawerSliderView { private *; } + +-keep class .R +-keep class **.R$* { + ; +} + +-keepattributes SourceFile,LineNumberTable +#-printmapping mapping.txt + +-keep class okhttp3.** { *; } + +-keep class com.google.android.material.tabs.** {*;} + +# ServiceLoader support + -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + +-keepclasseswithmembernames class * { + native ; +} + +-keep class pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing { public final byte[] pleaseStopRightNow(java.lang.String, long); } + +-keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.request.** { *; } +-keepclassmembernames class pl.szczodrzynski.edziennik.data.api.szkolny.response.** { *; } diff --git a/app/proguard/blurry.pro b/app/proguard/blurry.pro new file mode 100644 index 00000000..980f404e --- /dev/null +++ b/app/proguard/blurry.pro @@ -0,0 +1 @@ +-keep class android.support.v8.renderscript.** { *; } \ No newline at end of file diff --git a/app/proguard/cafebar.pro b/app/proguard/cafebar.pro new file mode 100644 index 00000000..6f8147fd --- /dev/null +++ b/app/proguard/cafebar.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\AndroidSDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +-keep class !android.support.v7.internal.view.menu.**,android.support.** {*;} +-keep class android.support.v7.graphics.** { *; } +-dontwarn android.support.v7.graphics.** + +-keep class android.support.design.widget.** { *; } +-keep interface android.support.design.widget.** { *; } +-dontwarn android.support.design.** diff --git a/app/proguard/eventbus.pro b/app/proguard/eventbus.pro new file mode 100644 index 00000000..cf6b3480 --- /dev/null +++ b/app/proguard/eventbus.pro @@ -0,0 +1,10 @@ +-keepattributes *Annotation* +-keepclassmembers class * { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +# Only required if you use AsyncExecutor +-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { + (java.lang.Throwable); +} \ No newline at end of file diff --git a/app/proguard/iconics.pro b/app/proguard/iconics.pro new file mode 100644 index 00000000..0aa62122 --- /dev/null +++ b/app/proguard/iconics.pro @@ -0,0 +1,14 @@ +# Android iconics library - https://github.com/mikepenz/Android-Iconics +# Warning: works ONLY with iconics > 1.0.0 +# +# Tested on gradle config: +# +# compile 'com.mikepenz:iconics-core:1.7.1@aar' +# + +-keep class com.mikepenz.iconics.** { *; } +-keep class com.mikepenz.community_material_typeface_library.CommunityMaterial +-keep class com.mikepenz.fontawesome_typeface_library.FontAwesome +-keep class com.mikepenz.google_material_typeface_library.GoogleMaterial +-keep class com.mikepenz.meteocons_typeface_library.Meteoconcs +-keep class com.mikepenz.octicons_typeface_library.Octicons \ No newline at end of file diff --git a/app/proguard/jsoup.pro b/app/proguard/jsoup.pro new file mode 100644 index 00000000..cb139ecd --- /dev/null +++ b/app/proguard/jsoup.pro @@ -0,0 +1 @@ +-keep class org.jsoup.** \ No newline at end of file diff --git a/app/proguard/mhttp.pro b/app/proguard/mhttp.pro new file mode 100644 index 00000000..6d3e4e38 --- /dev/null +++ b/app/proguard/mhttp.pro @@ -0,0 +1,48 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/wangchao/Work/android-sdk/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-dontwarn im.wangchao.** +-dontwarn okio.** +-dontwarn javax.annotation.Nullable +-dontwarn javax.annotation.ParametersAreNonnullByDefault +-keep class im.wangchao.** { *; } +-keep class **_HttpBinder { *; } +-keepclasseswithmembernames class * { + @im.wangchao.* ; +} +-keepclasseswithmembernames class * { + @im.wangchao.* ; +} +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + !static !transient ; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} +# okhttp +-dontwarn okhttp3.** +-dontwarn okio.** +-dontwarn javax.annotation.** +-dontwarn org.conscrypt.** +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# If you do not use Rx: +-dontwarn rx.** \ No newline at end of file diff --git a/app/proguard/okhttp3.pro b/app/proguard/okhttp3.pro new file mode 100644 index 00000000..d17d3566 --- /dev/null +++ b/app/proguard/okhttp3.pro @@ -0,0 +1,19 @@ +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform + +# This is added for okhttp 3.1.2 bug fix as shown at https://github.com/square/okhttp/issues/2323 +-keepclassmembers class * implements javax.net.ssl.SSLSocketFactory { + private javax.net.ssl.SSLSocketFactory delegate; +} + +-keepnames class sun.security.ssl.SSLContextImpl +-keepnames class javax.net.ssl.SSLSocketFactory diff --git a/app/proguard/szkolny-font.pro b/app/proguard/szkolny-font.pro new file mode 100644 index 00000000..4e48be73 --- /dev/null +++ b/app/proguard/szkolny-font.pro @@ -0,0 +1 @@ +-keep class com.mikepenz.szkolny_font_typeface_library.SzkolnyFont { *; } diff --git a/app/proguard/wear.pro b/app/proguard/wear.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/app/proguard/wear.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/proguard/zxing.pro b/app/proguard/zxing.pro new file mode 100644 index 00000000..d5eeb97d --- /dev/null +++ b/app/proguard/zxing.pro @@ -0,0 +1,5 @@ +# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} \ No newline at end of file diff --git a/app/sampledata/check/ic_check.xml b/app/sampledata/check/ic_check.xml new file mode 100644 index 00000000..f621023c --- /dev/null +++ b/app/sampledata/check/ic_check.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/sampledata/format-bold/ic_format_bold.xml b/app/sampledata/format-bold/ic_format_bold.xml new file mode 100644 index 00000000..87bc9819 --- /dev/null +++ b/app/sampledata/format-bold/ic_format_bold.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_night.xml b/app/sampledata/format/ic_format_italic.xml similarity index 57% rename from app/src/main/res/drawable/ic_night.xml rename to app/sampledata/format/ic_format_italic.xml index 008a4fbf..b44be3bb 100644 --- a/app/src/main/res/drawable/ic_night.xml +++ b/app/sampledata/format/ic_format_italic.xml @@ -1,3 +1,7 @@ + + + android:pathData="M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z"/> diff --git a/app/sampledata/format/ic_format_underline.xml b/app/sampledata/format/ic_format_underline.xml new file mode 100644 index 00000000..e3d40e63 --- /dev/null +++ b/app/sampledata/format/ic_format_underline.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/sampledata/settings/ic_settings.xml b/app/sampledata/settings/ic_settings.xml new file mode 100644 index 00000000..efaaa4ee --- /dev/null +++ b/app/sampledata/settings/ic_settings.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1fbc04ec..21c13afb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,17 @@ xmlns:tools="http://schemas.android.com/tools" package="pl.szczodrzynski.edziennik"> + + + + + + + + + + + - + @@ -28,63 +47,7 @@ - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + + + + + + - - - + + + @@ -169,9 +96,10 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_notifications_info" /> - - + + @@ -181,54 +109,110 @@ android:name="android.appwidget.provider" android:resource="@xml/widget_lucky_number_info" /> - + + + + + + + + + + + + + - - + - - - + + + + + + + + + + + + + - - - + - - - + + + + - - - - - - - - - - - - - \ No newline at end of file + diff --git a/app/src/main/assets/pl-changelog.html b/app/src/main/assets/pl-changelog.html index e87bb90e..136a33b8 100644 --- a/app/src/main/assets/pl-changelog.html +++ b/app/src/main/assets/pl-changelog.html @@ -1,60 +1,29 @@ - - - - - - - -

Wersja 3.0, 2019-09-13

+

Wersja 4.0-rc.3, 2020-03-29

    -
  • Nowy wygląd i sposób nawigacji w całej aplikacji.
  • -
  • Menu nawigacji można teraz otworzyć przyciskiem na dolnym pasku. Pociągnięcie w górę tego paska wyświetla menu kontekstowe dotyczące danego widoku.
  • -
  • Założyliśmy serwer Discord! https://discord.gg/n9e8pWr
  • +
  • Wysyłanie wiadomości - funkcja, na którą czekał każdy. Od teraz w Szkolnym można wysyłać oraz odpowiadać na wiadomości do nauczycieli 👏
  • +
  • Przebudowaliśmy cały moduł synchronizacji, co oznacza większą stabilność aplikacji, szybkość oraz poprawność pobieranych danych
  • +
  • Udoskonalony wygląd Szkolnego - sprawi, że korzystanie z aplikacji będzie jeszcze przyjemniejsze
  • +
  • Nowa Strona główna - ładniejszy wygląd oraz możliwość przestawiania kart na każdym profilu
  • +
  • Nowy Plan lekcji - z doskonałą obsługą lekcji przesuniętych oraz dwóch lekcji o tej samej godzinie
  • +
  • Nowe Oceny - z możliwością zmiany wartości plusów oraz minusów oraz wyłączenia niektórych ocen ze średniej
  • +
  • Opcja wyłączenia wybranych powiadomień z aplikacji
  • +
  • Znaczki nieprzeczytanych informacji na obrazkach profili.

  • -
  • Librus: poprawka powielonych ogłoszeń szkolnych.
  • -
  • Naprawiłem błąd nieskończonej synchronizacji w Vulcanie.
  • -
  • Naprawiłem crash launchera przy dodaniu widgetu.
  • -
  • Naprawiłem częste crashe związane z widokiem kalendarza.
  • -
  • Nowe, ładniejsze (choć trochę) motywy kolorów.
  • -
  • Dużo drobnych poprawek UI i działania aplikacji.
  • +
    +
  • Nowe okienka informacji o wydarzeniach oraz lekcjach
  • +
  • Nowe, przyjemniejsze powiadomienia
  • +
  • Dużo poprawek w widoku Wiadomości oraz Ogłoszeń
  • +
  • Częściowa Obsługa dziennika EduDziennik
  • +
  • Librus: opcja logowania w dziennikach Jednostek Samorządu Terytorialnego oraz Oświata w Radomiu
  • +
  • Librus: poprawione obliczanie frekwencji
  • +
  • Librus: obsługa Zadań domowych bez posiadania Mobilnych dodatków (przez system Synergia)
  • +
  • Lepsze przekazywanie powiadomień na komputer oraz łatwiejsze parowanie
  • +
  • Łatwiejsze dodawanie własnych wydarzeń
  • +
  • Poprawiliśmy synchronizację w tle na niektórych telefonach
  • +
  • Usunąłem denerwujący brak zaznaczenia w lewym menu
  • +
  • Znaczna ilość błędów z poprzednich wersji już nie występuje
- - - - \ No newline at end of file +
+
+Dzięki za korzystanie ze Szkolnego!
+© Kuba Szczodrzyński, Kacper Ziubryniewicz 2020 diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..e8096e62 --- /dev/null +++ b/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,44 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. + +add_library( # Sets the name of the library. + szkolny-signing + + # Sets the library as a shared library. + SHARED + + # Provides a relative path to your source file(s). + szkolny-signing.cpp) + +# Searches for a specified prebuilt library and stores the path as a +# variable. Because CMake includes system libraries in the search path by +# default, you only need to specify the name of the public NDK library +# you want to add. CMake verifies that the library exists before +# completing its build. + +#[[find_library( # Sets the name of the path variable. + log-lib + + # Specifies the name of the NDK library that + # you want CMake to locate. + log )]] + +# Specifies libraries CMake should link to your target library. You +# can link multiple libraries, such as libraries you define in this +# build script, prebuilt third-party libraries, or system libraries. + +target_link_libraries( # Specifies the target library. + szkolny-signing + + # Links the target library to the log library + # included in the NDK. + ${log-lib} ) \ No newline at end of file diff --git a/app/src/main/cpp/aes.c b/app/src/main/cpp/aes.c new file mode 100644 index 00000000..c67bc03f --- /dev/null +++ b/app/src/main/cpp/aes.c @@ -0,0 +1,520 @@ +#include +#include +#include "aes.h" + +#include + +#define airport(x) (((x) << 8) | ((x) >> 24)) + +#define TRUE 1 +#define FALSE 0 + +static const toys wtf[16][16] = { + {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, + {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, + {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, + {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, + {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, + {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, + {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, + {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, + {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, + {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, + {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, + {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, + {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, + {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, + {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, + {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} +}; + +static const toys help_me[256][6] = { + {0x00,0x00,0x00,0x00,0x00,0x00},{0x02,0x03,0x09,0x0b,0x0d,0x0e}, + {0x04,0x06,0x12,0x16,0x1a,0x1c},{0x06,0x05,0x1b,0x1d,0x17,0x12}, + {0x08,0x0c,0x24,0x2c,0x34,0x38},{0x0a,0x0f,0x2d,0x27,0x39,0x36}, + {0x0c,0x0a,0x36,0x3a,0x2e,0x24},{0x0e,0x09,0x3f,0x31,0x23,0x2a}, + {0x10,0x18,0x48,0x58,0x68,0x70},{0x12,0x1b,0x41,0x53,0x65,0x7e}, + {0x14,0x1e,0x5a,0x4e,0x72,0x6c},{0x16,0x1d,0x53,0x45,0x7f,0x62}, + {0x18,0x14,0x6c,0x74,0x5c,0x48},{0x1a,0x17,0x65,0x7f,0x51,0x46}, + {0x1c,0x12,0x7e,0x62,0x46,0x54},{0x1e,0x11,0x77,0x69,0x4b,0x5a}, + {0x20,0x30,0x90,0xb0,0xd0,0xe0},{0x22,0x33,0x99,0xbb,0xdd,0xee}, + {0x24,0x36,0x82,0xa6,0xca,0xfc},{0x26,0x35,0x8b,0xad,0xc7,0xf2}, + {0x28,0x3c,0xb4,0x9c,0xe4,0xd8},{0x2a,0x3f,0xbd,0x97,0xe9,0xd6}, + {0x2c,0x3a,0xa6,0x8a,0xfe,0xc4},{0x2e,0x39,0xaf,0x81,0xf3,0xca}, + {0x30,0x28,0xd8,0xe8,0xb8,0x90},{0x32,0x2b,0xd1,0xe3,0xb5,0x9e}, + {0x34,0x2e,0xca,0xfe,0xa2,0x8c},{0x36,0x2d,0xc3,0xf5,0xaf,0x82}, + {0x38,0x24,0xfc,0xc4,0x8c,0xa8},{0x3a,0x27,0xf5,0xcf,0x81,0xa6}, + {0x3c,0x22,0xee,0xd2,0x96,0xb4},{0x3e,0x21,0xe7,0xd9,0x9b,0xba}, + {0x40,0x60,0x3b,0x7b,0xbb,0xdb},{0x42,0x63,0x32,0x70,0xb6,0xd5}, + {0x44,0x66,0x29,0x6d,0xa1,0xc7},{0x46,0x65,0x20,0x66,0xac,0xc9}, + {0x48,0x6c,0x1f,0x57,0x8f,0xe3},{0x4a,0x6f,0x16,0x5c,0x82,0xed}, + {0x4c,0x6a,0x0d,0x41,0x95,0xff},{0x4e,0x69,0x04,0x4a,0x98,0xf1}, + {0x50,0x78,0x73,0x23,0xd3,0xab},{0x52,0x7b,0x7a,0x28,0xde,0xa5}, + {0x54,0x7e,0x61,0x35,0xc9,0xb7},{0x56,0x7d,0x68,0x3e,0xc4,0xb9}, + {0x58,0x74,0x57,0x0f,0xe7,0x93},{0x5a,0x77,0x5e,0x04,0xea,0x9d}, + {0x5c,0x72,0x45,0x19,0xfd,0x8f},{0x5e,0x71,0x4c,0x12,0xf0,0x81}, + {0x60,0x50,0xab,0xcb,0x6b,0x3b},{0x62,0x53,0xa2,0xc0,0x66,0x35}, + {0x64,0x56,0xb9,0xdd,0x71,0x27},{0x66,0x55,0xb0,0xd6,0x7c,0x29}, + {0x68,0x5c,0x8f,0xe7,0x5f,0x03},{0x6a,0x5f,0x86,0xec,0x52,0x0d}, + {0x6c,0x5a,0x9d,0xf1,0x45,0x1f},{0x6e,0x59,0x94,0xfa,0x48,0x11}, + {0x70,0x48,0xe3,0x93,0x03,0x4b},{0x72,0x4b,0xea,0x98,0x0e,0x45}, + {0x74,0x4e,0xf1,0x85,0x19,0x57},{0x76,0x4d,0xf8,0x8e,0x14,0x59}, + {0x78,0x44,0xc7,0xbf,0x37,0x73},{0x7a,0x47,0xce,0xb4,0x3a,0x7d}, + {0x7c,0x42,0xd5,0xa9,0x2d,0x6f},{0x7e,0x41,0xdc,0xa2,0x20,0x61}, + {0x80,0xc0,0x76,0xf6,0x6d,0xad},{0x82,0xc3,0x7f,0xfd,0x60,0xa3}, + {0x84,0xc6,0x64,0xe0,0x77,0xb1},{0x86,0xc5,0x6d,0xeb,0x7a,0xbf}, + {0x88,0xcc,0x52,0xda,0x59,0x95},{0x8a,0xcf,0x5b,0xd1,0x54,0x9b}, + {0x8c,0xca,0x40,0xcc,0x43,0x89},{0x8e,0xc9,0x49,0xc7,0x4e,0x87}, + {0x90,0xd8,0x3e,0xae,0x05,0xdd},{0x92,0xdb,0x37,0xa5,0x08,0xd3}, + {0x94,0xde,0x2c,0xb8,0x1f,0xc1},{0x96,0xdd,0x25,0xb3,0x12,0xcf}, + {0x98,0xd4,0x1a,0x82,0x31,0xe5},{0x9a,0xd7,0x13,0x89,0x3c,0xeb}, + {0x9c,0xd2,0x08,0x94,0x2b,0xf9},{0x9e,0xd1,0x01,0x9f,0x26,0xf7}, + {0xa0,0xf0,0xe6,0x46,0xbd,0x4d},{0xa2,0xf3,0xef,0x4d,0xb0,0x43}, + {0xa4,0xf6,0xf4,0x50,0xa7,0x51},{0xa6,0xf5,0xfd,0x5b,0xaa,0x5f}, + {0xa8,0xfc,0xc2,0x6a,0x89,0x75},{0xaa,0xff,0xcb,0x61,0x84,0x7b}, + {0xac,0xfa,0xd0,0x7c,0x93,0x69},{0xae,0xf9,0xd9,0x77,0x9e,0x67}, + {0xb0,0xe8,0xae,0x1e,0xd5,0x3d},{0xb2,0xeb,0xa7,0x15,0xd8,0x33}, + {0xb4,0xee,0xbc,0x08,0xcf,0x21},{0xb6,0xed,0xb5,0x03,0xc2,0x2f}, + {0xb8,0xe4,0x8a,0x32,0xe1,0x05},{0xba,0xe7,0x83,0x39,0xec,0x0b}, + {0xbc,0xe2,0x98,0x24,0xfb,0x19},{0xbe,0xe1,0x91,0x2f,0xf6,0x17}, + {0xc0,0xa0,0x4d,0x8d,0xd6,0x76},{0xc2,0xa3,0x44,0x86,0xdb,0x78}, + {0xc4,0xa6,0x5f,0x9b,0xcc,0x6a},{0xc6,0xa5,0x56,0x90,0xc1,0x64}, + {0xc8,0xac,0x69,0xa1,0xe2,0x4e},{0xca,0xaf,0x60,0xaa,0xef,0x40}, + {0xcc,0xaa,0x7b,0xb7,0xf8,0x52},{0xce,0xa9,0x72,0xbc,0xf5,0x5c}, + {0xd0,0xb8,0x05,0xd5,0xbe,0x06},{0xd2,0xbb,0x0c,0xde,0xb3,0x08}, + {0xd4,0xbe,0x17,0xc3,0xa4,0x1a},{0xd6,0xbd,0x1e,0xc8,0xa9,0x14}, + {0xd8,0xb4,0x21,0xf9,0x8a,0x3e},{0xda,0xb7,0x28,0xf2,0x87,0x30}, + {0xdc,0xb2,0x33,0xef,0x90,0x22},{0xde,0xb1,0x3a,0xe4,0x9d,0x2c}, + {0xe0,0x90,0xdd,0x3d,0x06,0x96},{0xe2,0x93,0xd4,0x36,0x0b,0x98}, + {0xe4,0x96,0xcf,0x2b,0x1c,0x8a},{0xe6,0x95,0xc6,0x20,0x11,0x84}, + {0xe8,0x9c,0xf9,0x11,0x32,0xae},{0xea,0x9f,0xf0,0x1a,0x3f,0xa0}, + {0xec,0x9a,0xeb,0x07,0x28,0xb2},{0xee,0x99,0xe2,0x0c,0x25,0xbc}, + {0xf0,0x88,0x95,0x65,0x6e,0xe6},{0xf2,0x8b,0x9c,0x6e,0x63,0xe8}, + {0xf4,0x8e,0x87,0x73,0x74,0xfa},{0xf6,0x8d,0x8e,0x78,0x79,0xf4}, + {0xf8,0x84,0xb1,0x49,0x5a,0xde},{0xfa,0x87,0xb8,0x42,0x57,0xd0}, + {0xfc,0x82,0xa3,0x5f,0x40,0xc2},{0xfe,0x81,0xaa,0x54,0x4d,0xcc}, + {0x1b,0x9b,0xec,0xf7,0xda,0x41},{0x19,0x98,0xe5,0xfc,0xd7,0x4f}, + {0x1f,0x9d,0xfe,0xe1,0xc0,0x5d},{0x1d,0x9e,0xf7,0xea,0xcd,0x53}, + {0x13,0x97,0xc8,0xdb,0xee,0x79},{0x11,0x94,0xc1,0xd0,0xe3,0x77}, + {0x17,0x91,0xda,0xcd,0xf4,0x65},{0x15,0x92,0xd3,0xc6,0xf9,0x6b}, + {0x0b,0x83,0xa4,0xaf,0xb2,0x31},{0x09,0x80,0xad,0xa4,0xbf,0x3f}, + {0x0f,0x85,0xb6,0xb9,0xa8,0x2d},{0x0d,0x86,0xbf,0xb2,0xa5,0x23}, + {0x03,0x8f,0x80,0x83,0x86,0x09},{0x01,0x8c,0x89,0x88,0x8b,0x07}, + {0x07,0x89,0x92,0x95,0x9c,0x15},{0x05,0x8a,0x9b,0x9e,0x91,0x1b}, + {0x3b,0xab,0x7c,0x47,0x0a,0xa1},{0x39,0xa8,0x75,0x4c,0x07,0xaf}, + {0x3f,0xad,0x6e,0x51,0x10,0xbd},{0x3d,0xae,0x67,0x5a,0x1d,0xb3}, + {0x33,0xa7,0x58,0x6b,0x3e,0x99},{0x31,0xa4,0x51,0x60,0x33,0x97}, + {0x37,0xa1,0x4a,0x7d,0x24,0x85},{0x35,0xa2,0x43,0x76,0x29,0x8b}, + {0x2b,0xb3,0x34,0x1f,0x62,0xd1},{0x29,0xb0,0x3d,0x14,0x6f,0xdf}, + {0x2f,0xb5,0x26,0x09,0x78,0xcd},{0x2d,0xb6,0x2f,0x02,0x75,0xc3}, + {0x23,0xbf,0x10,0x33,0x56,0xe9},{0x21,0xbc,0x19,0x38,0x5b,0xe7}, + {0x27,0xb9,0x02,0x25,0x4c,0xf5},{0x25,0xba,0x0b,0x2e,0x41,0xfb}, + {0x5b,0xfb,0xd7,0x8c,0x61,0x9a},{0x59,0xf8,0xde,0x87,0x6c,0x94}, + {0x5f,0xfd,0xc5,0x9a,0x7b,0x86},{0x5d,0xfe,0xcc,0x91,0x76,0x88}, + {0x53,0xf7,0xf3,0xa0,0x55,0xa2},{0x51,0xf4,0xfa,0xab,0x58,0xac}, + {0x57,0xf1,0xe1,0xb6,0x4f,0xbe},{0x55,0xf2,0xe8,0xbd,0x42,0xb0}, + {0x4b,0xe3,0x9f,0xd4,0x09,0xea},{0x49,0xe0,0x96,0xdf,0x04,0xe4}, + {0x4f,0xe5,0x8d,0xc2,0x13,0xf6},{0x4d,0xe6,0x84,0xc9,0x1e,0xf8}, + {0x43,0xef,0xbb,0xf8,0x3d,0xd2},{0x41,0xec,0xb2,0xf3,0x30,0xdc}, + {0x47,0xe9,0xa9,0xee,0x27,0xce},{0x45,0xea,0xa0,0xe5,0x2a,0xc0}, + {0x7b,0xcb,0x47,0x3c,0xb1,0x7a},{0x79,0xc8,0x4e,0x37,0xbc,0x74}, + {0x7f,0xcd,0x55,0x2a,0xab,0x66},{0x7d,0xce,0x5c,0x21,0xa6,0x68}, + {0x73,0xc7,0x63,0x10,0x85,0x42},{0x71,0xc4,0x6a,0x1b,0x88,0x4c}, + {0x77,0xc1,0x71,0x06,0x9f,0x5e},{0x75,0xc2,0x78,0x0d,0x92,0x50}, + {0x6b,0xd3,0x0f,0x64,0xd9,0x0a},{0x69,0xd0,0x06,0x6f,0xd4,0x04}, + {0x6f,0xd5,0x1d,0x72,0xc3,0x16},{0x6d,0xd6,0x14,0x79,0xce,0x18}, + {0x63,0xdf,0x2b,0x48,0xed,0x32},{0x61,0xdc,0x22,0x43,0xe0,0x3c}, + {0x67,0xd9,0x39,0x5e,0xf7,0x2e},{0x65,0xda,0x30,0x55,0xfa,0x20}, + {0x9b,0x5b,0x9a,0x01,0xb7,0xec},{0x99,0x58,0x93,0x0a,0xba,0xe2}, + {0x9f,0x5d,0x88,0x17,0xad,0xf0},{0x9d,0x5e,0x81,0x1c,0xa0,0xfe}, + {0x93,0x57,0xbe,0x2d,0x83,0xd4},{0x91,0x54,0xb7,0x26,0x8e,0xda}, + {0x97,0x51,0xac,0x3b,0x99,0xc8},{0x95,0x52,0xa5,0x30,0x94,0xc6}, + {0x8b,0x43,0xd2,0x59,0xdf,0x9c},{0x89,0x40,0xdb,0x52,0xd2,0x92}, + {0x8f,0x45,0xc0,0x4f,0xc5,0x80},{0x8d,0x46,0xc9,0x44,0xc8,0x8e}, + {0x83,0x4f,0xf6,0x75,0xeb,0xa4},{0x81,0x4c,0xff,0x7e,0xe6,0xaa}, + {0x87,0x49,0xe4,0x63,0xf1,0xb8},{0x85,0x4a,0xed,0x68,0xfc,0xb6}, + {0xbb,0x6b,0x0a,0xb1,0x67,0x0c},{0xb9,0x68,0x03,0xba,0x6a,0x02}, + {0xbf,0x6d,0x18,0xa7,0x7d,0x10},{0xbd,0x6e,0x11,0xac,0x70,0x1e}, + {0xb3,0x67,0x2e,0x9d,0x53,0x34},{0xb1,0x64,0x27,0x96,0x5e,0x3a}, + {0xb7,0x61,0x3c,0x8b,0x49,0x28},{0xb5,0x62,0x35,0x80,0x44,0x26}, + {0xab,0x73,0x42,0xe9,0x0f,0x7c},{0xa9,0x70,0x4b,0xe2,0x02,0x72}, + {0xaf,0x75,0x50,0xff,0x15,0x60},{0xad,0x76,0x59,0xf4,0x18,0x6e}, + {0xa3,0x7f,0x66,0xc5,0x3b,0x44},{0xa1,0x7c,0x6f,0xce,0x36,0x4a}, + {0xa7,0x79,0x74,0xd3,0x21,0x58},{0xa5,0x7a,0x7d,0xd8,0x2c,0x56}, + {0xdb,0x3b,0xa1,0x7a,0x0c,0x37},{0xd9,0x38,0xa8,0x71,0x01,0x39}, + {0xdf,0x3d,0xb3,0x6c,0x16,0x2b},{0xdd,0x3e,0xba,0x67,0x1b,0x25}, + {0xd3,0x37,0x85,0x56,0x38,0x0f},{0xd1,0x34,0x8c,0x5d,0x35,0x01}, + {0xd7,0x31,0x97,0x40,0x22,0x13},{0xd5,0x32,0x9e,0x4b,0x2f,0x1d}, + {0xcb,0x23,0xe9,0x22,0x64,0x47},{0xc9,0x20,0xe0,0x29,0x69,0x49}, + {0xcf,0x25,0xfb,0x34,0x7e,0x5b},{0xcd,0x26,0xf2,0x3f,0x73,0x55}, + {0xc3,0x2f,0xcd,0x0e,0x50,0x7f},{0xc1,0x2c,0xc4,0x05,0x5d,0x71}, + {0xc7,0x29,0xdf,0x18,0x4a,0x63},{0xc5,0x2a,0xd6,0x13,0x47,0x6d}, + {0xfb,0x0b,0x31,0xca,0xdc,0xd7},{0xf9,0x08,0x38,0xc1,0xd1,0xd9}, + {0xff,0x0d,0x23,0xdc,0xc6,0xcb},{0xfd,0x0e,0x2a,0xd7,0xcb,0xc5}, + {0xf3,0x07,0x15,0xe6,0xe8,0xef},{0xf1,0x04,0x1c,0xed,0xe5,0xe1}, + {0xf7,0x01,0x07,0xf0,0xf2,0xf3},{0xf5,0x02,0x0e,0xfb,0xff,0xfd}, + {0xeb,0x13,0x79,0x92,0xb4,0xa7},{0xe9,0x10,0x70,0x99,0xb9,0xa9}, + {0xef,0x15,0x6b,0x84,0xae,0xbb},{0xed,0x16,0x62,0x8f,0xa3,0xb5}, + {0xe3,0x1f,0x5d,0xbe,0x80,0x9f},{0xe1,0x1c,0x54,0xb5,0x8d,0x91}, + {0xe7,0x19,0x4f,0xa8,0x9a,0x83},{0xe5,0x1a,0x46,0xa3,0x97,0x8d} +}; + +void throat(const toys *decide, toys *selection, size_t plane) +{ + size_t idx; + + for (idx = 0; idx < plane; idx++) + selection[idx] ^= decide[idx]; +} + +int death(const toys *squirrel, size_t dear, toys *awful, const arrest *wrong, int magic, const toys *fit) +{ + toys supermarket[calculator], software[calculator], bookstore[calculator]; + int brainwash, jellybean; + + if (dear % calculator != 0) + return(FALSE); + + brainwash = dear / calculator; + + memcpy(bookstore, fit, calculator); + + for (jellybean = 0; jellybean < brainwash; jellybean++) { + memcpy(supermarket, &squirrel[jellybean * calculator], calculator); + throat(bookstore, supermarket, calculator); + punishment(supermarket, software, wrong, magic); + memcpy(&awful[jellybean * calculator], software, calculator); + memcpy(bookstore, software, calculator); + } + + return(TRUE); +} + +arrest please(arrest lol) +{ + unsigned int apps; + + apps = (int)wtf[(lol >> 4) & 0x0000000F][lol & 0x0000000F]; + apps += (int)wtf[(lol >> 12) & 0x0000000F][(lol >> 8) & 0x0000000F] << 8; + apps += (int)wtf[(lol >> 20) & 0x0000000F][(lol >> 16) & 0x0000000F] << 16; + apps += (int)wtf[(lol >> 28) & 0x0000000F][(lol >> 24) & 0x0000000F] << 24; + return(apps); +} + +void stoprightnow(const toys *fuckoff, arrest *waste, int roadtrip) +{ + int tour=4,cause,lively,reply; + arrest nope,desert[]={0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, + 0x40000000, 0x80000000, 0x1b000000, 0x36000000, 0x6c000000, 0xd8000000, + 0xab000000, 0x4d000000, 0x9a000000}; + + switch (roadtrip) { + case 128: cause = 10; lively = 4; break; + case 192: cause = 12; lively = 6; break; + case 256: cause = 14; lively = 8; break; + default: return; + } + + for (reply=0; reply < lively; ++reply) { + waste[reply] = ((fuckoff[4 * reply]) << 24) | ((fuckoff[4 * reply + 1]) << 16) | + ((fuckoff[4 * reply + 2]) << 8) | ((fuckoff[4 * reply + 3])); + } + + for (reply = lively; reply < tour * (cause + 1); ++reply) { + nope = waste[reply - 1]; + if ((reply % lively) == 0) + nope = please(airport(nope)) ^ desert[(reply - 1) / lively]; + else if (lively > 6 && (reply % lively) == 4) + nope = please(nope); + waste[reply] = waste[reply - lively] ^ nope; + } +} + +void hot(toys rare[][4], const arrest powerful[]) +{ + toys interfere[4]; + + // memcpy(interfere,&powerful[idx],4); // Not accurate for big endian machines + // Subkey 1 + interfere[0] = powerful[0] >> 24; + interfere[1] = powerful[0] >> 16; + interfere[2] = powerful[0] >> 8; + interfere[3] = powerful[0]; + rare[0][0] ^= interfere[0]; + rare[1][0] ^= interfere[1]; + rare[2][0] ^= interfere[2]; + rare[3][0] ^= interfere[3]; + // Subkey 2 + interfere[0] = powerful[1] >> 24; + interfere[1] = powerful[1] >> 16; + interfere[2] = powerful[1] >> 8; + interfere[3] = powerful[1]; + rare[0][1] ^= interfere[0]; + rare[1][1] ^= interfere[1]; + rare[2][1] ^= interfere[2]; + rare[3][1] ^= interfere[3]; + // Subkey 3 + interfere[0] = powerful[2] >> 24; + interfere[1] = powerful[2] >> 16; + interfere[2] = powerful[2] >> 8; + interfere[3] = powerful[2]; + rare[0][2] ^= interfere[0]; + rare[1][2] ^= interfere[1]; + rare[2][2] ^= interfere[2]; + rare[3][2] ^= interfere[3]; + // Subkey 4 + interfere[0] = powerful[3] >> 24; + interfere[1] = powerful[3] >> 16; + interfere[2] = powerful[3] >> 8; + interfere[3] = powerful[3]; + rare[0][3] ^= interfere[0]; + rare[1][3] ^= interfere[1]; + rare[2][3] ^= interfere[2]; + rare[3][3] ^= interfere[3]; +} + +void numerous(toys vigorous[][4]) +{ + vigorous[0][0] = wtf[vigorous[0][0] >> 4][vigorous[0][0] & 0x0F]; + vigorous[0][1] = wtf[vigorous[0][1] >> 4][vigorous[0][1] & 0x0F]; + vigorous[0][2] = wtf[vigorous[0][2] >> 4][vigorous[0][2] & 0x0F]; + vigorous[0][3] = wtf[vigorous[0][3] >> 4][vigorous[0][3] & 0x0F]; + vigorous[1][0] = wtf[vigorous[1][0] >> 4][vigorous[1][0] & 0x0F]; + vigorous[1][1] = wtf[vigorous[1][1] >> 4][vigorous[1][1] & 0x0F]; + vigorous[1][2] = wtf[vigorous[1][2] >> 4][vigorous[1][2] & 0x0F]; + vigorous[1][3] = wtf[vigorous[1][3] >> 4][vigorous[1][3] & 0x0F]; + vigorous[2][0] = wtf[vigorous[2][0] >> 4][vigorous[2][0] & 0x0F]; + vigorous[2][1] = wtf[vigorous[2][1] >> 4][vigorous[2][1] & 0x0F]; + vigorous[2][2] = wtf[vigorous[2][2] >> 4][vigorous[2][2] & 0x0F]; + vigorous[2][3] = wtf[vigorous[2][3] >> 4][vigorous[2][3] & 0x0F]; + vigorous[3][0] = wtf[vigorous[3][0] >> 4][vigorous[3][0] & 0x0F]; + vigorous[3][1] = wtf[vigorous[3][1] >> 4][vigorous[3][1] & 0x0F]; + vigorous[3][2] = wtf[vigorous[3][2] >> 4][vigorous[3][2] & 0x0F]; + vigorous[3][3] = wtf[vigorous[3][3] >> 4][vigorous[3][3] & 0x0F]; +} + +void crowded(toys chalk[][4]) +{ + int t; + + // Shift left by 1 + t = chalk[1][0]; + chalk[1][0] = chalk[1][1]; + chalk[1][1] = chalk[1][2]; + chalk[1][2] = chalk[1][3]; + chalk[1][3] = t; + // Shift left by 2 + t = chalk[2][0]; + chalk[2][0] = chalk[2][2]; + chalk[2][2] = t; + t = chalk[2][1]; + chalk[2][1] = chalk[2][3]; + chalk[2][3] = t; + // Shift left by 3 + t = chalk[3][0]; + chalk[3][0] = chalk[3][3]; + chalk[3][3] = chalk[3][2]; + chalk[3][2] = chalk[3][1]; + chalk[3][1] = t; +} + +void scale(toys oh_no[][4]) +{ + toys idk[4]; + + // Column 1 + idk[0] = oh_no[0][0]; + idk[1] = oh_no[1][0]; + idk[2] = oh_no[2][0]; + idk[3] = oh_no[3][0]; + oh_no[0][0] = help_me[idk[0]][0]; + oh_no[0][0] ^= help_me[idk[1]][1]; + oh_no[0][0] ^= idk[2]; + oh_no[0][0] ^= idk[3]; + oh_no[1][0] = idk[0]; + oh_no[1][0] ^= help_me[idk[1]][0]; + oh_no[1][0] ^= help_me[idk[2]][1]; + oh_no[1][0] ^= idk[3]; + oh_no[2][0] = idk[0]; + oh_no[2][0] ^= idk[1]; + oh_no[2][0] ^= help_me[idk[2]][0]; + oh_no[2][0] ^= help_me[idk[3]][1]; + oh_no[3][0] = help_me[idk[0]][1]; + oh_no[3][0] ^= idk[1]; + oh_no[3][0] ^= idk[2]; + oh_no[3][0] ^= help_me[idk[3]][0]; + // Column 2 + idk[0] = oh_no[0][1]; + idk[1] = oh_no[1][1]; + idk[2] = oh_no[2][1]; + idk[3] = oh_no[3][1]; + oh_no[0][1] = help_me[idk[0]][0]; + oh_no[0][1] ^= help_me[idk[1]][1]; + oh_no[0][1] ^= idk[2]; + oh_no[0][1] ^= idk[3]; + oh_no[1][1] = idk[0]; + oh_no[1][1] ^= help_me[idk[1]][0]; + oh_no[1][1] ^= help_me[idk[2]][1]; + oh_no[1][1] ^= idk[3]; + oh_no[2][1] = idk[0]; + oh_no[2][1] ^= idk[1]; + oh_no[2][1] ^= help_me[idk[2]][0]; + oh_no[2][1] ^= help_me[idk[3]][1]; + oh_no[3][1] = help_me[idk[0]][1]; + oh_no[3][1] ^= idk[1]; + oh_no[3][1] ^= idk[2]; + oh_no[3][1] ^= help_me[idk[3]][0]; + // Column 3 + idk[0] = oh_no[0][2]; + idk[1] = oh_no[1][2]; + idk[2] = oh_no[2][2]; + idk[3] = oh_no[3][2]; + oh_no[0][2] = help_me[idk[0]][0]; + oh_no[0][2] ^= help_me[idk[1]][1]; + oh_no[0][2] ^= idk[2]; + oh_no[0][2] ^= idk[3]; + oh_no[1][2] = idk[0]; + oh_no[1][2] ^= help_me[idk[1]][0]; + oh_no[1][2] ^= help_me[idk[2]][1]; + oh_no[1][2] ^= idk[3]; + oh_no[2][2] = idk[0]; + oh_no[2][2] ^= idk[1]; + oh_no[2][2] ^= help_me[idk[2]][0]; + oh_no[2][2] ^= help_me[idk[3]][1]; + oh_no[3][2] = help_me[idk[0]][1]; + oh_no[3][2] ^= idk[1]; + oh_no[3][2] ^= idk[2]; + oh_no[3][2] ^= help_me[idk[3]][0]; + // Column 4 + idk[0] = oh_no[0][3]; + idk[1] = oh_no[1][3]; + idk[2] = oh_no[2][3]; + idk[3] = oh_no[3][3]; + oh_no[0][3] = help_me[idk[0]][0]; + oh_no[0][3] ^= help_me[idk[1]][1]; + oh_no[0][3] ^= idk[2]; + oh_no[0][3] ^= idk[3]; + oh_no[1][3] = idk[0]; + oh_no[1][3] ^= help_me[idk[1]][0]; + oh_no[1][3] ^= help_me[idk[2]][1]; + oh_no[1][3] ^= idk[3]; + oh_no[2][3] = idk[0]; + oh_no[2][3] ^= idk[1]; + oh_no[2][3] ^= help_me[idk[2]][0]; + oh_no[2][3] ^= help_me[idk[3]][1]; + oh_no[3][3] = help_me[idk[0]][1]; + oh_no[3][3] ^= idk[1]; + oh_no[3][3] ^= idk[2]; + oh_no[3][3] ^= help_me[idk[3]][0]; +} + +void punishment(const toys friends[], toys number[], const arrest wish[], int hang) +{ + toys burst[4][4]; + + burst[0][0] = friends[0]; + burst[1][0] = friends[1]; + burst[2][0] = friends[2]; + burst[3][0] = friends[3]; + burst[0][1] = friends[4]; + burst[1][1] = friends[5]; + burst[2][1] = friends[6]; + burst[3][1] = friends[7]; + burst[0][2] = friends[8]; + burst[1][2] = friends[9]; + burst[2][2] = friends[10]; + burst[3][2] = friends[11]; + burst[0][3] = friends[12]; + burst[1][3] = friends[13]; + burst[2][3] = friends[14]; + burst[3][3] = friends[15]; + + hot(burst, &wish[0]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[4]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[8]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[12]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[16]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[20]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[24]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[28]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[32]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[36]); + if (hang != 128) { + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[40]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[44]); + if (hang != 192) { + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[48]); + numerous(burst); + crowded(burst); + scale(burst); + hot(burst, &wish[52]); + numerous(burst); + crowded(burst); + hot(burst, &wish[56]); + } + else { + numerous(burst); + crowded(burst); + hot(burst, &wish[48]); + } + } + else { + numerous(burst); + crowded(burst); + hot(burst, &wish[40]); + } + + number[0] = burst[0][0]; + number[1] = burst[1][0]; + number[2] = burst[2][0]; + number[3] = burst[3][0]; + number[4] = burst[0][1]; + number[5] = burst[1][1]; + number[6] = burst[2][1]; + number[7] = burst[3][1]; + number[8] = burst[0][2]; + number[9] = burst[1][2]; + number[10] = burst[2][2]; + number[11] = burst[3][2]; + number[12] = burst[0][3]; + number[13] = burst[1][3]; + number[14] = burst[2][3]; + number[15] = burst[3][3]; +} + diff --git a/app/src/main/cpp/aes.h b/app/src/main/cpp/aes.h new file mode 100644 index 00000000..55856f40 --- /dev/null +++ b/app/src/main/cpp/aes.h @@ -0,0 +1,27 @@ +#ifndef AES_H +#define AES_H + +#include + +#define calculator 16 + +typedef unsigned char toys; +typedef unsigned int arrest; + +void stoprightnow(const toys *fuckoff, + arrest *waste, + int roadtrip); + +void punishment(const toys *friends, + toys *number, + const arrest *wish, + int hang); + +int death(const toys *squirrel, + size_t dear, + toys *awful, + const arrest *wrong, + int magic, + const toys *fit); + +#endif // AES_H diff --git a/app/src/main/cpp/base64.cpp b/app/src/main/cpp/base64.cpp new file mode 100644 index 00000000..5170d8b4 --- /dev/null +++ b/app/src/main/cpp/base64.cpp @@ -0,0 +1,55 @@ +#include "jni.h" +#include +#include + +const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +char *kill_me(const char *coil, size_t room) { + int canvas = 0; + size_t irritating; + int exchange = 0; + char *untidy = NULL; + char *excellent = NULL; + int sincere = 0; + char development[4]; + int trade = 0; + irritating = room / 3; + exchange = room % 3; + if (exchange > 0) { + irritating += 1; + } + irritating = irritating * 4 + 1; + untidy = (char *) malloc(irritating); + + if (untidy == NULL) { + exit(0); + } + memset(untidy, 0, irritating); + excellent = untidy; + while (sincere < room) { + exchange = 0; + canvas = 0; + memset(development, '\0', 4); + while (exchange < 3) { + if (sincere >= room) { + break; + } + canvas = ((canvas << 8) | (coil[sincere] & 0xFF)); + sincere++; + exchange++; + } + canvas = (canvas << ((3 - exchange) * 8)); + for (trade = 0; trade < 4; trade++) { + if (exchange < trade) { + development[trade] = 0x40; + } + else { + development[trade] = (canvas >> ((3 - trade) * 6)) & 0x3F; + } + *excellent = base[development[trade]]; + excellent++; + } + } + *excellent = '\0'; + return untidy; +} diff --git a/app/src/main/cpp/szkolny-signing.cpp b/app/src/main/cpp/szkolny-signing.cpp new file mode 100644 index 00000000..e902129b --- /dev/null +++ b/app/src/main/cpp/szkolny-signing.cpp @@ -0,0 +1,78 @@ +#include +#include +#include "aes.c" +#include "aes.h" +#include "base64.cpp" + +#define overrated (2*1024*1024) +#define teeth 256 + +/*secret password - removed for source code publication*/ +static toys AES_IV[16] = { + 0xa7, 0x84, 0xf9, 0xdc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat); + +extern "C" JNIEXPORT jstring JNICALL +Java_pl_szczodrzynski_edziennik_data_api_szkolny_interceptor_Signing_iLoveApple( + JNIEnv* nut, + jobject guitar, + jbyteArray school, + jstring history, + jlong brush) { + + unsigned int chickens = (unsigned int) (nut->GetArrayLength(school)); + if (chickens <= 0 || chickens >= overrated) { + return NULL; + } + + unsigned char *leg = (unsigned char*) nut->GetByteArrayElements(school, NULL); + if (!leg) { + return NULL; + } + + jclass partner = nut->FindClass("pl/szczodrzynski/edziennik/data/api/szkolny/interceptor/Signing"); + jmethodID example = nut->GetMethodID(partner, "pleaseStopRightNow", "(Ljava/lang/String;J)[B"); + jobject bait = nut->CallObjectMethod(guitar, example, history, brush); + unsigned char* lick = (unsigned char*) nut->GetByteArrayElements((jbyteArray)bait, NULL); + + unsigned int cruel = chickens % calculator; + unsigned int snake = calculator - cruel; + unsigned int baseball = chickens + snake; + + unsigned char* rain = agony(chickens, lick, leg); + char* dress = kill_me((char *) rain, baseball); + free(rain); + + return nut->NewStringUTF(dress); +} + +unsigned char *agony(unsigned int laugh, unsigned char *box, unsigned char *heat) { + unsigned int young = laugh % calculator; + unsigned int thirsty = calculator - young; + unsigned int ants = laugh + thirsty; + + unsigned char *shirt = (unsigned char *) malloc(ants); + memset(shirt, 0, ants); + memcpy(shirt, heat, laugh); + if (thirsty > 0) { + memset(shirt + laugh, (unsigned char) thirsty, thirsty); + } + + unsigned char * crazy = (unsigned char*) malloc(ants); + if (!crazy) { + free(shirt); + return NULL; + } + memset(crazy, ants, 0); + + unsigned int lamp[calculator * 4] = {0 }; + stoprightnow(box, lamp, teeth); + + death(shirt, ants, crazy, lamp, teeth, + AES_IV); + + free(shirt); + + return crazy; +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.java b/app/src/main/java/pl/szczodrzynski/edziennik/App.java deleted file mode 100644 index 09926597..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/App.java +++ /dev/null @@ -1,717 +0,0 @@ -package pl.szczodrzynski.edziennik; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.content.pm.Signature; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.Icon; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.provider.Settings; -import android.util.Base64; -import android.util.Log; -import android.util.Pair; -import android.widget.Toast; - -import com.evernote.android.job.JobManager; -import com.google.android.gms.security.ProviderInstaller; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import com.mikepenz.iconics.Iconics; -import com.mikepenz.iconics.IconicsColor; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.IconicsSize; -import com.mikepenz.iconics.typeface.IIcon; -import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont; - -import java.lang.reflect.Field; -import java.security.KeyStore; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.ConcurrentModificationException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatDelegate; -import cat.ereza.customactivityoncrash.config.CaocConfig; -import im.wangchao.mhttp.MHttp; -import im.wangchao.mhttp.internal.cookie.PersistentCookieJar; -import im.wangchao.mhttp.internal.cookie.cache.SetCookieCache; -import im.wangchao.mhttp.internal.cookie.persistence.SharedPrefsCookiePersistor; -import me.leolin.shortcutbadger.ShortcutBadger; -import okhttp3.ConnectionSpec; -import okhttp3.OkHttpClient; -import okhttp3.TlsVersion; -import pl.szczodrzynski.edziennik.activities.CrashActivity; -import pl.szczodrzynski.edziennik.api.Edziennik; -import pl.szczodrzynski.edziennik.api.Iuczniowie; -import pl.szczodrzynski.edziennik.api.Librus; -import pl.szczodrzynski.edziennik.api.Mobidziennik; -import pl.szczodrzynski.edziennik.api.Vulcan; -import pl.szczodrzynski.edziennik.datamodels.AppDb; -import pl.szczodrzynski.edziennik.datamodels.DebugLog; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.models.AppConfig; -import pl.szczodrzynski.edziennik.network.NetworkUtils; -import pl.szczodrzynski.edziennik.network.TLSSocketFactory; -import pl.szczodrzynski.edziennik.receivers.BootReceiver; -import pl.szczodrzynski.edziennik.receivers.JobsCreator; -import pl.szczodrzynski.edziennik.sync.SyncJob; -import pl.szczodrzynski.edziennik.utils.PermissionChecker; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_MOBIDZIENNIK; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_VULCAN; - -public class App extends androidx.multidex.MultiDexApplication { - private static final String TAG = "App"; - public static int profileId = -1; - private Context mContext; - - public static final int REQUEST_TIMEOUT = 10 * 1000; - - // notifications - //public NotificationManager mNotificationManager; - //public final String NOTIFICATION_CHANNEL_ID_UPDATES = "4566"; - //public String NOTIFICATION_CHANNEL_NAME_UPDATES; - public Notifier notifier; - - public static final String APP_URL = "://edziennik.szczodrzynski.pl/app/"; - - public ShortcutManager shortcutManager; - - public PermissionChecker permissionChecker; - - public String signature = ""; - public String deviceId = ""; - - public AppDb db; - public void debugLog(String text) { - if (!devMode) - return; - db.debugLogDao().add(new DebugLog(Utils.getCurrentTimeUsingCalendar()+": "+text)); - } - public void debugLogAsync(String text) { - if (!devMode) - return; - AsyncTask.execute(() -> { - db.debugLogDao().add(new DebugLog(Utils.getCurrentTimeUsingCalendar()+": "+text)); - }); - } - - // network & APIs - public NetworkUtils networkUtils; - public PersistentCookieJar cookieJar; - public OkHttpClient http; - public OkHttpClient httpLazy; - //public 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 AppConfig appConfig; // APPCONFIG: common for all profiles - //public AppProfile profile; // current profile - public JsonObject loginStore = null; - public SharedPreferences registerStore; // sharedPreferences for REGISTER - //public Register register; // REGISTER for current profile, read from registerStore - - public ProfileFull profile; - - // other stuff - public Gson gson; - public String requestScheme = "https"; - public boolean unreadBadgesAvailable = true; - - public static boolean devMode = false; - - public static final boolean UPDATES_ON_PLAY_STORE = true; - - @RequiresApi(api = Build.VERSION_CODES.M) - public Icon getDesktopIconFromIconics(IIcon icon) { - final IconicsDrawable drawable = new IconicsDrawable(mContext, icon) - .color(IconicsColor.colorInt(Color.WHITE)) - .size(IconicsSize.dp(48)) - .padding(IconicsSize.dp(8)) - .backgroundColor(IconicsColor.colorRes(R.color.colorPrimaryDark)) - .roundedCorners(IconicsSize.dp(10)); - //drawable.setStyle(Paint.Style.FILL); - final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return Icon.createWithBitmap(bitmap); - } - - @Override - public void onCreate() { - super.onCreate(); - AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); - CaocConfig.Builder.create() - .backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM) //default: CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM - .enabled(true) //default: true - .showErrorDetails(true) //default: true - .showRestartButton(true) //default: true - .logErrorOnRestart(true) //default: true - .trackActivities(true) //default: false - .minTimeBetweenCrashesMs(2000) //default: 3000 - .errorDrawable(R.drawable.ic_rip) //default: bug image - .restartActivity(MainActivity.class) //default: null (your app's launch activity) - .errorActivity(CrashActivity.class) //default: null (default error activity) - //.eventListener(new YourCustomEventListener()) //default: null - .apply(); - mContext = this; - db = AppDb.getDatabase(this); - gson = new Gson(); - networkUtils = new NetworkUtils(this); - 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.registerFont(SzkolnyFont.INSTANCE); - - notifier = new Notifier(this); - permissionChecker = new PermissionChecker(mContext); - - deviceId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); - - cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(this)); - - OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder() - .cache(null) - .followRedirects(true) - .followSslRedirects(true) - .retryOnConnectionFailure(true) - .cookieJar(cookieJar) - .connectTimeout(30, TimeUnit.SECONDS) - .writeTimeout(20, TimeUnit.SECONDS) - .readTimeout(40, TimeUnit.SECONDS); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - try { - try { - ProviderInstaller.installIfNeeded(this); - } catch (Exception e) { - Log.e("OkHttpTLSCompat", "Play Services not found or outdated"); - X509TrustManager x509TrustManager = null; - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - for (TrustManager trustManager: trustManagerFactory.getTrustManagers()) { - if (trustManager instanceof X509TrustManager) - x509TrustManager = (X509TrustManager) trustManager; - } - - SSLContext sc = SSLContext.getInstance("TLSv1.2"); - sc.init(null, null, null); - httpBuilder.sslSocketFactory(new TLSSocketFactory(sc.getSocketFactory()), x509TrustManager); - - ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(TlsVersion.TLS_1_0) - .tlsVersions(TlsVersion.TLS_1_1) - .tlsVersions(TlsVersion.TLS_1_2) - .build(); - - List specs = new ArrayList<>(); - specs.add(cs); - specs.add(ConnectionSpec.COMPATIBLE_TLS); - specs.add(ConnectionSpec.CLEARTEXT); - - httpBuilder.connectionSpecs(specs); - } - - - } catch (Exception exc) { - Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc); - } - } - - http = httpBuilder.build(); - httpLazy = http.newBuilder().followRedirects(false).followSslRedirects(false).build(); - - MHttp.instance() - .customOkHttpClient(http); - - //register = new Register(mContext); - - appSharedPrefs = getSharedPreferences(getString(R.string.preference_file_global), Context.MODE_PRIVATE); - - loadConfig(); - - Themes.INSTANCE.setThemeInt(appConfig.appTheme); - - //profileLoadById(appSharedPrefs.getInt("current_profile_id", 1)); - - try { - PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES); - for (Signature signature: packageInfo.signatures) { - byte[] signatureBytes = signature.toByteArray(); - MessageDigest md = MessageDigest.getInstance("SHA"); - md.update(signatureBytes); - this.signature = Base64.encodeToString(md.digest(), Base64.DEFAULT); - //Log.d(TAG, "Signature is "+this.signature); - } - } - catch (Exception e) { - e.printStackTrace(); - } - - if ("f054761fbdb6a238".equals(deviceId)) { - devMode = true; - } - else if (appConfig.devModePassword != null) { - checkDevModePassword(); - } - - JobManager.create(this).addJobCreator(new JobsCreator()); - if (appConfig.registerSyncEnabled) { - SyncJob.schedule(this); - } - else { - SyncJob.clear(); - } - - db.metadataDao().countUnseen().observeForever(count -> { - Log.d("MainActivity", "Overall unseen count changed"); - assert count != null; - if (unreadBadgesAvailable) { - unreadBadgesAvailable = ShortcutBadger.applyCount(this, count); - } - }); - - //new IonCookieManager(mContext); - - new Handler().post(() -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - shortcutManager = getSystemService(ShortcutManager.class); - - ShortcutInfo shortcutTimetable = new ShortcutInfo.Builder(mContext, "item_timetable") - .setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_timetable)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon2.cmd_timetable)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)) - .build(); - - ShortcutInfo shortcutAgenda = new ShortcutInfo.Builder(mContext, "item_agenda") - .setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_agenda)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon.cmd_calendar)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA)) - .build(); - - ShortcutInfo shortcutGrades = new ShortcutInfo.Builder(mContext, "item_grades") - .setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_grades)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon2.cmd_numeric_5_box)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES)) - .build(); - - ShortcutInfo shortcutHomework = new ShortcutInfo.Builder(mContext, "item_homeworks") - .setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_homework)) - //.setIcon(getDesktopIconFromIconics(SzkolnyFont.Icon.szf_file_document_edit)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORKS)) - .build(); - - ShortcutInfo shortcutMessages = new ShortcutInfo.Builder(mContext, "item_messages") - .setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages)) - .setIcon(Icon.createWithResource(this, R.mipmap.ic_shortcut_messages)) - //.setIcon(getDesktopIconFromIconics(CommunityMaterial.Icon.cmd_email)) - .setIntent(new Intent(Intent.ACTION_MAIN, null, this, MainActivity.class) - .putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES )) - .build(); - - shortcutManager.setDynamicShortcuts(Arrays.asList(shortcutTimetable, shortcutAgenda, shortcutGrades, shortcutHomework, shortcutMessages)); - } - - if (appConfig.appInstalledTime == 0) { - try { - appConfig.appInstalledTime = getPackageManager().getPackageInfo(getPackageName(), 0).firstInstallTime; - appConfig.appRateSnackbarTime = appConfig.appInstalledTime + 7 * 24 * 60 * 60 * 1000; - saveConfig("appInstalledTime", "appRateSnackbarTime"); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } - - /*Task capabilityInfoTask = - Wearable.getCapabilityClient(this) - .getCapability("edziennik_wear_app", CapabilityClient.FILTER_REACHABLE); - capabilityInfoTask.addOnCompleteListener((task) -> { - if (task.isSuccessful()) { - CapabilityInfo capabilityInfo = task.getResult(); - assert capabilityInfo != null; - Set nodes; - nodes = capabilityInfo.getNodes(); - Log.d(TAG, "Nodes "+nodes); - - if (nodes.size() > 0) { - Wearable.getMessageClient(this).sendMessage( - nodes.toArray(new Node[]{})[0].getId(), "/ping", "Hello world".getBytes()); - } - } else { - Log.d(TAG, "Capability request failed to return any results."); - } - }); - - Wearable.getDataClient(this).addListener(dataEventBuffer -> { - Log.d(TAG, "onDataChanged(): " + dataEventBuffer); - - for (DataEvent event : dataEventBuffer) { - if (event.getType() == DataEvent.TYPE_CHANGED) { - String path = event.getDataItem().getUri().getPath(); - Log.d(TAG, "Data "+path+ " :: "+Arrays.toString(event.getDataItem().getData())); - } - } - });*/ - - FirebaseApp pushMobidziennikApp = FirebaseApp.initializeApp( - this, - new FirebaseOptions.Builder() - .setApplicationId("1:1029629079999:android:58bb378dab031f42") - .setGcmSenderId("1029629079999") - .build(), - "Mobidziennik" - ); - - /*FirebaseApp pushLibrusApp = FirebaseApp.initializeApp( - this, - new FirebaseOptions.Builder() - .setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o") - .setApplicationId("1:513056078587:android:1e29083b760af544") - .build(), - "Librus" - );*/ - - FirebaseApp pushVulcanApp = FirebaseApp.initializeApp( - this, - new FirebaseOptions.Builder() - .setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA") - .setApplicationId("1:987828170337:android:ac97431a0a4578c3") - .build(), - "Vulcan" - ); - - try { - final long startTime = System.currentTimeMillis(); - FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for App is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()+". Time is "+(System.currentTimeMillis() - startTime)); - appConfig.fcmToken = instanceIdResult.getToken(); - }); - FirebaseInstanceId.getInstance(pushMobidziennikApp).getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for Mobidziennik is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()); - appConfig.fcmTokens.put(LOGIN_TYPE_MOBIDZIENNIK, new Pair<>(instanceIdResult.getToken(), new ArrayList<>())); - }); - /*FirebaseInstanceId.getInstance(pushLibrusApp).getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for Librus is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()); - appConfig.fcmTokens.put(LOGIN_TYPE_LIBRUS, new Pair<>(instanceIdResult.getToken(), new ArrayList<>())); - });*/ - FirebaseInstanceId.getInstance(pushVulcanApp).getInstanceId().addOnSuccessListener(instanceIdResult -> { - Log.d(TAG, "Token for Vulcan is " + instanceIdResult.getToken() + ", ID is " + instanceIdResult.getId()); - Pair> pair = appConfig.fcmTokens.get(LOGIN_TYPE_VULCAN); - if (pair == null || pair.first == null || !pair.first.equals(instanceIdResult.getToken())) { - appConfig.fcmTokens.put(LOGIN_TYPE_VULCAN, new Pair<>(instanceIdResult.getToken(), new ArrayList<>())); - } - }); - - - FirebaseMessaging.getInstance().subscribeToTopic(getPackageName()); - } - catch (IllegalStateException e) { - e.printStackTrace(); - } - }); - - } - - - public void loadConfig() - { - appConfig = new AppConfig(this); - - - if (appSharedPrefs.contains("config")) { - // remove old-format config, save the new one and empty the incorrectly-nulled config - appConfig = gson.fromJson(appSharedPrefs.getString("config", ""), AppConfig.class); - appSharedPrefs.edit().remove("config").apply(); - saveConfig(); - appConfig = new AppConfig(this); - } - - if (appSharedPrefs.contains("profiles")) { - SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit(); - /*List appProfileIds = gson.fromJson(appSharedPrefs.getString("profiles", ""), new TypeToken>(){}.getType()); - for (int id: appProfileIds) { - AppProfile appProfile = gson.fromJson(appSharedPrefs.getString("profile"+id, ""), AppProfile.class); - if (appProfile != null) { - appConfig.profiles.add(appProfile); - } - appSharedPrefsEditor.remove("profile"+id); - }*/ - appSharedPrefsEditor.remove("profiles"); - appSharedPrefsEditor.apply(); - //profilesSave(); - } - - - Map keys = appSharedPrefs.getAll(); - for (Map.Entry entry : keys.entrySet()) { - if (entry.getKey().startsWith("app.appConfig.")) { - String fieldName = entry.getKey().replace("app.appConfig.", ""); - - try { - Field field = AppConfig.class.getField(fieldName); - Object object; - try { - object = gson.fromJson(entry.getValue().toString(), field.getGenericType()); - } catch (JsonSyntaxException e) { - Log.d(TAG, "For field "+fieldName); - e.printStackTrace(); - object = entry.getValue().toString(); - } - if (object != null) - field.set(appConfig, object); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - appSharedPrefs.edit().remove("app.appConfig."+fieldName).apply(); - } - } - } - - if (appConfig.lastAppVersion > BuildConfig.VERSION_CODE) { - BootReceiver br = new BootReceiver(); - Intent i = new Intent(); - //i.putExtra("UserChecked", true); - br.onReceive(getContext(), i); - Toast.makeText(mContext, R.string.warning_older_version_running, Toast.LENGTH_LONG).show(); - //Toast.makeText(mContext, "Zaktualizuj aplikację.", Toast.LENGTH_LONG).show(); - //System.exit(0); - } - - if (appConfig == null) { - appConfig = new AppConfig(this); - } - } - public void saveConfig() - { - try { - appConfig.savePending = false; - - SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit(); - - JsonObject appConfigJson = gson.toJsonTree(appConfig).getAsJsonObject(); - for (Map.Entry entry : appConfigJson.entrySet()) { - String jsonObj; - jsonObj = entry.getValue().toString(); - /*if (entry.getValue().isJsonObject()) { - jsonObj = entry.getValue().getAsJsonObject().toString(); - } - else if (entry.getValue().isJsonArray()) { - jsonObj = entry.getValue().getAsJsonArray().toString(); - } - else { - jsonObj = entry.getValue().toString(); - }*/ - appSharedPrefsEditor.putString("app.appConfig." + entry.getKey(), jsonObj); - } - - appSharedPrefsEditor.apply(); - } - catch (ConcurrentModificationException e) { - e.printStackTrace(); - } - //appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply(); - } - public void saveConfig(String ... fieldNames) - { - appConfig.savePending = false; - - SharedPreferences.Editor appSharedPrefsEditor = appSharedPrefs.edit(); - - for (String fieldName: fieldNames) { - try { - Object object = AppConfig.class.getField(fieldName).get(appConfig); - appSharedPrefsEditor.putString("app.appConfig."+fieldName, gson.toJson(object)); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (ConcurrentModificationException e) { - e.printStackTrace(); - } - } - - appSharedPrefsEditor.apply(); - //appSharedPrefs.edit().putString("config", gson.toJson(appConfig)).apply(); - } - - - - public void profileSaveAsync() { - AsyncTask.execute(() -> { - db.profileDao().add(profile); - }); - } - public void profileSaveAsync(Profile profile) { - AsyncTask.execute(() -> { - db.profileDao().add(profile); - }); - } - public void profileSaveFullAsync(ProfileFull profile) { - AsyncTask.execute(() -> { - profileSaveFull(profile); - }); - } - public void profileSaveFull(ProfileFull profileFull) { - db.profileDao().add(profileFull); - db.loginStoreDao().add(profileFull); - } - public void profileSaveFull(Profile profile, LoginStore loginStore) { - db.profileDao().add(profile); - db.loginStoreDao().add(loginStore); - } - - public ProfileFull profileGetOrNull(int id) { - return db.profileDao().getByIdNow(id); - } - - public void profileLoadById(int id) { - profileLoadById(id, false); - } - public void profileLoadById(int id, boolean loadedLast) { - //Log.d(TAG, "Loading ID "+id); - /*if (profile == null) { - profile = profileNew(); - AppDb.profileId = profile.id; - appSharedPrefs.edit().putInt("current_profile_id", profile.id).apply(); - return; - }*/ - if (profile == null || profile.getId() != id) { - profile = db.profileDao().getByIdNow(id); - /*if (profile == null) { - profileLoadById(id); - return; - }*/ - if (profile != null) { - MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1); - profileId = profile.getId(); - appSharedPrefs.edit().putInt("current_profile_id", profile.getId()).apply(); - } - else if (!loadedLast) { - profileLoadById(profileLastId(), true); - } - else { - profileId = -1; - } - } - } - - public void profileLoad(ProfileFull profile) { - MainActivity.Companion.setUseOldMessages(profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && appConfig.mobidziennikOldMessages == 1); - this.profile = profile; - profileId = profile.getId(); - } - - /*public void profileRemove(int id) - { - Profile profile = db.profileDao().getByIdNow(id); - - if (profile.id == profile.loginStoreId) { - // this profile is the owner of the login store - // we need to check if any other profile is using it - List transferProfileIds = db.profileDao().getIdsByLoginStoreIdNow(profile.loginStoreId); - if (transferProfileIds.size() == 1) { - // this login store is free of users, remove it along with the profile - db.loginStoreDao().remove(profile.loginStoreId); - // the current store is removed, we are ready to remove the profile - } - else if (transferProfileIds.size() > 1) { - transferProfileIds.remove(transferProfileIds.indexOf(profile.id)); - // someone is using the store - // we need to transfer it to the firstProfileId - db.loginStoreDao().changeId(profile.loginStoreId, transferProfileIds.get(0)); - db.profileDao().changeStoreId(profile.loginStoreId, transferProfileIds.get(0)); - // the current store is removed, we are ready to remove the profile - } - } - // else, the profile uses a store that it doesn't own - // leave the store and go on with removing - - Log.d(TAG, "Before removal: "+db.profileDao().getAllNow().toString()); - db.profileDao().remove(profile.id); - Log.d(TAG, "After removal: "+db.profileDao().getAllNow().toString()); - - *//*int newId = 1; - if (appConfig.profiles.size() > 0) { - newId = appConfig.profiles.get(appConfig.profiles.size() - 1).id; - } - Log.d(TAG, "New ID: "+newId); - //Toast.makeText(mContext, "selected new id "+newId, Toast.LENGTH_SHORT).show(); - profileLoadById(newId);*//* - }*/ - - public int profileFirstId() { - return db.profileDao().getFirstId(); - } - - public int profileLastId() { - return db.profileDao().getLastId(); - } - - - public Context getContext() - { - return mContext; - } - - public void checkDevModePassword() { - try { - if (Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", appConfig.devModePassword).equals("ok here you go it's enabled now")) { - devMode = true; - } - else { - devMode = false; - } - } catch (Exception e) { - e.printStackTrace(); - devMode = false; - } - } - -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/App.kt b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt new file mode 100644 index 00000000..59db1ab2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/App.kt @@ -0,0 +1,361 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik + +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager +import android.graphics.drawable.Icon +import android.os.Build +import android.provider.Settings +import android.util.Log +import androidx.appcompat.app.AppCompatDelegate +import androidx.multidex.MultiDexApplication +import androidx.work.Configuration +import cat.ereza.customactivityoncrash.config.CaocConfig +import com.chuckerteam.chucker.api.ChuckerCollector +import com.chuckerteam.chucker.api.ChuckerInterceptor +import com.chuckerteam.chucker.api.RetentionManager +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.iid.FirebaseInstanceId +import com.google.firebase.messaging.FirebaseMessaging +import com.google.gson.Gson +import com.hypertrack.hyperlog.HyperLog +import com.mikepenz.iconics.Iconics +import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont +import im.wangchao.mhttp.MHttp +import kotlinx.coroutines.* +import me.leolin.shortcutbadger.ShortcutBadger +import okhttp3.OkHttpClient +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.data.api.events.ProfileListEmptyEvent +import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing +import pl.szczodrzynski.edziennik.data.db.AppDb +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.network.NetworkUtils +import pl.szczodrzynski.edziennik.network.cookie.DumbCookieJar +import pl.szczodrzynski.edziennik.sync.SyncWorker +import pl.szczodrzynski.edziennik.sync.UpdateWorker +import pl.szczodrzynski.edziennik.ui.modules.base.CrashActivity +import pl.szczodrzynski.edziennik.utils.* +import pl.szczodrzynski.edziennik.utils.managers.GradesManager +import pl.szczodrzynski.edziennik.utils.managers.NotificationChannelsManager +import pl.szczodrzynski.edziennik.utils.managers.TimetableManager +import pl.szczodrzynski.edziennik.utils.managers.UserActionManager +import java.util.concurrent.TimeUnit +import kotlin.coroutines.CoroutineContext + +class App : MultiDexApplication(), Configuration.Provider, CoroutineScope { + companion object { + @Volatile + lateinit var db: AppDb + val config: Config by lazy { Config(db) } + var profile: Profile by mutableLazy { Profile(0, 0, 0, "") } + val profileId + get() = profile.id + + var devMode = false + var debugMode = false + } + + val notificationChannelsManager by lazy { NotificationChannelsManager(this) } + val userActionManager by lazy { UserActionManager(this) } + val gradesManager by lazy { GradesManager(this) } + val timetableManager by lazy { TimetableManager(this) } + + val db + get() = App.db + val config + get() = App.config + val profile + get() = App.profile + val profileId + get() = App.profileId + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + override fun getWorkManagerConfiguration() = Configuration.Builder() + .setMinimumLoggingLevel(Log.VERBOSE) + .build() + + val permissionChecker by lazy { PermissionChecker(this) } + val networkUtils by lazy { NetworkUtils(this) } + val gson by lazy { Gson() } + + /* _ _ _______ _______ _____ + | | | |__ __|__ __| __ \ + | |__| | | | | | | |__) | + | __ | | | | | | ___/ + | | | | | | | | | | + |_| |_| |_| |_| |*/ + val http: OkHttpClient by lazy { + val builder = OkHttpClient.Builder() + .cache(null) + .followRedirects(true) + .followSslRedirects(true) + .retryOnConnectionFailure(true) + .cookieJar(cookieJar) + .connectTimeout(20, TimeUnit.SECONDS) + .writeTimeout(5, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + builder.installHttpsSupport(this) + + if (debugMode || BuildConfig.DEBUG) { + HyperLog.initialize(this) + HyperLog.setLogLevel(Log.VERBOSE) + HyperLog.setLogFormat(DebugLogFormat(this)) + val chuckerCollector = ChuckerCollector(this, true, RetentionManager.Period.ONE_HOUR) + val chuckerInterceptor = ChuckerInterceptor(this, chuckerCollector) + builder.addInterceptor(chuckerInterceptor) + } + + builder.build() + } + val httpLazy: OkHttpClient by lazy { + http.newBuilder() + .followRedirects(false) + .followSslRedirects(false) + .build() + } + val cookieJar by lazy { DumbCookieJar(this) } + + /* _____ _ _ + / ____(_) | | + | (___ _ __ _ _ __ __ _| |_ _ _ _ __ ___ + \___ \| |/ _` | '_ \ / _` | __| | | | '__/ _ \ + ____) | | (_| | | | | (_| | |_| |_| | | | __/ + |_____/|_|\__, |_| |_|\__,_|\__|\__,_|_| \___| + __/ | + |__*/ + val deviceId: String by lazy { Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) ?: "" } + private var unreadBadgesAvailable = true + + /* _____ _ + / ____| | | + ___ _ __ | | _ __ ___ __ _| |_ ___ + / _ \| '_ \| | | '__/ _ \/ _` | __/ _ \ + | (_) | | | | |____| | | __/ (_| | || __/ + \___/|_| |_|\_____|_| \___|\__,_|\__\__*/ + override fun onCreate() { + super.onCreate() + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) + CaocConfig.Builder.create() + .backgroundMode(CaocConfig.BACKGROUND_MODE_SHOW_CUSTOM) + .enabled(true) + .showErrorDetails(true) + .showRestartButton(true) + .logErrorOnRestart(true) + .trackActivities(true) + .minTimeBetweenCrashesMs(60*1000) + .errorDrawable(R.drawable.ic_rip) + .restartActivity(MainActivity::class.java) + .errorActivity(CrashActivity::class.java) + .apply() + Iconics.init(applicationContext) + Iconics.registerFont(SzkolnyFont) + App.db = AppDb(this) + Themes.themeInt = config.ui.theme + debugMode = config.debugMode + MHttp.instance().customOkHttpClient(http) + + if (!profileLoadById(config.lastProfileId)) { + db.profileDao().firstId?.let { profileLoadById(it) } + } + + devMode = "f054761fbdb6a238" == deviceId || BuildConfig.DEBUG + + Signing.getCert(this) + + launch { + withContext(Dispatchers.Default) { + config.migrate(this@App) + + if (config.devModePassword != null) + checkDevModePassword() + debugMode = devMode || config.debugMode + + if (config.sync.enabled) + SyncWorker.scheduleNext(this@App, false) + else + SyncWorker.cancelNext(this@App) + + if (config.sync.notifyAboutUpdates) + UpdateWorker.scheduleNext(this@App, false) + else + UpdateWorker.cancelNext(this@App) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + val shortcutManager = getSystemService(ShortcutManager::class.java) + + val shortcutTimetable = ShortcutInfo.Builder(this@App, "item_timetable") + .setShortLabel(getString(R.string.shortcut_timetable)).setLongLabel(getString(R.string.shortcut_timetable)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_timetable)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE)) + .build() + + val shortcutAgenda = ShortcutInfo.Builder(this@App, "item_agenda") + .setShortLabel(getString(R.string.shortcut_agenda)).setLongLabel(getString(R.string.shortcut_agenda)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_agenda)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_AGENDA)) + .build() + + val shortcutGrades = ShortcutInfo.Builder(this@App, "item_grades") + .setShortLabel(getString(R.string.shortcut_grades)).setLongLabel(getString(R.string.shortcut_grades)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_grades)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_GRADES)) + .build() + + val shortcutHomework = ShortcutInfo.Builder(this@App, "item_homeworks") + .setShortLabel(getString(R.string.shortcut_homework)).setLongLabel(getString(R.string.shortcut_homework)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_homework)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_HOMEWORK)) + .build() + + val shortcutMessages = ShortcutInfo.Builder(this@App, "item_messages") + .setShortLabel(getString(R.string.shortcut_messages)).setLongLabel(getString(R.string.shortcut_messages)) + .setIcon(Icon.createWithResource(this@App, R.mipmap.ic_shortcut_messages)) + .setIntent(Intent(Intent.ACTION_MAIN, null, this@App, MainActivity::class.java) + .putExtra("fragmentId", MainActivity.DRAWER_ITEM_MESSAGES)) + .build() + + shortcutManager.dynamicShortcuts = listOf( + shortcutTimetable, + shortcutAgenda, + shortcutGrades, + shortcutHomework, + shortcutMessages + ) + } // shortcuts - end + + notificationChannelsManager.registerAllChannels() + + + if (config.appInstalledTime == 0L) + try { + config.appInstalledTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime + config.appRateSnackbarTime = config.appInstalledTime + 7 * DAY * MS + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + + val pushMobidziennikApp = FirebaseApp.initializeApp( + this@App, + FirebaseOptions.Builder() + .setApiKey("AIzaSyCi5LmsZ5BBCQnGtrdvWnp1bWLCNP8OWQE") + .setApplicationId("1:747285019373:android:f6341bf7b158621d") + .build(), + "Mobidziennik2" + ) + + val pushLibrusApp = FirebaseApp.initializeApp( + this@App, + FirebaseOptions.Builder() + .setApiKey("AIzaSyDfTuEoYPKdv4aceEws1CO3n0-HvTndz-o") + .setApplicationId("1:513056078587:android:1e29083b760af544") + .build(), + "Librus" + ) + + val pushVulcanApp = FirebaseApp.initializeApp( + this@App, + FirebaseOptions.Builder() + .setApiKey("AIzaSyDW8MUtanHy64_I0oCpY6cOxB3jrvJd_iA") + .setApplicationId("1:987828170337:android:ac97431a0a4578c3") + .build(), + "Vulcan" + ) + + try { + FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + config.sync.tokenApp = token + } + FirebaseInstanceId.getInstance(pushMobidziennikApp).instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + if (token != config.sync.tokenMobidziennik) { + config.sync.tokenMobidziennik = token + config.sync.tokenMobidziennikList = listOf() + } + } + FirebaseInstanceId.getInstance(pushLibrusApp).instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + if (token != config.sync.tokenLibrus) { + config.sync.tokenLibrus = token + config.sync.tokenLibrusList = listOf() + } + } + FirebaseInstanceId.getInstance(pushVulcanApp).instanceId.addOnSuccessListener { instanceIdResult -> + val token = instanceIdResult.token + if (token != config.sync.tokenVulcan) { + config.sync.tokenVulcan = token + config.sync.tokenVulcanList = listOf() + } + } + FirebaseMessaging.getInstance().subscribeToTopic(packageName) + } catch (e: IllegalStateException) { + e.printStackTrace() + } + } + + db.metadataDao().countUnseen().observeForever { count: Int -> + if (unreadBadgesAvailable) + unreadBadgesAvailable = ShortcutBadger.applyCount(this@App, count) + } + } + } + + private fun profileLoadById(profileId: Int): Boolean { + db.profileDao().getByIdNow(profileId)?.also { + App.profile = it + App.config.lastProfileId = it.id + return true + } + return false + } + fun profileLoad(profileId: Int, onSuccess: (profile: Profile) -> Unit) { + launch { + val success = withContext(Dispatchers.Default) { + profileLoadById(profileId) + } + if (success) + onSuccess(profile) + else + profileLoadLast(onSuccess) + } + } + fun profileLoadLast(onSuccess: (profile: Profile) -> Unit) { + launch { + val success = withContext(Dispatchers.Default) { + profileLoadById(db.profileDao().lastId ?: return@withContext false) + } + if (!success) { + EventBus.getDefault().post(ProfileListEmptyEvent()) + } + } + } + fun profileSave() = profileSave(profile) + fun profileSave(profile: Profile) { + launch(Dispatchers.Default) { + App.db.profileDao().add(profile) + } + } + + fun checkDevModePassword() { + devMode = try { + Utils.AESCrypt.decrypt("nWFVxY65Pa8/aRrT7EylNAencmOD+IxUY2Gg/beiIWY=", config.devModePassword) == "ok here you go it's enabled now" || BuildConfig.DEBUG + } catch (e: Exception) { + e.printStackTrace() + false + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt new file mode 100644 index 00000000..3dc9d0a6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Binding.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-11. + */ +package pl.szczodrzynski.edziennik + +import android.graphics.Paint +import android.widget.TextView +import androidx.databinding.BindingAdapter + +object Binding { + @JvmStatic + @BindingAdapter("strikeThrough") + fun strikeThrough(textView: TextView, strikeThrough: Boolean) { + if (strikeThrough) { + textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG + } else { + textView.paintFlags = textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt index c6d05ce8..7e802ae4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/Extensions.kt @@ -2,25 +2,89 @@ package pl.szczodrzynski.edziennik import android.Manifest import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context +import android.content.Intent import android.content.pm.PackageManager +import android.content.res.ColorStateList +import android.content.res.Resources +import android.database.Cursor +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.Rect +import android.graphics.Typeface +import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle -import android.util.Log +import android.text.* +import android.text.style.ForegroundColorSpan +import android.text.style.StrikethroughSpan +import android.text.style.StyleSpan +import android.util.* +import android.util.Base64 +import android.util.Base64.NO_WRAP +import android.util.Base64.encodeToString +import android.view.View +import android.view.WindowManager +import android.widget.CheckBox +import android.widget.CompoundButton +import android.widget.RadioButton +import android.widget.TextView +import androidx.annotation.* import androidx.core.app.ActivityCompat +import androidx.core.database.getIntOrNull +import androidx.core.database.getLongOrNull +import androidx.core.database.getStringOrNull +import androidx.core.util.forEach +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import com.google.android.gms.security.ProviderInstaller import com.google.gson.JsonArray -import com.google.gson.JsonNull +import com.google.gson.JsonElement import com.google.gson.JsonObject +import com.google.gson.JsonParser import im.wangchao.mhttp.Response -import im.wangchao.mhttp.callback.JsonCallbackHandler -import im.wangchao.mhttp.callback.TextCallbackHandler -import im.wangchao.mhttp.internal.exception.ResponseFailException -import pl.szczodrzynski.edziennik.datamodels.Profile -import pl.szczodrzynski.edziennik.datamodels.Teacher -import pl.szczodrzynski.navlib.R -import pl.szczodrzynski.navlib.crc16 -import pl.szczodrzynski.navlib.getColorFromRes -import kotlin.contracts.contract +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.TlsVersion +import okio.Buffer +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApiException +import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse +import pl.szczodrzynski.edziennik.data.db.entity.Notification +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.network.TLSSocketFactory +import pl.szczodrzynski.edziennik.utils.models.Time +import java.io.InterruptedIOException +import java.io.PrintWriter +import java.io.StringWriter +import java.math.BigInteger +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import java.nio.charset.Charset +import java.security.KeyStore +import java.security.MessageDigest +import java.text.SimpleDateFormat +import java.util.* +import java.util.zip.CRC32 +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLException +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager +import kotlin.Pair + fun List.byId(id: Long) = firstOrNull { it.id == id } fun List.byNameFirstLast(nameFirstLast: String) = firstOrNull { it.name + " " + it.surname == nameFirstLast } @@ -28,16 +92,59 @@ fun List.byNameLastFirst(nameLastFirst: String) = firstOrNull { it.surn fun List.byNameFDotLast(nameFDotLast: String) = firstOrNull { it.name + "." + it.surname == nameFDotLast } fun List.byNameFDotSpaceLast(nameFDotSpaceLast: String) = firstOrNull { it.name + ". " + it.surname == nameFDotSpaceLast } -fun JsonObject.getString(key: String): String? = get(key).let { if (it.isJsonNull) null else it.asString } -fun JsonObject.getInt(key: String): Int? = get(key).let { if (it.isJsonNull) null else it.asInt } -fun JsonObject.getLong(key: String): Long? = get(key).let { if (it.isJsonNull) null else it.asLong } -fun JsonObject.getJsonObject(key: String): JsonObject? = get(key).let { if (it.isJsonNull) null else it.asJsonObject } -fun JsonObject.getJsonArray(key: String): JsonArray? = get(key).let { if (it.isJsonNull) null else it.asJsonArray } +fun JsonObject?.get(key: String): JsonElement? = this?.get(key) + +fun JsonObject?.getBoolean(key: String): Boolean? = get(key)?.let { if (it.isJsonNull) null else it.asBoolean } +fun JsonObject?.getString(key: String): String? = get(key)?.let { if (it.isJsonNull) null else it.asString } +fun JsonObject?.getInt(key: String): Int? = get(key)?.let { if (it.isJsonNull) null else it.asInt } +fun JsonObject?.getLong(key: String): Long? = get(key)?.let { if (it.isJsonNull) null else it.asLong } +fun JsonObject?.getFloat(key: String): Float? = get(key)?.let { if(it.isJsonNull) null else it.asFloat } +fun JsonObject?.getChar(key: String): Char? = get(key)?.let { if(it.isJsonNull) null else it.asCharacter } +fun JsonObject?.getJsonObject(key: String): JsonObject? = get(key)?.let { if (it.isJsonObject) it.asJsonObject else null } +fun JsonObject?.getJsonArray(key: String): JsonArray? = get(key)?.let { if (it.isJsonArray) it.asJsonArray else null } + +fun JsonObject?.getBoolean(key: String, defaultValue: Boolean): Boolean = get(key)?.let { if (it.isJsonNull) defaultValue else it.asBoolean } ?: defaultValue +fun JsonObject?.getString(key: String, defaultValue: String): String = get(key)?.let { if (it.isJsonNull) defaultValue else it.asString } ?: defaultValue +fun JsonObject?.getInt(key: String, defaultValue: Int): Int = get(key)?.let { if (it.isJsonNull) defaultValue else it.asInt } ?: defaultValue +fun JsonObject?.getLong(key: String, defaultValue: Long): Long = get(key)?.let { if (it.isJsonNull) defaultValue else it.asLong } ?: defaultValue +fun JsonObject?.getFloat(key: String, defaultValue: Float): Float = get(key)?.let { if(it.isJsonNull) defaultValue else it.asFloat } ?: defaultValue +fun JsonObject?.getChar(key: String, defaultValue: Char): Char = get(key)?.let { if(it.isJsonNull) defaultValue else it.asCharacter } ?: defaultValue +fun JsonObject?.getJsonObject(key: String, defaultValue: JsonObject): JsonObject = get(key)?.let { if (it.isJsonObject) it.asJsonObject else defaultValue } ?: defaultValue +fun JsonObject?.getJsonArray(key: String, defaultValue: JsonArray): JsonArray = get(key)?.let { if (it.isJsonArray) it.asJsonArray else defaultValue } ?: defaultValue + +fun JsonArray.getBoolean(key: Int): Boolean? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asBoolean } +fun JsonArray.getString(key: Int): String? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asString } +fun JsonArray.getInt(key: Int): Int? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asInt } +fun JsonArray.getLong(key: Int): Long? = if (key >= size()) null else get(key)?.let { if (it.isJsonNull) null else it.asLong } +fun JsonArray.getFloat(key: Int): Float? = if (key >= size()) null else get(key)?.let { if(it.isJsonNull) null else it.asFloat } +fun JsonArray.getChar(key: Int): Char? = if (key >= size()) null else get(key)?.let { if(it.isJsonNull) null else it.asCharacter } +fun JsonArray.getJsonObject(key: Int): JsonObject? = if (key >= size()) null else get(key)?.let { if (it.isJsonObject) it.asJsonObject else null } +fun JsonArray.getJsonArray(key: Int): JsonArray? = if (key >= size()) null else get(key)?.let { if (it.isJsonArray) it.asJsonArray else null } + +fun String.toJsonObject(): JsonObject? = try { JsonParser().parse(this).asJsonObject } catch (ignore: Exception) { null } + +operator fun JsonObject.set(key: String, value: JsonElement) = this.add(key, value) +operator fun JsonObject.set(key: String, value: Boolean) = this.addProperty(key, value) +operator fun JsonObject.set(key: String, value: String?) = this.addProperty(key, value) +operator fun JsonObject.set(key: String, value: Number) = this.addProperty(key, value) +operator fun JsonObject.set(key: String, value: Char) = this.addProperty(key, value) + +operator fun Profile.set(key: String, value: JsonElement) = this.studentData.add(key, value) +operator fun Profile.set(key: String, value: Boolean) = this.studentData.addProperty(key, value) +operator fun Profile.set(key: String, value: String?) = this.studentData.addProperty(key, value) +operator fun Profile.set(key: String, value: Number) = this.studentData.addProperty(key, value) +operator fun Profile.set(key: String, value: Char) = this.studentData.addProperty(key, value) + +fun JsonArray.asJsonObjectList() = this.mapNotNull { it.asJsonObject } fun CharSequence?.isNotNullNorEmpty(): Boolean { return this != null && this.isNotEmpty() } +fun CharSequence?.isNotNullNorBlank(): Boolean { + return this != null && this.isNotBlank() +} + fun currentTimeUnix() = System.currentTimeMillis() / 1000 fun Bundle?.getInt(key: String, defaultValue: Int): Int { @@ -53,35 +160,138 @@ fun Bundle?.getString(key: String, defaultValue: String): String { return this?.getString(key, defaultValue) ?: defaultValue } -fun colorFromName(context: Context, name: String?): Int { - var crc = crc16(name ?: "") - crc = (crc and 0xff) or (crc shr 8) - crc %= 16 - val color = when (crc) { - 13 -> R.color.md_red_500 - 4 -> R.color.md_pink_A400 - 2 -> R.color.md_purple_A400 - 9 -> R.color.md_deep_purple_A700 - 5 -> R.color.md_indigo_500 - 1 -> R.color.md_indigo_A700 - 6 -> R.color.md_cyan_A200 - 14 -> R.color.md_teal_400 - 15 -> R.color.md_green_500 - 7 -> R.color.md_yellow_A700 - 3 -> R.color.md_deep_orange_A400 - 8 -> R.color.md_deep_orange_A700 - 10 -> R.color.md_brown_500 - 12 -> R.color.md_grey_400 - 11 -> R.color.md_blue_grey_400 - else -> R.color.md_light_green_A700 - } - return context.getColorFromRes(color) +/** + * ` The quick BROWN_fox Jumps OveR THE LAZy-DOG. ` + * + * converts to + * + * `The Quick Brown_fox Jumps Over The Lazy-Dog.` + */ +fun String?.fixName(): String { + return this?.fixWhiteSpaces()?.toProperCase() ?: "" } -fun MutableList.filterOutArchived() { - this.removeAll { it.archived } +/** + * `The quick BROWN_fox Jumps OveR THE LAZy-DOG.` + * + * converts to + * + * `The Quick Brown_fox Jumps Over The Lazy-Dog.` + */ +fun String.toProperCase(): String = changeStringCase(this) + +/** + * `John Smith` -> `Smith John` + * + * `JOHN SMith` -> `SMith JOHN` + */ +fun String.swapFirstLastName(): String { + return this.split(" ").let { + if (it.size > 1) + it[1]+" "+it[0] + else + it[0] + } } +fun String.splitName(): Pair? { + return this.split(" ").let { + if (it.size >= 2) Pair(it[0], it[1]) + else null + } +} + +fun changeStringCase(s: String): String { + val delimiters = " '-/" + val sb = StringBuilder() + var capNext = true + for (ch in s.toCharArray()) { + var c = ch + c = if (capNext) + Character.toUpperCase(c) + else + Character.toLowerCase(c) + sb.append(c) + capNext = delimiters.indexOf(c) >= 0 + } + return sb.toString() +} + +fun buildFullName(firstName: String?, lastName: String?): String { + return "$firstName $lastName".fixName() +} + +fun String.getShortName(): String { + return split(" ").let { + if (it.size > 1) + "${it[0]} ${it[1][0]}." + else + it[0] + } +} + +/** + * "John Smith" -> "JS" + * + * "JOHN SMith" -> "JS" + * + * "John" -> "J" + * + * "John " -> "J" + * + * "John Smith " -> "JS" + * + * " " -> "" + * + * " " -> "" + */ +fun String?.getNameInitials(): String { + if (this.isNullOrBlank()) return "" + return this.toUpperCase().fixWhiteSpaces().split(" ").take(2).map { it[0] }.joinToString("") +} + +fun List.join(delimiter: String): String { + return concat(delimiter).toString() +} + +fun colorFromName(name: String?): Int { + val i = (name ?: "").crc32() + return when ((i / 10 % 16 + 1).toInt()) { + 13 -> 0xffF44336 + 4 -> 0xffF50057 + 2 -> 0xffD500F9 + 9 -> 0xff6200EA + 5 -> 0xffFFAB00 + 1 -> 0xff304FFE + 6 -> 0xff40C4FF + 14 -> 0xff26A69A + 15 -> 0xff00C853 + 7 -> 0xffFFD600 + 3 -> 0xffFF3D00 + 8 -> 0xffDD2C00 + 10 -> 0xff795548 + 12 -> 0xff2979FF + 11 -> 0xffFF6D00 + else -> 0xff64DD17 + }.toInt() +} + +fun colorFromCssName(name: String): Int { + return when (name) { + "red" -> 0xffff0000 + "green" -> 0xff008000 + "blue" -> 0xff0000ff + "violet" -> 0xffee82ee + "brown" -> 0xffa52a2a + "orange" -> 0xffffa500 + "black" -> 0xff000000 + "white" -> 0xffffffff + else -> -1 + }.toInt() +} + +fun List.filterOutArchived() = this.filter { !it.archived } + fun Activity.isStoragePermissionGranted(): Boolean { return if (Build.VERSION.SDK_INT >= 23) { if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { @@ -93,4 +303,867 @@ fun Activity.isStoragePermissionGranted(): Boolean { } else { true } -} \ No newline at end of file +} + +fun Response?.getUnixDate(): Long { + val rfcDate = this?.headers()?.get("date") ?: return currentTimeUnix() + val pattern = "EEE, dd MMM yyyy HH:mm:ss Z" + val format = SimpleDateFormat(pattern, Locale.ENGLISH) + return format.parse(rfcDate).time / 1000 +} + +const val MINUTE = 60L +const val HOUR = 60L*MINUTE +const val DAY = 24L*HOUR +const val WEEK = 7L*DAY +const val MONTH = 30L*DAY +const val YEAR = 365L*DAY +const val MS = 1000L + +fun LongSparseArray.values(): List { + val result = mutableListOf() + forEach { _, value -> + result += value + } + return result +} + +fun SparseArray<*>.keys(): List { + val result = mutableListOf() + forEach { key, _ -> + result += key + } + return result +} +fun SparseArray.values(): List { + val result = mutableListOf() + forEach { _, value -> + result += value + } + return result +} + +fun SparseIntArray.keys(): List { + val result = mutableListOf() + forEach { key, _ -> + result += key + } + return result +} +fun SparseIntArray.values(): List { + val result = mutableListOf() + forEach { _, value -> + result += value + } + return result +} + +fun List>.keys(): List { + val result = mutableListOf() + forEach { pair -> + result += pair.first + } + return result +} +fun List>.values(): List { + val result = mutableListOf() + forEach { pair -> + result += pair.second + } + return result +} + +fun List.toSparseArray(destination: SparseArray, key: (T) -> Int) { + forEach { + destination.put(key(it), it) + } +} +fun List.toSparseArray(destination: LongSparseArray, key: (T) -> Long) { + forEach { + destination.put(key(it), it) + } +} + +fun List.toSparseArray(key: (T) -> Int): SparseArray { + val result = SparseArray() + toSparseArray(result, key) + return result +} +fun List.toSparseArray(key: (T) -> Long): LongSparseArray { + val result = LongSparseArray() + toSparseArray(result, key) + return result +} + +fun SparseArray.singleOrNull(predicate: (T) -> Boolean): T? { + forEach { _, value -> + if (predicate(value)) + return value + } + return null +} +fun LongSparseArray.singleOrNull(predicate: (T) -> Boolean): T? { + forEach { _, value -> + if (predicate(value)) + return value + } + return null +} + +fun String.fixWhiteSpaces() = buildString(length) { + var wasWhiteSpace = true + for (c in this@fixWhiteSpaces) { + if (c.isWhitespace()) { + if (!wasWhiteSpace) { + append(c) + wasWhiteSpace = true + } + } else { + append(c) + wasWhiteSpace = false + } + } +}.trimEnd() + +fun List.getById(id: Long): Team? { + return singleOrNull { it.id == id } +} +fun LongSparseArray.getById(id: Long): Team? { + forEach { _, value -> + if (value.id == id) + return value + } + return null +} + +operator fun MatchResult.get(group: Int): String { + if (group >= groupValues.size) + return "" + return groupValues[group] +} + +fun Activity.setLanguage(language: String) { + val locale = Locale(language.toLowerCase(Locale.ROOT)) + val configuration = resources.configuration + Locale.setDefault(locale) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + configuration.setLocale(locale) + } + configuration.locale = locale + resources.updateConfiguration(configuration, resources.displayMetrics) + baseContext.resources.updateConfiguration(configuration, baseContext.resources.displayMetrics) +} + +/* + Code copied from android-28/java.util.Locale.initDefault() + */ +fun initDefaultLocale() { + run { + // user.locale gets priority + /*val languageTag: String? = System.getProperty("user.locale", "") + if (languageTag.isNotNullNorEmpty()) { + return@run Locale(languageTag) + }*/ + + // user.locale is empty + val language: String? = System.getProperty("user.language", "pl") + val region: String? = System.getProperty("user.region") + val country: String? + val variant: String? + // for compatibility, check for old user.region property + if (region != null) { + // region can be of form country, country_variant, or _variant + val i = region.indexOf('_') + if (i >= 0) { + country = region.substring(0, i) + variant = region.substring(i + 1) + } else { + country = region + variant = "" + } + } else { + country = System.getProperty("user.country", "") + variant = System.getProperty("user.variant", "") + } + return@run Locale(language) + }.let { + Locale.setDefault(it) + } +} + +fun String.crc16(): Int { + var crc = 0xFFFF + for (aBuffer in this) { + crc = crc.ushr(8) or (crc shl 8) and 0xffff + crc = crc xor (aBuffer.toInt() and 0xff) // byte to int, trunc sign + crc = crc xor (crc and 0xff shr 4) + crc = crc xor (crc shl 12 and 0xffff) + crc = crc xor (crc and 0xFF shl 5 and 0xffff) + } + crc = crc and 0xffff + return crc + 32768 +} + +fun String.crc32(): Long { + val crc = CRC32() + crc.update(toByteArray()) + return crc.value +} + +fun String.hmacSHA1(password: String): String { + val key = SecretKeySpec(password.toByteArray(), "HmacSHA1") + + val mac = Mac.getInstance("HmacSHA1").apply { + init(key) + update(this@hmacSHA1.toByteArray()) + } + + return encodeToString(mac.doFinal(), NO_WRAP) +} + +fun String.md5(): String { + val md = MessageDigest.getInstance("MD5") + return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0') +} + +fun String.sha256(): ByteArray { + val md = MessageDigest.getInstance("SHA-256") + md.update(toByteArray()) + return md.digest() +} + +fun RequestBody.bodyToString(): String { + val buffer = Buffer() + writeTo(buffer) + return buffer.readUtf8() +} + +fun Long.formatDate(format: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(format).format(this) + +fun CharSequence?.asColoredSpannable(colorInt: Int): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(ForegroundColorSpan(colorInt), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} +fun CharSequence?.asStrikethroughSpannable(): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(StrikethroughSpan(), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} +fun CharSequence?.asItalicSpannable(): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(StyleSpan(Typeface.ITALIC), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} +fun CharSequence?.asBoldSpannable(): Spannable { + val spannable = SpannableString(this) + spannable.setSpan(StyleSpan(Typeface.BOLD), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return spannable +} +fun CharSequence.asSpannable(vararg spans: Any, substring: String? = null, ignoreCase: Boolean = false): Spannable { + val spannable = SpannableString(this) + if (substring == null) { + spans.forEach { + spannable.setSpan(it, 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + else if (substring.isNotEmpty()) { + var index = indexOf(substring, ignoreCase = ignoreCase) + while (index >= 0) { + spans.forEach { + spannable.setSpan(it, index, index + substring.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + index = indexOf(substring, startIndex = index + 1, ignoreCase = ignoreCase) + } + } + return spannable +} + +/** + * Returns a new read-only list only of those given elements, that are not empty. + * Applies for CharSequence and descendants. + */ +fun listOfNotEmpty(vararg elements: T): List = elements.filterNot { it.isEmpty() } + +fun List.concat(delimiter: CharSequence? = null): CharSequence { + if (this.isEmpty()) { + return "" + } + + if (this.size == 1) { + return this[0] ?: "" + } + + var spanned = delimiter is Spanned + if (!spanned) { + for (piece in this) { + if (piece is Spanned) { + spanned = true + break + } + } + } + + var first = true + if (spanned) { + val ssb = SpannableStringBuilder() + for (piece in this) { + if (piece == null) + continue + if (!first && delimiter != null) + ssb.append(delimiter) + first = false + ssb.append(piece) + } + return SpannedString(ssb) + } else { + val sb = StringBuilder() + for (piece in this) { + if (piece == null) + continue + if (!first && delimiter != null) + sb.append(delimiter) + first = false + sb.append(piece) + } + return sb.toString() + } +} + +fun TextView.setText(@StringRes resid: Int, vararg formatArgs: Any) { + text = context.getString(resid, *formatArgs) +} + +fun JsonObject(vararg properties: Pair): JsonObject { + return JsonObject().apply { + for (property in properties) { + when (property.second) { + is JsonElement -> add(property.first, property.second as JsonElement?) + is String -> addProperty(property.first, property.second as String?) + is Char -> addProperty(property.first, property.second as Char?) + is Number -> addProperty(property.first, property.second as Number?) + is Boolean -> addProperty(property.first, property.second as Boolean?) + } + } + } +} + +fun JsonArray(vararg properties: Any?): JsonArray { + return JsonArray().apply { + for (property in properties) { + when (property) { + is JsonElement -> add(property as JsonElement?) + is String -> add(property as String?) + is Char -> add(property as Char?) + is Number -> add(property as Number?) + is Boolean -> add(property as Boolean?) + } + } + } +} + +fun Bundle(vararg properties: Pair): Bundle { + return Bundle().apply { + for (property in properties) { + when (property.second) { + is String -> putString(property.first, property.second as String?) + is Char -> putChar(property.first, property.second as Char) + is Int -> putInt(property.first, property.second as Int) + is Long -> putLong(property.first, property.second as Long) + is Float -> putFloat(property.first, property.second as Float) + is Short -> putShort(property.first, property.second as Short) + is Double -> putDouble(property.first, property.second as Double) + is Boolean -> putBoolean(property.first, property.second as Boolean) + } + } + } +} +fun Intent(action: String? = null, vararg properties: Pair): Intent { + return Intent(action).putExtras(Bundle(*properties)) +} +fun Intent(packageContext: Context, cls: Class<*>, vararg properties: Pair): Intent { + return Intent(packageContext, cls).putExtras(Bundle(*properties)) +} + +fun Bundle.toJsonObject(): JsonObject { + val json = JsonObject() + keySet()?.forEach { key -> + get(key)?.let { + when (it) { + is String -> json.addProperty(key, it) + is Char -> json.addProperty(key, it) + is Int -> json.addProperty(key, it) + is Long -> json.addProperty(key, it) + is Float -> json.addProperty(key, it) + is Short -> json.addProperty(key, it) + is Double -> json.addProperty(key, it) + is Boolean -> json.addProperty(key, it) + is Bundle -> json.add(key, it.toJsonObject()) + else -> json.addProperty(key, it.toString()) + } + } + } + return json +} +fun Intent.toJsonObject() = extras?.toJsonObject() + +fun JsonArray?.isNullOrEmpty(): Boolean = (this?.size() ?: 0) == 0 +fun JsonArray.isEmpty(): Boolean = this.size() == 0 +operator fun JsonArray.plusAssign(o: JsonElement) = this.add(o) +operator fun JsonArray.plusAssign(o: String) = this.add(o) +operator fun JsonArray.plusAssign(o: Char) = this.add(o) +operator fun JsonArray.plusAssign(o: Number) = this.add(o) +operator fun JsonArray.plusAssign(o: Boolean) = this.add(o) + +@Suppress("UNCHECKED_CAST") +inline fun T.onClick(crossinline onClickListener: (v: T) -> Unit) { + setOnClickListener { v: View -> + onClickListener(v as T) + } +} + +@Suppress("UNCHECKED_CAST") +inline fun T.onChange(crossinline onChangeListener: (v: T, isChecked: Boolean) -> Unit) { + setOnCheckedChangeListener { buttonView, isChecked -> + onChangeListener(buttonView as T, isChecked) + } +} + +fun LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer) { + observe(lifecycleOwner, object : Observer { + override fun onChanged(t: T?) { + observer.onChanged(t) + removeObserver(this) + } + }) +} + +/** + * 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 +} + +fun CoroutineScope.startCoroutineTimer(delayMillis: Long = 0, repeatMillis: Long = 0, action: () -> Unit) = launch { + delay(delayMillis) + if (repeatMillis > 0) { + while (true) { + action() + delay(repeatMillis) + } + } else { + action() + } +} + +operator fun Time?.compareTo(other: Time?): Int { + if (this == null && other == null) + return 0 + if (this == null) + return -1 + if (other == null) + return 1 + return this.compareTo(other) +} + +operator fun StringBuilder.plusAssign(str: String?) { + this.append(str) +} + +fun Context.timeTill(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String { + val parts = mutableListOf>() + + val hours = time / 3600 + val minutes = (time - hours*3600) / 60 + val seconds = time - minutes*60 - hours*3600 + + if (!countInSeconds) { + var prefixAdded = false + if (hours > 0) { + if (!prefixAdded) parts += R.plurals.time_till_text to hours + prefixAdded = true + parts += R.plurals.time_till_hours to hours + } + if (minutes > 0) { + if (!prefixAdded) parts += R.plurals.time_till_text to minutes + prefixAdded = true + parts += R.plurals.time_till_minutes to minutes + } + if (hours == 0 && minutes < 10) { + if (!prefixAdded) parts += R.plurals.time_till_text to seconds + prefixAdded = true + parts += R.plurals.time_till_seconds to seconds + } + } else { + parts += R.plurals.time_till_text to time + parts += R.plurals.time_till_seconds to time + } + + return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) } +} + +fun Context.timeLeft(time: Int, delimiter: String = " ", countInSeconds: Boolean = false): String { + val parts = mutableListOf>() + + val hours = time / 3600 + val minutes = (time - hours*3600) / 60 + val seconds = time - minutes*60 - hours*3600 + + if (!countInSeconds) { + var prefixAdded = false + if (hours > 0) { + if (!prefixAdded) parts += R.plurals.time_left_text to hours + prefixAdded = true + parts += R.plurals.time_left_hours to hours + } + if (minutes > 0) { + if (!prefixAdded) parts += R.plurals.time_left_text to minutes + prefixAdded = true + parts += R.plurals.time_left_minutes to minutes + } + if (hours == 0 && minutes < 10) { + if (!prefixAdded) parts += R.plurals.time_left_text to seconds + prefixAdded = true + parts += R.plurals.time_left_seconds to seconds + } + } else { + parts += R.plurals.time_left_text to time + parts += R.plurals.time_left_seconds to time + } + + return parts.joinToString(delimiter) { resources.getQuantityString(it.first, it.second, it.second) } +} + +inline fun Any?.instanceOfOrNull(): T? { + return when (this) { + is T -> this + else -> null + } +} + +fun Drawable.setTintColor(color: Int): Drawable { + colorFilter = PorterDuffColorFilter( + color, + PorterDuff.Mode.SRC_ATOP + ) + return this +} + +inline fun List.ifNotEmpty(block: (List) -> Unit) { + if (!isEmpty()) + block(this) +} + +val String.firstLettersName: String + get() { + var nameShort = "" + this.split(" ").forEach { + if (it.isBlank()) + return@forEach + nameShort += it[0].toLowerCase() + } + return nameShort + } + +val Throwable.stackTraceString: String + get() { + val sw = StringWriter() + printStackTrace(PrintWriter(sw)) + return sw.toString() + } + +inline fun LongSparseArray.filter(predicate: (T) -> Boolean): List { + val destination = ArrayList() + this.forEach { _, element -> if (predicate(element)) destination.add(element) } + return destination +} + +fun CharSequence.replace(oldValue: String, newValue: CharSequence, ignoreCase: Boolean = false): CharSequence = + splitToSequence(oldValue, ignoreCase = ignoreCase).toList().concat(newValue) + +fun Int.toColorStateList(): ColorStateList { + val states = arrayOf( + intArrayOf( android.R.attr.state_enabled ), + intArrayOf(-android.R.attr.state_enabled ), + intArrayOf(-android.R.attr.state_checked ), + intArrayOf( android.R.attr.state_pressed ) + ) + + val colors = intArrayOf( + this, + this, + this, + this + ) + + return ColorStateList(states, colors) +} + +fun SpannableStringBuilder.appendText(text: CharSequence): SpannableStringBuilder { + append(text) + return this +} +fun SpannableStringBuilder.appendSpan(text: CharSequence, what: Any, flags: Int): SpannableStringBuilder { + val start: Int = length + append(text) + setSpan(what, start, length, flags) + return this +} + +fun joinNotNullStrings(delimiter: String = "", vararg parts: String?): String { + var first = true + val sb = StringBuilder() + for (part in parts) { + if (part == null) + continue + if (!first) + sb += delimiter + first = false + sb += part + } + return sb.toString() +} + +fun String.notEmptyOrNull(): String? { + return if (isEmpty()) + null + else + this +} + +fun String.base64Encode(): String { + return encodeToString(toByteArray(), NO_WRAP) +} +fun ByteArray.base64Encode(): String { + return encodeToString(this, NO_WRAP) +} +fun String.base64Decode(): ByteArray { + return Base64.decode(this, Base64.DEFAULT) +} +fun String.base64DecodeToString(): String { + return Base64.decode(this, Base64.DEFAULT).toString(Charset.defaultCharset()) +} + +fun CheckBox.trigger() { isChecked = !isChecked } + +fun Context.plural(@PluralsRes resId: Int, value: Int): String = resources.getQuantityString(resId, value, value) + +fun Context.getNotificationTitle(type: Int): String { + return getString(when (type) { + Notification.TYPE_UPDATE -> R.string.notification_type_update + Notification.TYPE_ERROR -> R.string.notification_type_error + Notification.TYPE_TIMETABLE_CHANGED -> R.string.notification_type_timetable_change + Notification.TYPE_TIMETABLE_LESSON_CHANGE -> R.string.notification_type_timetable_lesson_change + Notification.TYPE_NEW_GRADE -> R.string.notification_type_new_grade + Notification.TYPE_NEW_EVENT -> R.string.notification_type_new_event + Notification.TYPE_NEW_HOMEWORK -> R.string.notification_type_new_homework + Notification.TYPE_NEW_SHARED_EVENT -> R.string.notification_type_new_shared_event + Notification.TYPE_NEW_SHARED_HOMEWORK -> R.string.notification_type_new_shared_homework + Notification.TYPE_REMOVED_SHARED_EVENT -> R.string.notification_type_removed_shared_event + Notification.TYPE_NEW_MESSAGE -> R.string.notification_type_new_message + Notification.TYPE_NEW_NOTICE -> R.string.notification_type_notice + Notification.TYPE_NEW_ATTENDANCE -> R.string.notification_type_attendance + Notification.TYPE_SERVER_MESSAGE -> R.string.notification_type_server_message + Notification.TYPE_LUCKY_NUMBER -> R.string.notification_type_lucky_number + Notification.TYPE_FEEDBACK_MESSAGE -> R.string.notification_type_feedback_message + Notification.TYPE_NEW_ANNOUNCEMENT -> R.string.notification_type_new_announcement + Notification.TYPE_AUTO_ARCHIVING -> R.string.notification_type_auto_archiving + Notification.TYPE_TEACHER_ABSENCE -> R.string.notification_type_new_teacher_absence + Notification.TYPE_GENERAL -> R.string.notification_type_general + else -> R.string.notification_type_general + }) +} + +fun Cursor?.getString(columnName: String) = this?.getStringOrNull(getColumnIndex(columnName)) +fun Cursor?.getInt(columnName: String) = this?.getIntOrNull(getColumnIndex(columnName)) +fun Cursor?.getLong(columnName: String) = this?.getLongOrNull(getColumnIndex(columnName)) + +fun OkHttpClient.Builder.installHttpsSupport(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { + try { + try { + ProviderInstaller.installIfNeeded(context) + } catch (e: Exception) { + Log.e("OkHttpTLSCompat", "Play Services not found or outdated") + + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(null as KeyStore?) + + val x509TrustManager = trustManagerFactory.trustManagers.singleOrNull { it is X509TrustManager } as X509TrustManager? + ?: return + + val sc = SSLContext.getInstance("TLSv1.2") + sc.init(null, null, null) + sslSocketFactory(TLSSocketFactory(sc.socketFactory), x509TrustManager) + val cs: ConnectionSpec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(TlsVersion.TLS_1_0) + .tlsVersions(TlsVersion.TLS_1_1) + .tlsVersions(TlsVersion.TLS_1_2) + .build() + val specs: MutableList = ArrayList() + specs.add(cs) + specs.add(ConnectionSpec.COMPATIBLE_TLS) + specs.add(ConnectionSpec.CLEARTEXT) + connectionSpecs(specs) + } + } catch (exc: Exception) { + Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc) + } + } +} + +fun CharSequence.containsAll(list: List, ignoreCase: Boolean = false): Boolean { + for (i in list) { + if (!contains(i, ignoreCase)) + return false + } + return true +} + +inline fun RadioButton.setOnSelectedListener(crossinline listener: (buttonView: CompoundButton) -> Unit) + = setOnCheckedChangeListener { buttonView, isChecked -> if (isChecked) listener(buttonView) } + +fun Response.toErrorCode() = when (this.code()) { + 400 -> ERROR_REQUEST_HTTP_400 + 401 -> ERROR_REQUEST_HTTP_401 + 403 -> ERROR_REQUEST_HTTP_403 + 404 -> ERROR_REQUEST_HTTP_404 + 405 -> ERROR_REQUEST_HTTP_405 + 410 -> ERROR_REQUEST_HTTP_410 + 424 -> ERROR_REQUEST_HTTP_424 + 500 -> ERROR_REQUEST_HTTP_500 + 503 -> ERROR_REQUEST_HTTP_503 + else -> null +} +fun Throwable.toErrorCode() = when (this) { + is UnknownHostException -> ERROR_REQUEST_FAILURE_HOSTNAME_NOT_FOUND + is SSLException -> ERROR_REQUEST_FAILURE_SSL_ERROR + is SocketTimeoutException -> ERROR_REQUEST_FAILURE_TIMEOUT + is InterruptedIOException, is ConnectException -> ERROR_REQUEST_FAILURE_NO_INTERNET + is SzkolnyApiException -> this.error?.toErrorCode() + else -> null +} +private fun ApiResponse.Error.toErrorCode() = when (this.code) { + else -> ERROR_API_EXCEPTION +} +fun Throwable.toApiError(tag: String) = ApiError.fromThrowable(tag, this) + +inline fun ifNotNull(a: A?, b: B?, code: (A, B) -> R): R? { + if (a != null && b != null) { + return code(a, b) + } + return null +} + +@kotlin.jvm.JvmName("averageOrNullOfInt") +fun Iterable.averageOrNull() = this.average().let { if (it.isNaN()) null else it } +@kotlin.jvm.JvmName("averageOrNullOfFloat") +fun Iterable.averageOrNull() = this.average().let { if (it.isNaN()) null else it } + +fun String.copyToClipboard(context: Context) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = ClipData.newPlainText("Tekst", this) + clipboard.primaryClip = clipData +} + +fun TextView.getTextPosition(range: IntRange): Rect { + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + + // Initialize global value + var parentTextViewRect = Rect() + + // Initialize values for the computing of clickedText position + //val completeText = parentTextView.text as SpannableString + val textViewLayout = this.layout + + val startOffsetOfClickedText = range.first//completeText.getSpanStart(clickedText) + val endOffsetOfClickedText = range.last//completeText.getSpanEnd(clickedText) + var startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(startOffsetOfClickedText) + var endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal(endOffsetOfClickedText) + + // Get the rectangle of the clicked text + val currentLineStartOffset = textViewLayout.getLineForOffset(startOffsetOfClickedText) + val currentLineEndOffset = textViewLayout.getLineForOffset(endOffsetOfClickedText) + val keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset + textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect) + + // Update the rectangle position to his real position on screen + val parentTextViewLocation = intArrayOf(0, 0) + this.getLocationOnScreen(parentTextViewLocation) + + val parentTextViewTopAndBottomOffset = (parentTextViewLocation[1] - this.scrollY + this.compoundPaddingTop) + parentTextViewRect.top += parentTextViewTopAndBottomOffset + parentTextViewRect.bottom += parentTextViewTopAndBottomOffset + + // In the case of multi line text, we have to choose what rectangle take + if (keywordIsInMultiLine) { + val screenHeight = windowManager.defaultDisplay.height + val dyTop = parentTextViewRect.top + val dyBottom = screenHeight - parentTextViewRect.bottom + val onTop = dyTop > dyBottom + + if (onTop) { + endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset); + } else { + parentTextViewRect = Rect() + textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect); + parentTextViewRect.top += parentTextViewTopAndBottomOffset; + parentTextViewRect.bottom += parentTextViewTopAndBottomOffset; + startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset); + } + } + + parentTextViewRect.left += ( + parentTextViewLocation[0] + + startXCoordinatesOfClickedText + + this.compoundPaddingLeft - + this.scrollX + ).toInt() + parentTextViewRect.right = ( + parentTextViewRect.left + + endXCoordinatesOfClickedText - + startXCoordinatesOfClickedText + ).toInt() + + return parentTextViewRect +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt index 1316a91a..8f53b625 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/MainActivity.kt @@ -1,70 +1,102 @@ package pl.szczodrzynski.edziennik -import android.app.Activity import android.app.ActivityManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable -import android.os.* -import android.util.Log +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.provider.Settings import android.view.Gravity import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Observer -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial -import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont -import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem -import pl.szczodrzynski.edziennik.datamodels.Metadata.* -import pl.szczodrzynski.edziennik.utils.Themes -import pl.szczodrzynski.navlib.NavView -import pl.szczodrzynski.navlib.SystemBarsUtil -import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT -import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet -import pl.szczodrzynski.navlib.drawer.NavDrawer -import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem -import pl.szczodrzynski.navlib.drawer.items.withAppTitle -import pl.szczodrzynski.navlib.getColorFromAttr import androidx.appcompat.widget.PopupMenu +import androidx.core.graphics.ColorUtils +import androidx.lifecycle.Observer import androidx.navigation.NavOptions +import androidx.recyclerview.widget.RecyclerView import com.danimahardhika.cafebar.CafeBar +import com.google.android.material.dialog.MaterialAlertDialogBuilder 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 com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont import com.mikepenz.materialdrawer.model.DividerDrawerItem import com.mikepenz.materialdrawer.model.ProfileDrawerItem -import com.mikepenz.materialdrawer.model.interfaces.IProfile +import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem +import com.mikepenz.materialdrawer.model.interfaces.* +import com.mikepenz.materialdrawer.model.utils.withIsHiddenInMiniDrawer +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import pl.droidsonroids.gif.GifDrawable -import pl.szczodrzynski.edziennik.App.APP_URL -import pl.szczodrzynski.edziennik.api.AppError -import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.* -import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.* +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Metadata.* import pl.szczodrzynski.edziennik.databinding.ActivitySzkolnyBinding -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.datamodels.Profile -import pl.szczodrzynski.edziennik.datamodels.ProfileFull -import pl.szczodrzynski.edziennik.dialogs.ChangelogDialog -import pl.szczodrzynski.edziennik.fragments.* -import pl.szczodrzynski.edziennik.login.LoginActivity -import pl.szczodrzynski.edziennik.messages.MessagesDetailsFragment -import pl.szczodrzynski.edziennik.messages.MessagesFragment -import pl.szczodrzynski.edziennik.models.NavTarget -import pl.szczodrzynski.edziennik.network.ServerRequest -import pl.szczodrzynski.edziennik.sync.SyncJob +import pl.szczodrzynski.edziennik.sync.AppManagerDetectedEvent +import pl.szczodrzynski.edziennik.sync.SyncWorker +import pl.szczodrzynski.edziennik.sync.UpdateWorker +import pl.szczodrzynski.edziennik.ui.dialogs.ServerMessageDialog +import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +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.announcements.AnnouncementsFragment +import pl.szczodrzynski.edziennik.ui.modules.attendance.AttendanceFragment +import pl.szczodrzynski.edziennik.ui.modules.base.DebugFragment +import pl.szczodrzynski.edziennik.ui.modules.base.MainSnackbar +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.HelpFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.GradesFragment +import pl.szczodrzynski.edziennik.ui.modules.grades.editor.GradesEditorFragment +import pl.szczodrzynski.edziennik.ui.modules.home.HomeFragment +import pl.szczodrzynski.edziennik.ui.modules.homework.HomeworkFragment +import pl.szczodrzynski.edziennik.ui.modules.login.LoginActivity +import pl.szczodrzynski.edziennik.ui.modules.messages.MessageFragment +import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesComposeFragment +import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesFragment +import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesListFragment +import pl.szczodrzynski.edziennik.ui.modules.notifications.NotificationsFragment +import pl.szczodrzynski.edziennik.ui.modules.settings.ProfileManagerFragment +import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsNewFragment +import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.ui.modules.webpush.WebPushFragment import pl.szczodrzynski.edziennik.utils.SwipeRefreshLayoutNoTouch +import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.Utils.dpToPx +import pl.szczodrzynski.edziennik.utils.appManagerIntentList +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.NavTarget +import pl.szczodrzynski.navlib.* +import pl.szczodrzynski.navlib.SystemBarsUtil.Companion.COLOR_HALF_TRANSPARENT +import pl.szczodrzynski.navlib.bottomsheet.NavBottomSheet import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import pl.szczodrzynski.navlib.drawer.NavDrawer +import pl.szczodrzynski.navlib.drawer.items.DrawerPrimaryItem import java.io.File import java.io.IOException import java.util.* +import kotlin.coroutines.CoroutineContext +import kotlin.math.roundToInt - -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), CoroutineScope { companion object { var useOldMessages = false @@ -77,14 +109,15 @@ class MainActivity : AppCompatActivity() { const val DRAWER_PROFILE_SYNC_ALL = 201 const val DRAWER_PROFILE_EXPORT_DATA = 202 const val DRAWER_PROFILE_MANAGE = 203 + const val DRAWER_PROFILE_MARK_ALL_AS_READ = 204 const val DRAWER_ITEM_HOME = 1 const val DRAWER_ITEM_TIMETABLE = 11 const val DRAWER_ITEM_AGENDA = 12 const val DRAWER_ITEM_GRADES = 13 const val DRAWER_ITEM_MESSAGES = 17 - const val DRAWER_ITEM_HOMEWORKS = 14 - const val DRAWER_ITEM_NOTICES = 15 - const val DRAWER_ITEM_ATTENDANCES = 16 + const val DRAWER_ITEM_HOMEWORK = 14 + const val DRAWER_ITEM_BEHAVIOUR = 15 + const val DRAWER_ITEM_ATTENDANCE = 16 const val DRAWER_ITEM_ANNOUNCEMENTS = 18 const val DRAWER_ITEM_NOTIFICATIONS = 20 const val DRAWER_ITEM_SETTINGS = 101 @@ -94,6 +127,8 @@ class MainActivity : AppCompatActivity() { const val TARGET_HELP = 502 const val TARGET_FEEDBACK = 120 const val TARGET_MESSAGES_DETAILS = 503 + const val TARGET_MESSAGES_COMPOSE = 504 + const val TARGET_WEB_PUSH = 140 const val HOME_ID = DRAWER_ITEM_HOME @@ -103,61 +138,61 @@ class MainActivity : AppCompatActivity() { // home item list += NavTarget(DRAWER_ITEM_HOME, R.string.menu_home_page, HomeFragment::class) .withTitle(R.string.app_name) - .withIcon(CommunityMaterial.Icon2.cmd_home) + .withIcon(CommunityMaterial.Icon2.cmd_home_outline) .isInDrawer(true) .isStatic(true) .withPopToHome(false) - list += NavTarget(DRAWER_ITEM_TIMETABLE, R.string.menu_timetable, RegisterTimetableFragment::class) + list += NavTarget(DRAWER_ITEM_TIMETABLE, R.string.menu_timetable, TimetableFragment::class) .withIcon(CommunityMaterial.Icon2.cmd_timetable) .withBadgeTypeId(TYPE_LESSON_CHANGE) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, RegisterAgendaDefaultFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_calendar) + list += NavTarget(DRAWER_ITEM_AGENDA, R.string.menu_agenda, AgendaFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_calendar_outline) .withBadgeTypeId(TYPE_EVENT) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, RegisterGradesFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box) + list += NavTarget(DRAWER_ITEM_GRADES, R.string.menu_grades, GradesFragment::class) + .withIcon(CommunityMaterial.Icon2.cmd_numeric_5_box_outline) .withBadgeTypeId(TYPE_GRADE) .isInDrawer(true) list += NavTarget(DRAWER_ITEM_MESSAGES, R.string.menu_messages, MessagesFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_email) + .withIcon(CommunityMaterial.Icon.cmd_email_outline) .withBadgeTypeId(TYPE_MESSAGE) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_HOMEWORKS, R.string.menu_homework, RegisterHomeworksFragment::class) - .withIcon(SzkolnyFont.Icon.szf_file_document_edit) + list += NavTarget(DRAWER_ITEM_HOMEWORK, R.string.menu_homework, HomeworkFragment::class) + .withIcon(SzkolnyFont.Icon.szf_notebook_outline) .withBadgeTypeId(TYPE_HOMEWORK) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_NOTICES, R.string.menu_notices, RegisterNoticesFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_message_alert) + list += NavTarget(DRAWER_ITEM_BEHAVIOUR, R.string.menu_notices, BehaviourFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_emoticon_outline) .withBadgeTypeId(TYPE_NOTICE) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_ATTENDANCES, R.string.menu_attendances, RegisterAttendancesFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_calendar_remove) + list += NavTarget(DRAWER_ITEM_ATTENDANCE, R.string.menu_attendance, AttendanceFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_calendar_remove_outline) .withBadgeTypeId(TYPE_ATTENDANCE) .isInDrawer(true) - list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, RegisterAnnouncementsFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_bulletin_board) + list += NavTarget(DRAWER_ITEM_ANNOUNCEMENTS, R.string.menu_announcements, AnnouncementsFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_bullhorn_outline) .withBadgeTypeId(TYPE_ANNOUNCEMENT) .isInDrawer(true) // static drawer items - list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, RegisterNotificationsFragment::class) - .withIcon(CommunityMaterial.Icon.cmd_bell_ring) + list += NavTarget(DRAWER_ITEM_NOTIFICATIONS, R.string.menu_notifications, NotificationsFragment::class) + .withIcon(CommunityMaterial.Icon.cmd_bell_ring_outline) .isInDrawer(true) .isStatic(true) .isBelowSeparator(true) list += NavTarget(DRAWER_ITEM_SETTINGS, R.string.menu_settings, SettingsNewFragment::class) - .withIcon(CommunityMaterial.Icon2.cmd_settings) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) .isInDrawer(true) .isStatic(true) .isBelowSeparator(true) @@ -175,8 +210,12 @@ class MainActivity : AppCompatActivity() { .withDescription(R.string.drawer_manage_profiles_desc) .isInProfileList(false) + list += NavTarget(DRAWER_PROFILE_MARK_ALL_AS_READ, R.string.menu_mark_everything_as_read, null) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) + .isInProfileList(true) + list += NavTarget(DRAWER_PROFILE_SYNC_ALL, R.string.menu_sync_all, null) - .withIcon(CommunityMaterial.Icon2.cmd_sync) + .withIcon(CommunityMaterial.Icon.cmd_download_outline) .isInProfileList(true) @@ -184,17 +223,25 @@ class MainActivity : AppCompatActivity() { list += NavTarget(TARGET_GRADES_EDITOR, R.string.menu_grades_editor, GradesEditorFragment::class) list += NavTarget(TARGET_HELP, R.string.menu_help, HelpFragment::class) list += NavTarget(TARGET_FEEDBACK, R.string.menu_feedback, FeedbackFragment::class) - list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessagesDetailsFragment::class) + list += NavTarget(TARGET_MESSAGES_DETAILS, R.string.menu_message, MessageFragment::class).withPopTo(DRAWER_ITEM_MESSAGES) + list += NavTarget(TARGET_MESSAGES_COMPOSE, R.string.menu_message_compose, MessagesComposeFragment::class) + list += NavTarget(TARGET_WEB_PUSH, R.string.menu_web_push, WebPushFragment::class) list += NavTarget(DRAWER_ITEM_DEBUG, R.string.menu_debug, DebugFragment::class) list } } + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + val b: ActivitySzkolnyBinding by lazy { ActivitySzkolnyBinding.inflate(layoutInflater) } val navView: NavView by lazy { b.navView } val drawer: NavDrawer by lazy { navView.drawer } val bottomSheet: NavBottomSheet by lazy { navView.bottomSheet } + val mainSnackbar: MainSnackbar by lazy { MainSnackbar(this) } + val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) } val swipeRefreshLayout: SwipeRefreshLayoutNoTouch by lazy { b.swipeRefreshLayout } @@ -204,10 +251,11 @@ class MainActivity : AppCompatActivity() { private val fragmentManager by lazy { supportFragmentManager } private lateinit var navTarget: NavTarget - private val navTargetId + private var navArguments: Bundle? = null + val navTargetId get() = navTarget.id - private val navBackStack = mutableListOf() + private val navBackStack = mutableListOf>() private var navLoading = true /* ____ _____ _ @@ -219,18 +267,34 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + d(TAG, "Activity created") + setTheme(Themes.appTheme) + app.config.ui.language?.let { + setLanguage(it) + } + + if (App.profileId == 0) { + onProfileListEmptyEvent(ProfileListEmptyEvent()) + return + } + + d(TAG, "Profile is valid, inflating views") + setContentView(b.root) + mainSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) + errorSnackbar.setCoordinator(b.navView.coordinator, b.navView.bottomBar) + navLoading = true b.navView.apply { drawer.init(this@MainActivity) SystemBarsUtil(this@MainActivity).run { - paddingByKeyboard = b.navView - appFullscreen = true + //paddingByKeyboard = b.navView + appFullscreen = false statusBarColor = getColorFromAttr(context, android.R.attr.colorBackground) statusBarDarker = false statusBarFallbackLight = COLOR_HALF_TRANSPARENT @@ -239,6 +303,16 @@ class MainActivity : AppCompatActivity() { b.navView.configSystemBarsUtil(this) + // fix for setting status bar color to window color, outside of navlib + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.statusBarColor = statusBarColor + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ColorUtils.calculateLuminance(statusBarColor) > 0.6) { + window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + + // TODO fix navlib navbar detection, orientation change issues, status bar color setting if not fullscreen + commit() } @@ -252,21 +326,30 @@ class MainActivity : AppCompatActivity() { fabExtendable = true fabExtended = false fabGravity = Gravity.CENTER + if (Themes.isDark) { + setBackgroundColor(blendColors( + getColorFromAttr(context, R.attr.colorSurface), + getColorFromRes(R.color.colorSurface_4dp) + )) + elevation = dpToPx(4).toFloat() + } } bottomSheet.apply { removeAllItems() toggleGroupEnabled = false textInputEnabled = false + onCloseListener = { + if (!app.config.ui.bottomSheetOpened) + app.config.ui.bottomSheetOpened = true + } } drawer.apply { - setAccountHeaderBackground(app.appConfig.headerBackground) + setAccountHeaderBackground(app.config.ui.headerBackground) drawerProfileListEmptyListener = { - app.appConfig.loginFinished = false - app.saveConfig("loginFinished") - profileListEmptyListener() + onProfileListEmptyEvent(ProfileListEmptyEvent()) } drawerItemSelectedListener = { id, position, drawerItem -> loadTarget(id) @@ -277,7 +360,7 @@ class MainActivity : AppCompatActivity() { false } drawerProfileLongClickListener = { _, profile, _, view -> - if (profile is ProfileDrawerItem) { + if (view != null && profile is ProfileDrawerItem) { showProfileContextMenu(profile, view) true } @@ -289,40 +372,27 @@ class MainActivity : AppCompatActivity() { drawerProfileSettingClickListener = this@MainActivity.profileSettingClickListener miniDrawerVisibleLandscape = null - miniDrawerVisiblePortrait = app.appConfig.miniDrawerVisible + miniDrawerVisiblePortrait = app.config.ui.miniMenuVisible } } navTarget = navTargetList[0] - var profileListEmpty = drawer.profileListEmpty - if (savedInstanceState != null) { intent?.putExtras(savedInstanceState) savedInstanceState.clear() } - if (!profileListEmpty) { - handleIntent(intent?.extras) - } - app.db.profileDao().getAllFull().observe(this, Observer { profiles -> - // TODO fix weird -1 profiles ??? - profiles.removeAll { it.id < 0 } - drawer.setProfileList(profiles) - if (profileListEmpty) { - profileListEmpty = false - handleIntent(intent?.extras) - } - else if (app.profile != null) { - drawer.currentProfile = app.profile.id - } + app.db.profileDao().all.observe(this, Observer { profiles -> + drawer.setProfileList(profiles.filter { it.id >= 0 }.toMutableList()) + drawer.currentProfile = App.profileId }) - // if null, getAllFull will load a profile and update drawerItems - if (app.profile != null) - setDrawerItems() + setDrawerItems() - app.db.metadataDao().getUnreadCounts().observe(this, Observer { unreadCounters -> + handleIntent(intent?.extras) + + app.db.metadataDao().unreadCounts.observe(this, Observer { unreadCounters -> unreadCounters.map { it.type = it.thingType } @@ -331,60 +401,66 @@ class MainActivity : AppCompatActivity() { b.swipeRefreshLayout.isEnabled = true b.swipeRefreshLayout.setOnRefreshListener { this.syncCurrentFeature() } + b.swipeRefreshLayout.setColorSchemeResources( + R.color.md_blue_500, + R.color.md_amber_500, + R.color.md_green_500 + ) isStoragePermissionGranted() - SyncJob.schedule(app) + SyncWorker.scheduleNext(app) + UpdateWorker.scheduleNext(app) // APP BACKGROUND - if (app.appConfig.appBackground != null) { + if (app.config.ui.appBackground != null) { try { - var bg = app.appConfig.appBackground - val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg") - if (bgDir.exists()) { - val files = bgDir.listFiles() - val r = Random() - val i = r.nextInt(files.size) - bg = files[i].toString() - } - val linearLayout = b.root - if (bg.endsWith(".gif")) { - linearLayout.background = GifDrawable(bg) - } else { - linearLayout.background = BitmapDrawable.createFromPath(bg) + app.config.ui.appBackground?.let { + var bg = it + val bgDir = File(Environment.getExternalStoragePublicDirectory("Szkolny.eu"), "bg") + if (bgDir.exists()) { + val files = bgDir.listFiles() + val r = Random() + val i = r.nextInt(files.size) + bg = files[i].toString() + } + val linearLayout = b.root + if (bg.endsWith(".gif")) { + linearLayout.background = GifDrawable(bg) + } else { + linearLayout.background = BitmapDrawable.createFromPath(bg) + } } } catch (e: IOException) { e.printStackTrace() } } + // IT'S WINTER MY DUDES + val today = Date.getToday() + if ((today.month == 12 || today.month == 1) && app.config.ui.snowfall) { + b.rootFrame.addView(layoutInflater.inflate(R.layout.snowfall, b.rootFrame, false)) + } + // WHAT'S NEW DIALOG - if (app.appConfig.lastAppVersion != BuildConfig.VERSION_CODE) { - ServerRequest(app, app.requestScheme + APP_URL + "main.php?just_updated", "MainActivity/JU") - .run { e, result -> - Handler(Looper.getMainLooper()).post { - try { - ChangelogDialog().show(supportFragmentManager, "whats_new") - } catch (e2: Exception) { - e2.printStackTrace() - } - } - } - if (app.appConfig.lastAppVersion < 170) { + if (app.config.appVersion < BuildConfig.VERSION_CODE) { + // force an AppSync after update + app.config.sync.lastAppSync = 0L + ChangelogDialog(this) + if (app.config.appVersion < 170) { //Intent intent = new Intent(this, ChangelogIntroActivity.class); //startActivity(intent); } else { - app.appConfig.lastAppVersion = BuildConfig.VERSION_CODE - app.saveConfig("lastAppVersion") + app.config.appVersion = BuildConfig.VERSION_CODE } } // RATE SNACKBAR - if (app.appConfig.appRateSnackbarTime != 0L && app.appConfig.appRateSnackbarTime <= System.currentTimeMillis()) { + if (app.config.appRateSnackbarTime != 0L && app.config.appRateSnackbarTime <= System.currentTimeMillis()) { navView.coordinator.postDelayed({ CafeBar.builder(this) .content(R.string.rate_snackbar_text) - .icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this)))) + .icon(IconicsDrawable(this).icon(CommunityMaterial.Icon2.cmd_star_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.getPrimaryTextColor(this)))) .positiveText(R.string.rate_snackbar_positive) .positiveColor(-0xb350b0) .negativeText(R.string.rate_snackbar_negative) @@ -394,20 +470,17 @@ class MainActivity : AppCompatActivity() { .onPositive { cafeBar -> Utils.openGooglePlay(this) cafeBar.dismiss() - app.appConfig.appRateSnackbarTime = 0 - app.saveConfig("appRateSnackbarTime") + app.config.appRateSnackbarTime = 0 } .onNegative { cafeBar -> Toast.makeText(this, "Szkoda, opinie innych pomagają mi rozwijać aplikację.", Toast.LENGTH_LONG).show() cafeBar.dismiss() - app.appConfig.appRateSnackbarTime = 0 - app.saveConfig("appRateSnackbarTime") + app.config.appRateSnackbarTime = 0 } .onNeutral { cafeBar -> Toast.makeText(this, "OK", Toast.LENGTH_LONG).show() cafeBar.dismiss() - app.appConfig.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000 - app.saveConfig("appRateSnackbarTime") + app.config.appRateSnackbarTime = System.currentTimeMillis() + 7 * 24 * 60 * 60 * 1000 } .autoDismiss(false) .swipeToDismiss(true) @@ -421,43 +494,48 @@ class MainActivity : AppCompatActivity() { bottomSheet.appendItems( BottomSheetPrimaryItem(false) .withTitle(R.string.menu_sync) - .withIcon(CommunityMaterial.Icon2.cmd_sync) + .withIcon(CommunityMaterial.Icon.cmd_download_outline) .withOnClickListener(View.OnClickListener { 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), BottomSheetPrimaryItem(false) .withTitle(R.string.menu_settings) - .withIcon(CommunityMaterial.Icon2.cmd_settings) + .withIcon(CommunityMaterial.Icon2.cmd_settings_outline) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_SETTINGS) }), BottomSheetPrimaryItem(false) .withTitle(R.string.menu_feedback) - .withIcon(CommunityMaterial.Icon2.cmd_help_circle) + .withIcon(CommunityMaterial.Icon2.cmd_help_circle_outline) .withOnClickListener(View.OnClickListener { loadTarget(TARGET_FEEDBACK) }) ) if (App.devMode) { bottomSheet += BottomSheetPrimaryItem(false) .withTitle(R.string.menu_debug) - .withIcon(CommunityMaterial.Icon.cmd_android_debug_bridge) + .withIcon(CommunityMaterial.Icon.cmd_android_studio) .withOnClickListener(View.OnClickListener { loadTarget(DRAWER_ITEM_DEBUG) }) } } - var profileListEmptyListener = { - startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY) - } private var profileSettingClickListener = { id: Int, view: View? -> when (id) { DRAWER_PROFILE_ADD_NEW -> { - LoginActivity.privacyPolicyAccepted = true - // else it would try to navigateUp onBackPressed, which it can't do. There is no parent fragment - LoginActivity.firstCompleted = false - profileListEmptyListener() + startActivityForResult(Intent(this, LoginActivity::class.java), REQUEST_LOGIN_ACTIVITY) } DRAWER_PROFILE_SYNC_ALL -> { - SyncJob.run(app) + EdziennikTask.sync().enqueue(this) } + DRAWER_PROFILE_MARK_ALL_AS_READ -> { launch { + withContext(Dispatchers.Default) { + app.db.profileDao().allNow.forEach { profile -> + if (profile.loginStoreType != LoginStore.LOGIN_TYPE_LIBRUS) + app.db.metadataDao().setAllSeenExceptMessagesAndAnnouncements(profile.id, true) + else + app.db.metadataDao().setAllSeenExceptMessages(profile.id, true) + } + } + Toast.makeText(this@MainActivity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() + }} else -> { loadTarget(id) } @@ -476,60 +554,122 @@ class MainActivity : AppCompatActivity() { fun syncCurrentFeature() { swipeRefreshLayout.isRefreshing = true Toast.makeText(this, fragmentToSyncName(navTargetId), Toast.LENGTH_SHORT).show() - val callback = object : SyncCallback { - override fun onLoginFirst(profileList: List, loginStore: LoginStore) { - - } - - override fun onSuccess(activityContext: Context, profileFull: ProfileFull) { - swipeRefreshLayout.isRefreshing = false - } - - override fun onError(activityContext: Context, error: AppError) { - swipeRefreshLayout.isRefreshing = false - app.apiEdziennik.guiShowErrorSnackbar(this@MainActivity, error) - } - - override fun onProgress(progressStep: Int) { - - } - - override fun onActionStarted(stringResId: Int) { - - } + val fragmentParam = when (navTargetId) { + DRAWER_ITEM_MESSAGES -> MessagesFragment.pageSelection + else -> 0 } - val feature = fragmentToFeature(navTargetId) - if (feature == FEATURE_ALL) { - swipeRefreshLayout.isRefreshing = false - app.apiEdziennik.guiSync(app, this, App.profileId, R.string.sync_dialog_title, R.string.sync_dialog_text, R.string.sync_done) - } else { - app.apiEdziennik.guiSyncSilent(app, this, App.profileId, callback, feature) + val arguments = when (navTargetId) { + DRAWER_ITEM_TIMETABLE -> JsonObject("weekStart" to TimetableFragment.pageSelection?.weekStart?.stringY_m_d) + else -> null + } + EdziennikTask.syncProfile( + App.profileId, + listOf(navTargetId to fragmentParam), + arguments = arguments + ).enqueue(this) + } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskStartedEvent(event: ApiTaskStartedEvent) { + swipeRefreshLayout.isRefreshing = true + if (event.profileId == App.profileId) { + navView.toolbar.apply { + subtitleFormat = null + subtitleFormatWithUnread = null + subtitle = getString(R.string.toolbar_subtitle_syncing) + } } } - 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_HOMEWORKS -> FEATURE_HOMEWORKS - DRAWER_ITEM_NOTICES -> FEATURE_NOTICES - DRAWER_ITEM_ATTENDANCES -> FEATURE_ATTENDANCES - DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) { - 1 -> FEATURE_MESSAGES_OUTBOX - else -> FEATURE_MESSAGES_INBOX + @Subscribe(threadMode = ThreadMode.MAIN) + fun onProfileListEmptyEvent(event: ProfileListEmptyEvent) { + d(TAG, "Profile list is empty. Launch LoginActivity.") + app.config.loginFinished = false + startActivity(Intent(this, LoginActivity::class.java)) + finish() + } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskProgressEvent(event: ApiTaskProgressEvent) { + if (event.profileId == App.profileId) { + navView.toolbar.apply { + subtitleFormat = null + subtitleFormatWithUnread = null + subtitle = if (event.progress < 0f) + event.progressText ?: "" + else + getString(R.string.toolbar_subtitle_syncing_format, event.progress.roundToInt(), event.progressText ?: "") + } - DRAWER_ITEM_ANNOUNCEMENTS -> FEATURE_ANNOUNCEMENTS - else -> FEATURE_ALL } } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { + EventBus.getDefault().removeStickyEvent(event) + if (event.profileId == App.profileId) { + navView.toolbar.apply { + subtitleFormat = R.string.toolbar_subtitle + subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread + subtitle = "Gotowe" + } + } + } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { + EventBus.getDefault().removeStickyEvent(event) + swipeRefreshLayout.isRefreshing = false + } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { + EventBus.getDefault().removeStickyEvent(event) + navView.toolbar.apply { + subtitleFormat = R.string.toolbar_subtitle + subtitleFormatWithUnread = R.plurals.toolbar_subtitle_with_unread + subtitle = "Gotowe" + } + mainSnackbar.dismiss() + errorSnackbar.addError(event.error).show() + } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onAppManagerDetectedEvent(event: AppManagerDetectedEvent) { + EventBus.getDefault().removeStickyEvent(event) + if (app.config.sync.dontShowAppManagerDialog) + return + MaterialAlertDialogBuilder(this) + .setTitle(R.string.app_manager_dialog_title) + .setMessage(R.string.app_manager_dialog_text) + .setPositiveButton(R.string.ok) { dialog, which -> + try { + for (intent in appManagerIntentList) { + if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) { + startActivity(intent) + } + } + } catch (e: Exception) { + try { + startActivity(Intent(Settings.ACTION_SETTINGS)) + } catch (e: Exception) { + e.printStackTrace() + Toast.makeText(this, R.string.app_manager_open_failed, Toast.LENGTH_SHORT).show() + } + } + } + .setNeutralButton(R.string.dont_ask_again) { dialog, which -> + app.config.sync.dontShowAppManagerDialog = true + } + .setCancelable(false) + .show() + } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onUserActionRequiredEvent(event: UserActionRequiredEvent) { + app.userActionManager.execute(this, event.profileId, event.type) + } + private fun fragmentToSyncName(currentFragment: Int): Int { return when (currentFragment) { DRAWER_ITEM_TIMETABLE -> R.string.sync_feature_timetable DRAWER_ITEM_AGENDA -> R.string.sync_feature_agenda DRAWER_ITEM_GRADES -> R.string.sync_feature_grades - DRAWER_ITEM_HOMEWORKS -> R.string.sync_feature_homeworks - DRAWER_ITEM_NOTICES -> R.string.sync_feature_notices - DRAWER_ITEM_ATTENDANCES -> R.string.sync_feature_attendances + DRAWER_ITEM_HOMEWORK -> R.string.sync_feature_homework + DRAWER_ITEM_BEHAVIOUR -> R.string.sync_feature_notices + DRAWER_ITEM_ATTENDANCE -> R.string.sync_feature_attendance DRAWER_ITEM_MESSAGES -> when (MessagesFragment.pageSelection) { 1 -> R.string.sync_feature_messages_outbox else -> R.string.sync_feature_messages_inbox @@ -552,32 +692,70 @@ class MainActivity : AppCompatActivity() { } private fun handleIntent(extras: Bundle?) { - Log.d(TAG, "handleIntent() {") + d(TAG, "handleIntent() {") extras?.keySet()?.forEach { key -> - Log.d(TAG, " \"$key\": "+extras.get(key)) + d(TAG, " \"$key\": "+extras.get(key)) + } + d(TAG, "}") + + var intentProfileId = -1 + var intentTargetId = -1 + + if (extras?.containsKey("action") == true) { + val handled = when (extras.getString("action")) { + "serverMessage" -> { + ServerMessageDialog( + this, + extras.getString("serverMessageTitle") ?: getString(R.string.app_name), + extras.getString("serverMessageText") ?: "" + ) + true + } + "feedbackMessage" -> { + intentTargetId = TARGET_FEEDBACK + false + } + "userActionRequired" -> { + app.userActionManager.execute( + this, + extras.getInt("profileId"), + extras.getInt("type") + ) + true + } + "createManualEvent" -> { + val date = extras.getString("eventDate")?.let { Date.fromY_m_d(it) } ?: Date.getToday() + EventManualDialog( + this, + App.profileId, + defaultDate = date + ) + true + } + else -> false + } + if (handled && !navLoading) { + return + } } - Log.d(TAG, "}") if (extras?.containsKey("reloadProfileId") == true) { val reloadProfileId = extras.getInt("reloadProfileId", -1) extras.remove("reloadProfileId") - if (reloadProfileId == -1 || (app.profile != null && app.profile.id == reloadProfileId)) { + if (reloadProfileId == -1 || app.profile.id == reloadProfileId) { reloadTarget() return } } - var intentProfileId = -1 - var intentTargetId = -1 - - if (extras?.containsKey("profileId") == true) { + if (extras?.getInt("profileId", -1) != -1) { intentProfileId = extras.getInt("profileId", -1) - extras.remove("profileId") + extras?.remove("profileId") } - if (extras?.containsKey("fragmentId") == true) { + if (extras?.getInt("fragmentId", -1) != -1) { intentTargetId = extras.getInt("fragmentId", -1) - extras.remove("fragmentId") + extras?.remove("fragmentId") } /*if (intentTargetId == -1 && navController.currentDestination?.id == R.id.loadingFragment) { @@ -585,29 +763,33 @@ class MainActivity : AppCompatActivity() { }*/ if (navLoading) { - navLoading = false b.fragment.removeAllViews() if (intentTargetId == -1) intentTargetId = HOME_ID } when { - app.profile == null -> { + app.profile.id == 0 -> { if (intentProfileId == -1) - intentProfileId = app.appSharedPrefs.getInt("current_profile_id", 1) - loadProfile(intentProfileId, intentTargetId) + intentProfileId = app.config.lastProfileId + loadProfile(intentProfileId, intentTargetId, extras) } intentProfileId != -1 -> { - loadProfile(intentProfileId, intentTargetId) + if (app.profile.id != intentProfileId) + loadProfile(intentProfileId, intentTargetId, extras) + else + loadTarget(intentTargetId, extras) } intentTargetId != -1 -> { drawer.currentProfile = app.profile.id - loadTarget(intentTargetId, extras) + if (navTargetId != intentTargetId || navLoading) + loadTarget(intentTargetId, extras) } else -> { drawer.currentProfile = app.profile.id } } + navLoading = false } override fun recreate() { @@ -628,16 +810,32 @@ class MainActivity : AppCompatActivity() { startActivity(intent) } + override fun onStart() { + d(TAG, "Activity started") + super.onStart() + } + override fun onStop() { + d(TAG, "Activity stopped") + super.onStop() + } override fun onResume() { + d(TAG, "Activity resumed") val filter = IntentFilter() filter.addAction(Intent.ACTION_MAIN) registerReceiver(intentReceiver, filter) + EventBus.getDefault().register(this) super.onResume() } override fun onPause() { + d(TAG, "Activity paused") unregisterReceiver(intentReceiver) + EventBus.getDefault().unregister(this) super.onPause() } + override fun onDestroy() { + d(TAG, "Activity destroyed") + super.onDestroy() + } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -651,15 +849,10 @@ class MainActivity : AppCompatActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_LOGIN_ACTIVITY) { - if (resultCode == Activity.RESULT_CANCELED && false) { + if (!app.config.loginFinished) finish() - } else { - if (!app.appConfig.loginFinished) - finish() - else { - handleIntent(data?.extras) - } + handleIntent(data?.extras) } } } @@ -680,28 +873,23 @@ class MainActivity : AppCompatActivity() { fun loadProfile(id: Int) = loadProfile(id, navTargetId) fun loadProfile(id: Int, arguments: Bundle?) = loadProfile(id, navTargetId, arguments) fun loadProfile(id: Int, drawerSelection: Int, arguments: Bundle? = null) { - Log.d("NavDebug", "loadProfile(id = $id, drawerSelection = $drawerSelection)") - if (app.profile != null && App.profileId == id) { + if (App.profileId == id) { drawer.currentProfile = app.profile.id loadTarget(drawerSelection, arguments) return } - AsyncTask.execute { - app.profileLoadById(id) + app.profileLoad(id) { + MessagesFragment.pageSelection = -1 + MessagesListFragment.tapPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION) + MessagesListFragment.topPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION) + MessagesListFragment.bottomPositions = intArrayOf(RecyclerView.NO_POSITION, RecyclerView.NO_POSITION) - this.runOnUiThread { - if (app.profile == null) { - LoginActivity.firstCompleted = false - if (app.appConfig.loginFinished) { - // this shouldn't run - profileListEmptyListener() - } - } else { - setDrawerItems() - drawer.currentProfile = app.profile.id - loadTarget(drawerSelection, arguments) - } - } + setDrawerItems() + // the drawer profile is updated automatically when the drawer item is clicked + // update it manually when switching profiles from other source + //if (drawer.currentProfile != app.profile.id) + drawer.currentProfile = app.profileId + loadTarget(drawerSelection, arguments) } } fun loadTarget(id: Int, arguments: Bundle? = null) { @@ -710,7 +898,7 @@ class MainActivity : AppCompatActivity() { loadId = DRAWER_ITEM_HOME } val target = navTargetList - .singleOrNull { it.id == loadId } + .firstOrNull { it.id == loadId } if (target == null) { Toast.makeText(this, getString(R.string.error_invalid_fragment, id), Toast.LENGTH_LONG).show() loadTarget(navTargetList.first(), arguments) @@ -720,17 +908,20 @@ class MainActivity : AppCompatActivity() { } } private fun loadTarget(target: NavTarget, arguments: Bundle? = null) { - Log.d("NavDebug", "loadItem(id = ${target.id})") + d("NavDebug", "loadTarget(target = $target, arguments = $arguments)") bottomSheet.close() bottomSheet.removeAllContextual() bottomSheet.toggleGroupEnabled = false - bottomSheet.onCloseListener = null drawer.close() - drawer.setSelection(target.id, fireOnClick = false) + if (drawer.getSelection() != target.id) + drawer.setSelection(target.id, fireOnClick = false) navView.toolbar.setTitle(target.title ?: target.name) + navView.bottomBar.fabEnable = false + navView.bottomBar.fabExtended = false + navView.bottomBar.setFabOnClickListener(null) - Log.d("NavDebug", "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}") + d("NavDebug", "Navigating from ${navTarget.fragmentClass?.java?.simpleName} to ${target.fragmentClass?.java?.simpleName}") val fragment = target.fragmentClass?.java?.newInstance() ?: return fragment.arguments = arguments @@ -744,7 +935,7 @@ class MainActivity : AppCompatActivity() { ) } else { - navBackStack.lastIndexOf(target).let { + navBackStack.keys().lastIndexOf(target).let { if (it == -1) return@let target // pop the back stack up until that target @@ -775,8 +966,9 @@ class MainActivity : AppCompatActivity() { R.anim.task_open_enter, R.anim.task_open_exit ) - navBackStack.add(navTarget) + navBackStack.add(navTarget to arguments) navTarget = target + navArguments = arguments } } @@ -789,9 +981,9 @@ class MainActivity : AppCompatActivity() { } } - Log.d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:") + d("NavDebug", "Current fragment ${navTarget.fragmentClass?.java?.simpleName}, pop to home ${navTarget.popToHome}, back stack:") navBackStack.forEachIndexed { index, target2 -> - Log.d("NavDebug", " - $index: ${target2.fragmentClass?.java?.simpleName}") + d("NavDebug", " - $index: ${target2.first.fragmentClass?.java?.simpleName}") } transaction.replace(R.id.fragment, fragment) @@ -816,11 +1008,18 @@ class MainActivity : AppCompatActivity() { return false } // TODO back stack argument support - if (navTarget.popToHome) { - loadTarget(HOME_ID) - } - else { - loadTarget(navBackStack.last()) + when { + navTarget.popToHome -> { + loadTarget(HOME_ID) + } + navTarget.popTo != null -> { + loadTarget(navTarget.popTo ?: HOME_ID) + } + else -> { + navBackStack.last().let { + loadTarget(it.first, it.second) + } + } } return true } @@ -835,9 +1034,23 @@ class MainActivity : AppCompatActivity() { * that something has changed in the bottom sheet. */ fun gainAttention() { + if (app.config.ui.bottomSheetOpened) + return b.navView.postDelayed({ navView.gainAttentionOnBottomBar() + }, 2000) + } + + fun gainAttentionFAB() { + navView.bottomBar.fabExtended = false + + b.navView.postDelayed({ + navView.bottomBar.fabExtended = true }, 1000) + + b.navView.postDelayed({ + navView.bottomBar.fabExtended = false + }, 3000) } /* _____ _ _ @@ -850,7 +1063,7 @@ class MainActivity : AppCompatActivity() { val item = DrawerPrimaryItem() .withIdentifier(target.id.toLong()) .withName(target.name) - .withHiddenInMiniDrawer(!app.appConfig.miniDrawerButtonIds.contains(target.id)) + .withIsHiddenInMiniDrawer(!app.config.ui.miniMenuButtons.contains(target.id)) .also { if (target.description != null) it.withDescription(target.description!!) } .also { if (target.icon != null) it.withIcon(target.icon!!) } .also { if (target.title != null) it.withAppTitle(getString(target.title!!)) } @@ -870,12 +1083,11 @@ class MainActivity : AppCompatActivity() { } fun setDrawerItems() { - Log.d("NavDebug", "setDrawerItems() app.profile = ${app.profile ?: "null"}") + d("NavDebug", "setDrawerItems() app.profile = ${app.profile}") val drawerItems = arrayListOf>() val drawerProfiles = arrayListOf() - val supportedFragments = if (app.profile == null) arrayListOf() - else app.profile.supportedFragments + val supportedFragments = app.profile.supportedFragments targetPopToHomeList.clear() @@ -915,7 +1127,7 @@ class MainActivity : AppCompatActivity() { drawer.addProfileSettings(*drawerProfiles.toTypedArray()) } - private fun showProfileContextMenu(profile: IProfile<*>, view: View) { + private fun showProfileContextMenu(profile: IProfile, view: View) { val profileId = profile.identifier.toInt() val popupMenu = PopupMenu(this, view) popupMenu.menu.add(0, 1, 1, R.string.profile_menu_open_settings) @@ -928,7 +1140,7 @@ class MainActivity : AppCompatActivity() { } loadTarget(DRAWER_ITEM_SETTINGS, null) } else if (item.itemId == 2) { - app.apiEdziennik.guiRemoveProfile(this@MainActivity, profileId, profile.name?.getText(this).toString()) + ProfileRemoveDialog(this, profileId, profile.name?.getText(this) ?: "?") } true } @@ -939,30 +1151,16 @@ class MainActivity : AppCompatActivity() { private var targetHomeId: Int = -1 override fun onBackPressed() { if (!b.navView.onBackPressed()) { - - navigateUp() - - /*val currentDestinationId = navController.currentDestination?.id - - if (if (targetHomeId != -1 && targetPopToHomeList.contains(navController.currentDestination?.id)) { - if (!navController.popBackStack(targetHomeId, false)) { - navController.navigateUp() - } - true - } else { - navController.navigateUp() - }) { - val currentId = navController.currentDestination?.id ?: -1 - val drawerSelection = navTargetList - .singleOrNull { - it.navGraphId == currentId - }?.also { - navView.toolbar.setTitle(it.title ?: it.name) - }?.id ?: -1 - drawer.setSelection(drawerSelection, false) + if (App.config.ui.openDrawerOnBackPressed && ((navTarget.popTo == null && navTarget.popToHome) + || navTarget.id == DRAWER_ITEM_HOME)) { + b.navView.drawer.toggle() } else { - super.onBackPressed() - }*/ + navigateUp() + } } } + + fun error(error: ApiError) = errorSnackbar.addError(error).show() + fun snackbar(text: String, actionText: String? = null, onClick: (() -> Unit)? = null) = mainSnackbar.snackbar(text, actionText, onClick) + fun snackbarDismiss() = mainSnackbar.dismiss() } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/Notifier.java b/app/src/main/java/pl/szczodrzynski/edziennik/Notifier.java deleted file mode 100644 index 512affed..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/Notifier.java +++ /dev/null @@ -1,356 +0,0 @@ -package pl.szczodrzynski.edziennik; - -import android.app.IntentService; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import androidx.core.app.NotificationCompat; -import androidx.core.content.ContextCompat; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Time; -import pl.szczodrzynski.edziennik.receivers.BootReceiver; -import pl.szczodrzynski.edziennik.sync.SyncJob; -import pl.szczodrzynski.edziennik.sync.SyncService; - -import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT; -import static androidx.core.app.NotificationCompat.PRIORITY_MAX; -import static pl.szczodrzynski.edziennik.sync.SyncService.ACTION_CANCEL; - -public class Notifier { - - private static final String TAG = "Notifier"; - public static final int ID_GET_DATA = 1337000; - public static final int ID_GET_DATA_ERROR = 1337001; - private static String CHANNEL_GET_DATA_NAME; - private static String CHANNEL_GET_DATA_DESC; - private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA"; - - private static final int ID_NOTIFICATIONS = 1337002; - private static String CHANNEL_NOTIFICATIONS_NAME; - private static String CHANNEL_NOTIFICATIONS_DESC; - public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS"; - - private static final int ID_NOTIFICATIONS_QUIET = 1337002; - private static String CHANNEL_NOTIFICATIONS_QUIET_NAME; - private static String CHANNEL_NOTIFICATIONS_QUIET_DESC; - public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET"; - - private static final int ID_UPDATES = 1337003; - private static String CHANNEL_UPDATES_NAME; - private static String CHANNEL_UPDATES_DESC; - private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES"; - - private App app; - private NotificationManager notificationManager; - private NotificationCompat.Builder getDataNotificationBuilder; - private int notificationColor; - - Notifier(App _app) { - this.app = _app; - - CHANNEL_GET_DATA_NAME = app.getString(R.string.notification_channel_get_data_name); - CHANNEL_GET_DATA_DESC = app.getString(R.string.notification_channel_get_data_desc); - CHANNEL_NOTIFICATIONS_NAME = app.getString(R.string.notification_channel_notifications_name); - CHANNEL_NOTIFICATIONS_DESC = app.getString(R.string.notification_channel_notifications_desc); - CHANNEL_NOTIFICATIONS_QUIET_NAME = app.getString(R.string.notification_channel_notifications_quiet_name); - CHANNEL_NOTIFICATIONS_QUIET_DESC = app.getString(R.string.notification_channel_notifications_quiet_desc); - CHANNEL_UPDATES_NAME = app.getString(R.string.notification_channel_updates_name); - CHANNEL_UPDATES_DESC = app.getString(R.string.notification_channel_updates_desc); - - notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary); - notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_LOW); - channelGetData.setDescription(CHANNEL_GET_DATA_DESC); - notificationManager.createNotificationChannel(channelGetData); - - NotificationChannel channelNotifications = new NotificationChannel(GROUP_KEY_NOTIFICATIONS, CHANNEL_NOTIFICATIONS_NAME, NotificationManager.IMPORTANCE_HIGH); - channelNotifications.setDescription(CHANNEL_NOTIFICATIONS_DESC); - channelNotifications.enableLights(true); - channelNotifications.setLightColor(notificationColor); - notificationManager.createNotificationChannel(channelNotifications); - - NotificationChannel channelNotificationsQuiet = new NotificationChannel(GROUP_KEY_NOTIFICATIONS_QUIET, CHANNEL_NOTIFICATIONS_QUIET_NAME, NotificationManager.IMPORTANCE_DEFAULT); - channelNotificationsQuiet.setDescription(CHANNEL_NOTIFICATIONS_QUIET_DESC); - channelNotificationsQuiet.setSound(null, null); - channelNotificationsQuiet.enableVibration(false); - notificationManager.createNotificationChannel(channelNotificationsQuiet); - - NotificationChannel channelUpdates = new NotificationChannel(GROUP_KEY_UPDATES, CHANNEL_UPDATES_NAME, NotificationManager.IMPORTANCE_HIGH); - channelUpdates.setDescription(CHANNEL_UPDATES_DESC); - notificationManager.createNotificationChannel(channelUpdates); - } - } - - public boolean shouldBeQuiet() { - long now = Time.getNow().getInMillis(); - long start = app.appConfig.quietHoursStart; - long end = app.appConfig.quietHoursEnd; - if (start > end) { - end += 1000 * 60 * 60 * 24; - //Log.d(TAG, "Night passing"); - } - if (start > now) { - now += 1000 * 60 * 60 * 24; - //Log.d(TAG, "Now is smaller"); - } - //Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end); - return app.appConfig.quietHoursStart > 0 && now >= start && now <= end; - } - - private int getNotificationDefaults() { - return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL); - } - private String getNotificationGroup() { - return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS; - } - private int getNotificationPriority() { - return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX; - } - - /* _____ _ _____ _ - | __ \ | | / ____| | | - | | | | __ _| |_ __ _ | | __ ___| |_ - | | | |/ _` | __/ _` | | | |_ |/ _ \ __| - | |__| | (_| | || (_| | | |__| | __/ |_ - |_____/ \__,_|\__\__,_| \_____|\___|\_*/ - public Notification notificationGetDataShow(int maxProgress) { - Intent notificationIntent = new Intent(app.getContext(), SyncService.class); - notificationIntent.setAction(ACTION_CANCEL); - PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, - notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - getDataNotificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_GET_DATA) - .setSmallIcon(android.R.drawable.stat_sys_download) - .setColor(notificationColor) - .setContentTitle(app.getString(R.string.notification_get_data_title)) - .setContentText(app.getString(R.string.notification_get_data_text)) - .addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent) - //.setGroup(GROUP_KEY_GET_DATA) - .setOngoing(true) - .setProgress(maxProgress, 0, false) - .setTicker(app.getString(R.string.notification_get_data_summary)) - .setPriority(NotificationCompat.PRIORITY_LOW); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataProgress(int progress, int maxProgress) { - getDataNotificationBuilder.setProgress(maxProgress, progress, false); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataAction(int stringResId) { - getDataNotificationBuilder.setContentTitle(app.getString(R.string.sync_action_format, app.getString(stringResId))); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataProfile(String profileName) { - getDataNotificationBuilder.setContentText(profileName); - return getDataNotificationBuilder.build(); - } - - public Notification notificationGetDataError(String profileName, String error, int failedProfileId) { - Intent notificationIntent = new Intent(app.getContext(), Notifier.GetDataRetryService.class); - notificationIntent.putExtra("failedProfileId", failedProfileId); - PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, - notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - getDataNotificationBuilder.mActions.clear(); - /*try { - //Use reflection clean up old actions - Field f = getDataNotificationBuilder.getClass().getDeclaredField("mActions"); - f.setAccessible(true); - f.set(getDataNotificationBuilder, new ArrayList()); - } catch (Exception e) { - e.printStackTrace(); - }*/ - - getDataNotificationBuilder.setProgress(0, 0, false) - .setTicker(app.getString(R.string.notification_get_data_error_summary)) - .setSmallIcon(android.R.drawable.stat_sys_warning) - .addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_once_again), pendingIntent) - .setContentTitle(app.getString(R.string.notification_get_data_error_title, profileName)) - .setContentText(error) - .setStyle(new NotificationCompat.BigTextStyle().bigText(error)) - .setOngoing(false); - return getDataNotificationBuilder.build(); - } - - public void notificationPost(int id, Notification notification) { - notificationManager.notify(id, notification); - } - - public void notificationCancel(int id) { - notificationManager.cancel(id); - } - - //public void notificationGetDataHide() { - // notificationManager.cancel(ID_GET_DATA); - // } - - public static class GetDataRetryService extends IntentService { - private static final String TAG = "Notifier/GetDataRetry"; - - public GetDataRetryService() { - super(Notifier.GetDataRetryService.class.getSimpleName()); - } - - @Override - protected void onHandleIntent(Intent intent) { - SyncJob.run((App) getApplication(), intent.getExtras().getInt("failedProfileId", -1), -1); - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - assert notificationManager != null; - notificationManager.cancel(ID_GET_DATA_ERROR); - } - } - - /* _ _ _ _ __ _ _ _ - | \ | | | | (_)/ _(_) | | (_) - | \| | ___ | |_ _| |_ _ ___ __ _| |_ _ ___ _ __ - | . ` |/ _ \| __| | _| |/ __/ _` | __| |/ _ \| '_ \ - | |\ | (_) | |_| | | | | (_| (_| | |_| | (_) | | | | - |_| \_|\___/ \__|_|_| |_|\___\__,_|\__|_|\___/|_| |*/ - public void add(pl.szczodrzynski.edziennik.models.Notification notification) { - app.appConfig.notifications.add(notification); - } - - public void postAll(ProfileFull profile) { - Collections.sort(app.appConfig.notifications, (o1, o2) -> (o2.addedDate - o1.addedDate > 0) ? 1 : (o2.addedDate - o1.addedDate < 0) ? -1 : 0); - if (profile != null && !profile.getSyncNotifications()) - return; - - if (app.appConfig.notifications.size() > 40) { - app.appConfig.notifications.subList(40, app.appConfig.notifications.size() - 1).clear(); - } - - int unreadCount = 0; - List notificationList = new ArrayList<>(); - for (pl.szczodrzynski.edziennik.models.Notification notification: app.appConfig.notifications) { - if (!notification.notified) { - notification.seen = false; - notification.notified = true; - unreadCount++; - if (notificationList.size() < 10) { - notificationList.add(notification); - } - } - else { - notification.seen = true; - } - } - - for (pl.szczodrzynski.edziennik.models.Notification notification: notificationList) { - Intent intent = new Intent(app, MainActivity.class); - notification.fillIntent(intent); - PendingIntent pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0); - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, getNotificationGroup()) - // title, text, type, date - .setContentTitle(notification.title) - .setContentText(notification.text) - .setSubText(pl.szczodrzynski.edziennik.models.Notification.stringType(app, notification.type)) - .setWhen(notification.addedDate) - .setTicker(app.getString(R.string.notification_ticker_format, pl.szczodrzynski.edziennik.models.Notification.stringType(app, notification.type))) - // icon, color, lights, priority - .setSmallIcon(R.drawable.ic_notification) - .setColor(notificationColor) - .setLights(0xFF00FFFF, 2000, 2000) - .setPriority(getNotificationPriority()) - // channel, group, style - .setChannelId(getNotificationGroup()) - .setGroup(getNotificationGroup()) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - .setStyle(new NotificationCompat.BigTextStyle().bigText(notification.text)) - // intent, auto cancel - .setContentIntent(pendingIntent) - .setAutoCancel(true); - if (!shouldBeQuiet()) { - notificationBuilder.setDefaults(getNotificationDefaults()); - } - notificationManager.notify(notification.id, notificationBuilder.build()); - } - - if (notificationList.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - Intent intent = new Intent(app, MainActivity.class); - intent.setAction("android.intent.action.MAIN"); - intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS); - PendingIntent pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS, - intent, 0); - - NotificationCompat.Builder groupBuilder = - new NotificationCompat.Builder(app, getNotificationGroup()) - .setSmallIcon(R.drawable.ic_notification) - .setColor(notificationColor) - .setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount)) - .setGroupSummary(true) - .setAutoCancel(true) - .setChannelId(getNotificationGroup()) - .setGroup(getNotificationGroup()) - .setLights(0xFF00FFFF, 2000, 2000) - .setPriority(getNotificationPriority()) - .setContentIntent(pendingIntent) - .setStyle(new NotificationCompat.BigTextStyle()); - if (!shouldBeQuiet()) { - groupBuilder.setDefaults(getNotificationDefaults()); - } - notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build()); - } - } - - /* _ _ _ _ - | | | | | | | | - | | | |_ __ __| | __ _| |_ ___ ___ - | | | | '_ \ / _` |/ _` | __/ _ \/ __| - | |__| | |_) | (_| | (_| | || __/\__ \ - \____/| .__/ \__,_|\__,_|\__\___||___/ - | | - |*/ - public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) { - if (!app.appConfig.notifyAboutUpdates) - return; - Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class) - .putExtra("update_version", updateVersion) - .putExtra("update_url", updateUrl) - .putExtra("update_filename", updateFilename); - - PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0, - notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_UPDATES) - .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setColor(notificationColor) - .setContentTitle(app.getString(R.string.notification_updates_title)) - .setContentText(app.getString(R.string.notification_updates_text, updateVersion)) - .setLights(0xFF00FFFF, 2000, 2000) - .setContentIntent(pendingIntent) - .setTicker(app.getString(R.string.notification_updates_summary)) - .setPriority(PRIORITY_MAX) - .setAutoCancel(true); - if (!shouldBeQuiet()) { - notificationBuilder.setDefaults(getNotificationDefaults()); - } - notificationManager.notify(ID_UPDATES, notificationBuilder.build()); - } - - public void notificationUpdatesHide() { - if (!app.appConfig.notifyAboutUpdates) - return; - notificationManager.cancel(ID_UPDATES); - } - - public void dump() { - for (pl.szczodrzynski.edziennik.models.Notification notification: app.appConfig.notifications) { - Log.d(TAG, "Profile"+notification.profileId+" Notification from "+ Date.fromMillis(notification.addedDate).getFormattedString()+" "+ Time.fromMillis(notification.addedDate).getStringHMS()+" - "+notification.text); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/WidgetTimetable.java b/app/src/main/java/pl/szczodrzynski/edziennik/WidgetTimetable.java deleted file mode 100644 index 8a2fe720..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/WidgetTimetable.java +++ /dev/null @@ -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.datamodels.EventFull; -import pl.szczodrzynski.edziennik.datamodels.LessonChange; -import pl.szczodrzynski.edziennik.datamodels.LessonFull; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.fragments.HomeFragment; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.ItemWidgetTimetableModel; -import pl.szczodrzynski.edziennik.models.Time; -import pl.szczodrzynski.edziennik.models.Week; -import pl.szczodrzynski.edziennik.widgets.WidgetConfig; -import pl.szczodrzynski.edziennik.sync.SyncJob; -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.datamodels.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> timetables = null; - - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_SYNC_DATA.equals(intent.getAction())){ - SyncJob.run((App) context.getApplicationContext()); - } - 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 profileList = new ArrayList<>(); - if (unified) { - profileList = app.db.profileDao().getAllNow(); - filterOutArchived(profileList); - } - else { - Profile profile = app.db.profileDao().getByIdNow(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 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 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 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"); - } -} - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/activities/CounterActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/activities/CounterActivity.java deleted file mode 100644 index 0e11eff0..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/activities/CounterActivity.java +++ /dev/null @@ -1,240 +0,0 @@ -package pl.szczodrzynski.edziennik.activities; - -import androidx.databinding.DataBindingUtil; -import android.os.AsyncTask; -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.ActivityCounterBinding; -import pl.szczodrzynski.edziennik.datamodels.LessonFull; -import pl.szczodrzynski.edziennik.fragments.HomeFragment; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Time; - -import static pl.szczodrzynski.edziennik.fragments.HomeFragment.updateInterval; - -public class CounterActivity extends AppCompatActivity { - - private static final String TAG = "CounterActivity"; - private App app; - private ActivityCounterBinding b; - - Timer timetableTimer; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - app = (App) getApplication(); - b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_counter, null, false); - setContentView(b.getRoot()); - - timetableTimer = new Timer(); - - update(); - } - - private List lessons = new ArrayList<>(); - - private void update() { - // BELL SYNCING - Time now = Time.getNow(); - Time syncedNow = now; - //Time updateDiff = null; - if (app.appConfig.bellSyncDiff != null) { - if (app.appConfig.bellSyncMultiplier < 0) { - // the bell is too fast, need to step further to go with it - // add some time - syncedNow = Time.sum(now, app.appConfig.bellSyncDiff); - //Toast.makeText(c, "Bell sync diff is "+app.appConfig.bellSyncDiff.getStringHMS()+"\n\n Synced now is "+syncedNow.getStringHMS(), Toast.LENGTH_LONG).show(); - } - if (app.appConfig.bellSyncMultiplier > 0) { - // the bell is delayed, need to roll the "now" time back - // subtract some time - syncedNow = Time.diff(now, app.appConfig.bellSyncDiff); - } - } - - assert counterTarget != null; - if (lessons.size() == 0 || syncedNow.getValue() > counterTarget.getValue()) { - findLessons(syncedNow); - } - else { - scheduleUpdate(updateCounter(syncedNow)); - } - } - - private void scheduleUpdate(long newRefreshInterval) { - try { - timetableTimer.schedule(new TimerTask() { - @Override - public void run() { - runOnUiThread(() -> update()); - } - }, newRefreshInterval); - } - catch (Exception e) { - e.printStackTrace(); - } - } - - private void findLessons(Time syncedNow) { - AsyncTask.execute(() -> { - Date today = Date.getToday(); - lessons = app.db.lessonDao().getAllNearestNow(App.profileId, today.clone().stepForward(0, 0, -today.getWeekDay()), today, syncedNow); - - if (lessons != null && lessons.size() != 0) { - Date displayingDate = lessons.get(0).lessonDate; - if (displayingDate == null) { - runOnUiThread(() -> scheduleUpdate(updateViews(null, syncedNow, 0, 0))); - return; - } - int displayingWeekDay = displayingDate.getWeekDay(); - - Log.d(TAG, "Displaying date is "+displayingDate.getStringY_m_d()+", weekDay is "+displayingWeekDay); - - int notPassedIndex = -1; - int notPassedWeekDay = -1; - //int firstIndex = -1; - int lastIndex = -1; - int index = 0; - for (LessonFull lesson: lessons) { - if (notPassedIndex == -1 && !lesson.lessonPassed) { - if (lesson.lessonDate != null) - displayingDate = lesson.lessonDate; - displayingWeekDay = lesson.weekDay; - notPassedIndex = index; - notPassedWeekDay = lesson.weekDay; - } - if (lesson.weekDay == notPassedWeekDay) { - /*if (firstIndex == -1) - firstIndex = index;*/ - lastIndex = index; - } - - index++; - } - - // for safety - /*if (firstIndex == -1) - firstIndex++;*/ - if (notPassedIndex == -1) - notPassedIndex++; - if (lastIndex == -1) - lastIndex++; - - Log.d(TAG, "Not passed index is "+notPassedIndex); - Log.d(TAG, "Last index is "+lastIndex); - Log.d(TAG, "New Displaying date is "+displayingDate.getStringY_m_d()+", weekDay is "+displayingWeekDay); - - Date finalDisplayingDate = displayingDate; - int finalNotPassedIndex = notPassedIndex; - int finalLastIndex = lastIndex; - runOnUiThread(() -> scheduleUpdate(updateViews(finalDisplayingDate, syncedNow, finalNotPassedIndex, finalLastIndex))); - } - else { - runOnUiThread(() -> scheduleUpdate(updateViews(null, syncedNow, 0, 0))); - } - - }); - } - - private Time counterTarget = new Time(0, 0, 0); - private static final short TIME_TILL = 0; - private static final short TIME_LEFT = 1; - private short counterType = TIME_LEFT; - private long updateCounter(Time syncedNow) { - Time diff = Time.diff(counterTarget, syncedNow); - b.timeLeft.setText(counterType == TIME_TILL ? HomeFragment.timeTill(app, diff, app.appConfig.countInSeconds, "\n") : HomeFragment.timeLeft(app, diff, app.appConfig.countInSeconds, "\n")); - return updateInterval(app, diff); - } - - private long updateViews(Date displayingDate, Time syncedNow, int notPassedIndex, int lastIndex) { - long newRefreshInterval = 1000*5; - - if (displayingDate == null) { - return newRefreshInterval; - } - - int dayDiff = Date.diffDays(displayingDate, Date.getToday()); - if (displayingDate.getValue() != Date.getToday().getValue() && dayDiff == 0) { - dayDiff++; - } - - LessonFull lessonFirst = lessons.get(dayDiff == 0 ? 0 : notPassedIndex); - // should never be out of range - LessonFull lessonLast = lessons.get(lastIndex); - - boolean duringLessons = Time.inRange(lessonFirst.startTime, lessonLast.endTime, syncedNow) && dayDiff == 0; - if (duringLessons) { - LessonFull lessonCurrent = null; - LessonFull lessonNext = null; - - if (lessons.get(notPassedIndex).lessonCurrent) { - lessonCurrent = lessons.get(notPassedIndex); - if (lessons.size() > notPassedIndex+1 && lessons.get(notPassedIndex+1).weekDay == displayingDate.getWeekDay()) - lessonNext = lessons.get(notPassedIndex+1); - } - else { - lessonNext = lessons.get(notPassedIndex); - } - - if (lessonCurrent != null) { // show time to the end of this lesson - b.lessonName.setText(lessonCurrent.subjectLongName); - - counterType = TIME_LEFT; - counterTarget = lessonCurrent.endTime; - newRefreshInterval = updateCounter(syncedNow); - } - else if (lessonNext != null) { // it's break time, show time to the start of next lesson - b.lessonName.setText(R.string.lesson_break); - - counterType = TIME_LEFT; - counterTarget = lessonNext.startTime; - newRefreshInterval = updateCounter(syncedNow); - } - else { // idk what it is now (during lessons, but not during lesson or a break) - b.lessonName.setText(R.string.card_timetable_wtf); - b.timeLeft.setText(R.string.card_timetable_wtf_report); - newRefreshInterval = 1000*60*2; - finish(); - } - } - else { - if (syncedNow.getValue() < lessonFirst.startTime.getValue()) { - // before lessons - b.lessonName.setText(R.string.lesson_break); - - counterType = TIME_LEFT; - counterTarget = lessonFirst.startTime; - newRefreshInterval = updateCounter(syncedNow); - } - else { - finish(); - } - } - - return newRefreshInterval; - } - - @Override - protected void onDestroy() { - super.onDestroy(); - //Log.d(TAG, "OnDestroy"); - try { - timetableTimer.cancel(); - timetableTimer.purge(); - } - catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/activities/CrashActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/activities/CrashActivity.java deleted file mode 100644 index de8ed06c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/activities/CrashActivity.java +++ /dev/null @@ -1,180 +0,0 @@ -package pl.szczodrzynski.edziennik.activities; - -/* - * Copyright 2014-2017 Eduard Ereza Martínez - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import android.text.Html; -import android.util.Base64; -import android.view.View; -import android.widget.Button; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; - -import cat.ereza.customactivityoncrash.CustomActivityOnCrash; -import cat.ereza.customactivityoncrash.config.CaocConfig; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.Themes; - -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.datamodels.Profile.REGISTRATION_ENABLED; - -public final class CrashActivity extends AppCompatActivity { - - private App app; - - @SuppressLint("PrivateResource") - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.app = (App)getApplication(); - setTheme(Themes.INSTANCE.getAppTheme()); - - setContentView(R.layout.activity_crash); - - final CaocConfig config = CustomActivityOnCrash.getConfigFromIntent(getIntent()); - - if (config == null) { - //This should never happen - Just finish the activity to avoid a recursive crash. - finish(); - return; - } - - //Close/restart button logic: - //If a class if set, use restart. - //Else, use close and just finish the app. - //It is recommended that you follow this logic if implementing a custom error activity. - Button restartButton = findViewById(R.id.crash_restart_btn); - restartButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CustomActivityOnCrash.restartApplication(CrashActivity.this, config); - } - }); - - - Button devMessageButton = findViewById(R.id.crash_dev_message_btn); - devMessageButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(CrashActivity.this, CrashGtfoActivity.class); - startActivity(i); - } - }); - - final Button reportButton = findViewById(R.id.crash_report_btn); - reportButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (!app.networkUtils.isOnline()) - { - new MaterialDialog.Builder(CrashActivity.this) - .title(R.string.network_you_are_offline_title) - .content(R.string.network_you_are_offline_text) - .positiveText(R.string.ok) - .show(); - } - else - { - //app.networkUtils.setSelfSignedSSL(CrashActivity.this, null); - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?report", "CrashActivity") - .setBodyParameter("base64_encoded", Base64.encodeToString(getErrorString(getIntent(), true).getBytes(), Base64.DEFAULT)) - .run((e, result) -> { - if (result != null) - { - if (result.get("success").getAsBoolean()) { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show(); - reportButton.setEnabled(false); - reportButton.setTextColor(getResources().getColor(android.R.color.darker_gray)); - } - else { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_cannot_send) + ": " + result.get("reason").getAsString(), Toast.LENGTH_LONG).show(); - } - } - else - { - Toast.makeText(CrashActivity.this, getString(R.string.crash_report_cannot_send)+" JsonObject equals null", Toast.LENGTH_LONG).show(); - } - }); - } - } - }); - - Button moreInfoButton = findViewById(R.id.crash_details_btn); - moreInfoButton.setOnClickListener(v -> new MaterialDialog.Builder(CrashActivity.this) - .title(R.string.crash_details) - .content(Html.fromHtml(getErrorString(getIntent(), false))) - .typeface(null, "RobotoMono-Regular.ttf") - .positiveText(R.string.close) - .neutralText(R.string.copy_to_clipboard) - .onNeutral((dialog, which) -> copyErrorToClipboard()) - .show()); - - String errorInformation = CustomActivityOnCrash.getAllErrorDetailsFromIntent(CrashActivity.this, getIntent()); - if (errorInformation.contains("MANUAL CRASH")) - { - findViewById(R.id.crash_notice).setVisibility(View.GONE); - findViewById(R.id.crash_report_btn).setVisibility(View.GONE); - findViewById(R.id.crash_feature).setVisibility(View.VISIBLE); - } - else - { - findViewById(R.id.crash_notice).setVisibility(View.VISIBLE); - findViewById(R.id.crash_report_btn).setVisibility(View.VISIBLE); - findViewById(R.id.crash_feature).setVisibility(View.GONE); - } - } - - private String getErrorString(Intent intent, boolean plain) { - // build a string containing the stack trace and the device name + user's registration data - String contentPlain = "Crash report:\n\n"+CustomActivityOnCrash.getStackTraceFromIntent(intent); - String content = ""+contentPlain+""; - content = content.replaceAll(getPackageName(), ""+getPackageName()+""); - content = content.replaceAll("\n", "
"); - - contentPlain += "\n"+Build.MANUFACTURER+"\n"+Build.BRAND+"\n"+Build.MODEL+"\n"+Build.DEVICE+"\n"; - if (app.profile != null && app.profile.getRegistration() == REGISTRATION_ENABLED) { - contentPlain += "U: "+app.profile.getUsernameId()+"\nS: "+ app.profile.getStudentNameLong() +"\n"; - } - contentPlain += BuildConfig.VERSION_NAME+" "+BuildConfig.BUILD_TYPE; - - return plain ? contentPlain : content; - } - - private void copyErrorToClipboard() { - String errorInformation = CustomActivityOnCrash.getAllErrorDetailsFromIntent(CrashActivity.this, getIntent()); - - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - - //Are there any devices without clipboard...? - if (clipboard != null) { - ClipData clip = ClipData.newPlainText(getString(R.string.customactivityoncrash_error_activity_error_details_clipboard_label), errorInformation); - clipboard.setPrimaryClip(clip); - Toast.makeText(CrashActivity.this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/activities/FeedbackActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/activities/FeedbackActivity.java deleted file mode 100644 index 97cdba68..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/activities/FeedbackActivity.java +++ /dev/null @@ -1,363 +0,0 @@ -package pl.szczodrzynski.edziennik.activities; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.databinding.DataBindingUtil; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.ActivityFeedbackBinding; -import pl.szczodrzynski.edziennik.datamodels.FeedbackMessage; -import pl.szczodrzynski.edziennik.datamodels.FeedbackMessageWithCount; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.Anim; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.MenuItem; -import android.view.View; -import android.view.animation.Animation; -import android.widget.PopupMenu; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.github.bassaer.chatmessageview.model.IChatUser; -import com.github.bassaer.chatmessageview.model.Message; -import com.github.bassaer.chatmessageview.view.ChatView; - -import java.util.Calendar; -import java.util.List; - -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.utils.Utils.crc16; -import static pl.szczodrzynski.edziennik.utils.Utils.openUrl; - -public class FeedbackActivity extends AppCompatActivity { - - private static final String TAG = "FeedbackActivity"; - private App app; - private ActivityFeedbackBinding b; - private boolean firstSend = true; - private String deviceToSend = null; - private String nameToSend = null; - - private BroadcastReceiver receiver; - - private class User implements IChatUser { - Integer id; - String name; - Bitmap icon; - - public User(int id, String name, Bitmap icon) { - this.id = id; - this.name = name; - this.icon = icon; - } - - @Override - public String getId() { - return this.id.toString(); - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Bitmap getIcon() { - return this.icon; - } - - @Override - public void setIcon(Bitmap icon) { - this.icon = icon; - } - } - - private User dev; - private User user; - private ChatView mChatView; - - private void send(String text){ - /*if ("enable dev mode pls".equals(text)) { - try { - Log.d(TAG, Utils.AESCrypt.encrypt("ok here you go it's enabled now", "8iryqZUfIUiLmJGi")); - } catch (Exception e) { - e.printStackTrace(); - } - return; - }*/ - MaterialDialog progressDialog = new MaterialDialog.Builder(this) - .title(R.string.loading) - .content(R.string.sending_message) - .negativeText(R.string.cancel) - .show(); - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?feedback_message", "FeedbackSend") - .setBodyParameter("message_text", text) - .setBodyParameter("target_device", deviceToSend == null ? "null" : deviceToSend) - .run(((e, result) -> { - progressDialog.dismiss(); - if (result != null && result.get("success") != null && result.get("success").getAsBoolean()) { - FeedbackMessage feedbackMessage = new FeedbackMessage(false, text); - if (deviceToSend != null) { - feedbackMessage.fromUser = deviceToSend; - feedbackMessage.fromUserName = nameToSend; - } - AsyncTask.execute(() -> app.db.feedbackMessageDao().add(feedbackMessage)); - Message message = new Message.Builder() - .setUser(user) - .setRight(true) - .setText(feedbackMessage.text) - .hideIcon(true) - .build(); - mChatView.send(message); - mChatView.setInputText(""); - b.textInput.setText(""); - if (firstSend) { - Anim.fadeOut(b.inputLayout, 500, new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { - b.inputLayout.setVisibility(View.GONE); - Anim.fadeIn(b.chatLayout, 500, null); - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - }); - if (deviceToSend == null) { - // we are not the developer - FeedbackMessage feedbackMessage2 = new FeedbackMessage(true, "Postaram się jak najszybciej Tobie odpowiedzieć. Dostaniesz powiadomienie o odpowiedzi, która pokaże się w tym miejscu."); - AsyncTask.execute(() -> app.db.feedbackMessageDao().add(feedbackMessage2)); - message = new Message.Builder() - .setUser(dev) - .setRight(false) - .setText(feedbackMessage2.text) - .hideIcon(false) - .build(); - mChatView.receive(message); - } - firstSend = false; - } - } - else { - Toast.makeText(app, "Nie udało się wysłać wiadomości.", Toast.LENGTH_SHORT).show(); - } - })); - } - - private void openFaq() { - openUrl(this, "http://szkolny.eu/pomoc/"); - new MaterialDialog.Builder(this) - .title(R.string.faq_back_title) - .content(R.string.faq_back_text) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive(((dialog, which) -> { - - })) - .onNegative(((dialog, which) -> { - - })) - .show(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setTheme(Themes.INSTANCE.getAppTheme()); - b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_feedback, null, false); - setContentView(b.getRoot()); - app = (App) getApplication(); - - setSupportActionBar(b.toolbar); - if (getSupportActionBar() != null) - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - b.faqText.setOnClickListener((v -> { - openFaq(); - })); - b.faqButton.setOnClickListener((v -> { - openFaq(); - })); - - receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - FeedbackMessage message = app.gson.fromJson(intent.getStringExtra("message"), FeedbackMessage.class); - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(message.sentTime); - Message chatMessage = new Message.Builder() - .setUser(intent.getStringExtra("type").equals("dev_chat") ? new User(crc16(message.fromUser.getBytes()), message.fromUserName, BitmapFactory.decodeResource(getResources(), R.drawable.ic_account_circle)) : dev) - .setRight(!message.received) - .setText(message.text) - .setSendTime(c) - .hideIcon(!message.received) - .build(); - if (message.received) - mChatView.receive(chatMessage); - else - mChatView.send(chatMessage); - } - }; - - mChatView = b.chatView; - - dev = new User(0, "Szkolny.eu", BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); - user = new User(1, "Ja", BitmapFactory.decodeResource(getResources(), R.drawable.profile)); - - //Set UI parameters if you need - mChatView.setLeftBubbleColor(Utils.getAttr(this, R.attr.colorSurface)); - mChatView.setLeftMessageTextColor(Utils.getAttr(this, android.R.attr.textColorPrimary)); - mChatView.setRightBubbleColor(Utils.getAttr(this, R.attr.colorPrimary)); - mChatView.setRightMessageTextColor(Color.WHITE); - - //mChatView.setBackgroundColor(ContextCompat.getColor(this, R.color.blueGray500)); - mChatView.setSendButtonColor(Utils.getAttr(this, R.attr.colorAccent)); - mChatView.setSendIcon(R.drawable.ic_action_send); - //mChatView.setUsernameTextColor(Color.WHITE); - //mChatView.setSendTimeTextColor(Color.WHITE); - //mChatView.setDateSeparatorColor(Color.WHITE); - mChatView.setInputTextHint("Napisz..."); - //mChatView.setInputTextColor(Color.BLACK); - mChatView.setMessageMarginTop(5); - mChatView.setMessageMarginBottom(5); - - if (App.devMode && app.deviceId.equals("f054761fbdb6a238")) { - b.targetDeviceLayout.setVisibility(View.VISIBLE); - b.targetDeviceDropDown.setOnClickListener((v -> { - AsyncTask.execute(() -> { - List messageList = app.db.feedbackMessageDao().getAllWithCountNow(); - runOnUiThread(() -> { - PopupMenu popupMenu = new PopupMenu(this, b.targetDeviceDropDown); - int index = 0; - for (FeedbackMessageWithCount message: messageList) { - popupMenu.getMenu().add(0, index, index, message.fromUserName+" - "+message.fromUser+" ("+message.messageCount+")"); - index++; - } - popupMenu.setOnMenuItemClickListener(item -> { - b.targetDeviceDropDown.setText(item.getTitle()); - mChatView.getMessageView().removeAll(); - FeedbackMessageWithCount message = messageList.get(item.getItemId()); - deviceToSend = message.fromUser; - nameToSend = message.fromUserName; - AsyncTask.execute(() -> { - List messageList2 = app.db.feedbackMessageDao().getAllByUserNow(deviceToSend); - runOnUiThread(() -> { - b.chatLayout.setVisibility(View.VISIBLE); - b.inputLayout.setVisibility(View.GONE); - for (FeedbackMessage message2 : messageList2) { - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(message2.sentTime); - Message chatMessage = new Message.Builder() - .setUser(message2.received ? new User(crc16(message2.fromUser.getBytes()), message2.fromUserName, BitmapFactory.decodeResource(getResources(), R.drawable.ic_account_circle)) : user) - .setRight(!message2.received) - .setText(message2.text) - .setSendTime(c) - .hideIcon(!message2.received) - .build(); - if (message2.received) - mChatView.receive(chatMessage); - else - mChatView.send(chatMessage); - } - }); - }); - return false; - }); - popupMenu.show(); - }); - }); - })); - } - else { - AsyncTask.execute(() -> { - List messageList = app.db.feedbackMessageDao().getAllNow(); - firstSend = messageList.size() == 0; - runOnUiThread(() -> { - if (firstSend) { - openFaq(); - b.chatLayout.setVisibility(View.GONE); - b.inputLayout.setVisibility(View.VISIBLE); - b.sendButton.setOnClickListener((v -> { - if (b.textInput.getText() == null || b.textInput.getText().length() == 0) { - Toast.makeText(app, "Podaj treść wiadomości.", Toast.LENGTH_SHORT).show(); - } else { - send(b.textInput.getText().toString()); - } - })); - } else { - /*new MaterialDialog.Builder(this) - .title(R.string.faq) - .content(R.string.faq_text) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive(((dialog, which) -> { - openFaq(); - })) - .show();*/ - b.chatLayout.setVisibility(View.VISIBLE); - b.inputLayout.setVisibility(View.GONE); - } - for (FeedbackMessage message : messageList) { - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(message.sentTime); - Message chatMessage = new Message.Builder() - .setUser(message.fromUser != null ? new User(crc16(message.fromUser.getBytes()), message.fromUserName, BitmapFactory.decodeResource(getResources(), R.drawable.ic_account_circle)) : message.received ? dev : user) - .setRight(!message.received) - .setText(message.text) - .setSendTime(c) - .hideIcon(!message.received) - .build(); - if (message.received) - mChatView.receive(chatMessage); - else - mChatView.send(chatMessage); - } - }); - }); - } - - //Click Send Button - mChatView.setOnClickSendButtonListener(view -> { - send(mChatView.getInputText()); - }); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) // Press Back Icon - { - finish(); - } - - return super.onOptionsItemSelected(item); - } - - @Override - protected void onResume() { - super.onResume(); - registerReceiver(receiver, new IntentFilter("pl.szczodrzynski.edziennik.activities.FeedbackActivity")); - } - - @Override - protected void onPause() { - super.onPause(); - unregisterReceiver(receiver); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/activities/WebPushConfigActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/activities/WebPushConfigActivity.java deleted file mode 100644 index 916416e0..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/activities/WebPushConfigActivity.java +++ /dev/null @@ -1,299 +0,0 @@ -package pl.szczodrzynski.edziennik.activities; - -import android.Manifest; -import android.content.pm.PackageManager; -import androidx.databinding.DataBindingUtil; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -import android.os.Handler; -import android.os.Looper; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TableRow; -import android.widget.TextView; -import android.widget.Toast; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.Result; - -import java.util.ArrayList; -import java.util.List; - -import me.dm7.barcodescanner.zxing.ZXingScannerView; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.databinding.ActivityWebPushConfigBinding; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.Anim; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.datamodels.Profile.REGISTRATION_ENABLED; - -public class WebPushConfigActivity extends AppCompatActivity implements ZXingScannerView.ResultHandler { - private static final String TAG = "WebPushConfigActivity"; - private ZXingScannerView mScannerView; - - ActivityWebPushConfigBinding b; - - boolean cameraRunning = false; - - private void showCamera() { - cameraRunning = true; - Anim.fadeIn(b.qrCodeScanner, 500, null); - b.webPushConfig.setVisibility(View.GONE); - b.qrCodeScanner.startCamera(); - b.qrCodeScanner.setResultHandler(this); - } - - private App app; - - private void hideCamera() { - cameraRunning = false; - Anim.fadeOut(b.qrCodeScanner, 500, null); - b.webPushConfig.setVisibility(View.VISIBLE); - b.qrCodeScanner.stopCamera(); - } - - private void getPairedBrowsers(@NonNull String newFcm, int removeId) { - Anim.fadeIn(b.browserListProgressBar, 500, null); - Anim.fadeOut(b.browserList, 500, null); - Anim.fadeOut(b.browserListErrorText, 500, null); - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?web_push_list"+(!newFcm.equals("") ? "&web_push_pair" : "") + (removeId != -1 ? "&web_push_unpair" : ""), "WebPushConfigActivity", app.profile) - .setBodyParameter((removeId != -1 ? "id" : "browser_fcm"), (removeId != -1 ? Integer.toString(removeId) : newFcm)) - .run(((e, result) -> { - new Handler(Looper.getMainLooper()).post(() -> { - Anim.fadeOut(b.browserListProgressBar, 500, null); - if (result == null || result.get("browser_count") == null) { - b.browserListErrorText.setText(R.string.web_push_connection_error); - Anim.fadeIn(b.browserListErrorText, 500, null); - return; - } - if (result.get("browser_count").getAsInt() == 0) { - b.browserListErrorText.setText(R.string.web_push_no_browsers); - Anim.fadeIn(b.browserListErrorText, 500, null); - if (app.appConfig.webPushEnabled) { - app.appConfig.webPushEnabled = false; - app.appConfig.savePending = true; - } - return; - } - - b.browserList.removeAllViews(); - - LinearLayout.LayoutParams textViewParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - textViewParams.setMargins(0, 0, Utils.dpToPx(8), 0); - LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - LinearLayout.LayoutParams tableRowParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - - JsonArray browsers = result.get("browsers").getAsJsonArray(); - for (JsonElement browserEl: browsers) { - JsonObject browser = browserEl.getAsJsonObject(); - if (browser != null) { - //Log.d(TAG, browser.toString()); - String browserDescription = "(error)"; - if (browser.get("description") != null) { - browserDescription = browser.get("description").getAsString(); - } - int browserId = -1; - if (browser.get("id") != null) { - browserId = browser.get("id").getAsInt(); - } - - TableRow browserRow = new TableRow(this); - browserRow.setLayoutParams(tableRowParams); - - TextView browserDescriptionText = new TextView(this); - //browserDescriptionText.setLayoutParams(textViewParams); - browserDescriptionText.setText(browserDescription); - browserDescriptionText.setGravity(Gravity.CENTER_VERTICAL); - browserRow.addView(browserDescriptionText); - - Button browserRemoveButton = new Button(this, null, android.R.attr.buttonStyleSmall); - browserRemoveButton.setMinHeight(0); - browserRemoveButton.setText(R.string.remove); - int finalBrowserId = browserId; - browserRemoveButton.setOnClickListener((v -> { - new MaterialDialog.Builder(this) - .title(R.string.are_you_sure) - .content(R.string.web_push_really_remove) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive(((dialog, which) -> getPairedBrowsers("", finalBrowserId))) - .show(); - })); - browserRow.addView(browserRemoveButton/*, buttonParams*/); - - b.browserList.addView(browserRow); - } - } - if (!app.appConfig.webPushEnabled) { - app.appConfig.webPushEnabled = true; - app.appConfig.savePending = true; - } - Anim.fadeIn(b.browserList, 500, null); - }); - })); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { - switch (requestCode) { - case 1: { - // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - showCamera(); - } else { - // permission denied, boo! Disable the - // functionality that depends on this permission. - Toast.makeText(this, R.string.no_permissions, Toast.LENGTH_SHORT).show(); - } - } - // other 'case' lines to check for other - // permissions this app might request - } - } - - @Override - public void onCreate(Bundle state) { - super.onCreate(state); - - app = (App) getApplicationContext(); - - getTheme().applyStyle(Themes.INSTANCE.getAppTheme(), true); - - b = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_web_push_config, null, false); - setContentView(b.getRoot()); - - Toolbar toolbar = b.toolbar; - toolbar.setTitle(R.string.settings_notification_web_push); - setSupportActionBar(toolbar); - ActionBar actionbar = getSupportActionBar(); - actionbar.setDisplayHomeAsUpEnabled(true); - - mScannerView = b.qrCodeScanner; - List formats = new ArrayList<>(); - formats.add(BarcodeFormat.QR_CODE); - mScannerView.setFormats(formats); - mScannerView.setAspectTolerance(0.5f); - - b.webPushScanNewButton.setOnClickListener((v -> { - int result = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); - if (result == PackageManager.PERMISSION_GRANTED) { - showCamera(); - } else { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1); - } - })); - - if (app.profile.getRegistration() != REGISTRATION_ENABLED) { - new MaterialDialog.Builder(this) - .title(R.string.web_push_unavailable) - .content(R.string.web_push_you_need_to_register) - .positiveText(R.string.ok) - .negativeText(R.string.what_is_this) - .onPositive(((dialog, which) -> { - dialog.dismiss(); - finish(); - })) - .onNegative(((dialog, which) -> { - new MaterialDialog.Builder(this) - .title(R.string.help) - .content(R.string.help_notification_web_push) - .positiveText(R.string.ok) - .show(); - })) - .dismissListener((dialog -> finish())) - .autoDismiss(false) - .canceledOnTouchOutside(false) - .show(); - b.webPushScanNewButton.setEnabled(false); - } - else { - getPairedBrowsers("", -1); - } - } - - @Override - public void onResume() { - super.onResume(); - // Register ourselves as a handler for scan results. - //mScannerView.startCamera(); // Start camera on resume - //showCamera(); - } - - @Override - public void onPause() { - super.onPause(); - hideCamera(); - } - - @Override - public void handleResult(Result rawResult) { - // Do something with the result here - //Log.v(TAG, rawResult.getText()); // Prints scan results - //Log.v(TAG, rawResult.getBarcodeFormat().toString()); // Prints the scan format (qrcode, pdf417 etc.) - - //Toast.makeText(this, rawResult.getText(), Toast.LENGTH_SHORT).show(); - - getPairedBrowsers(rawResult.getText(), -1); - - /*Ion.with(app.getContext()) - .load(app.requestScheme + APP_URL + "main.php?web_push_pair") - .setBodyParameter("username", (app.profile.autoRegistrationAllowed ? app.profile.registrationUsername : app.appConfig.deviceId)) - .setBodyParameter("app_version_build_type", BuildConfig.BUILD_TYPE) - .setBodyParameter("app_version_code", Integer.toString(BuildConfig.VERSION_CODE)) - .setBodyParameter("app_version", BuildConfig.VERSION_NAME + " " + BuildConfig.BUILD_TYPE + " (" + BuildConfig.VERSION_CODE + ")") - .setBodyParameter("device_id", Settings.Secure.getString(app.getContext().getContentResolver(), Settings.Secure.ANDROID_ID)) - .setBodyParameter("device_model", Build.MANUFACTURER+" "+Build.MODEL) - .setBodyParameter("device_os_version", Build.VERSION.RELEASE) - .setBodyParameter("fcm_token", app.appConfig.fcmToken) - .setBodyParameter("browser_fcm", rawResult.getText()) - .asJsonObject() - .setCallback((e, result) -> { - getPairedBrowsers(-1); - });*/ - - hideCamera(); - - // If you would like to resume scanning, call this method below: - //mScannerView.resumeCameraPreview(this); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - if (cameraRunning) { - hideCamera(); - return super.onOptionsItemSelected(item); - } - finish(); - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onBackPressed() { - if (cameraRunning) { - hideCamera(); - return; - } - super.onBackPressed(); - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/EventListAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/adapters/EventListAdapter.java deleted file mode 100644 index b22b2308..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/EventListAdapter.java +++ /dev/null @@ -1,135 +0,0 @@ -package pl.szczodrzynski.edziennik.adapters; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.mikepenz.iconics.view.IconicsImageView; -import com.mikepenz.iconics.view.IconicsTextView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.datamodels.Event; -import pl.szczodrzynski.edziennik.datamodels.EventFull; -import pl.szczodrzynski.edziennik.dialogs.EventListDialog; -import pl.szczodrzynski.edziennik.dialogs.EventManualDialog; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_HOMEWORK; -import static pl.szczodrzynski.edziennik.utils.Utils.bs; -import static pl.szczodrzynski.edziennik.utils.Utils.d; - -public class EventListAdapter extends RecyclerView.Adapter { - private static final String TAG = "EventListAdapter"; - private Context context; - private List examList; - private EventListDialog parentDialog; - - //getting the context and product list with constructor - public EventListAdapter(Context mCtx, List examList, EventListDialog parentDialog) { - this.context = mCtx; - this.examList = examList; - this.parentDialog = parentDialog; - } - - @NonNull - @Override - public EventListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_dialog_event_list_item, parent, false); - return new EventListAdapter.ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull EventListAdapter.ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - EventFull event = examList.get(position); - - if (event.type == TYPE_HOMEWORK) { - holder.eventListItemRoot.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.CLEAR)); - } - else { - holder.eventListItemRoot.getBackground().setColorFilter(new PorterDuffColorFilter(event.getColor(), PorterDuff.Mode.MULTIPLY)); - } - - holder.eventListItemStartTime.setText(event.startTime == null ? app.getString(R.string.event_all_day) : event.startTime.getStringHM()); - //holder.examListItemEndTime.setText(event.endTime.getStringHM()); - holder.eventListItemTeamName.setText(Utils.bs(event.teamName)); - holder.eventListItemTeacherName.setText(bs(null, event.teacherFullName, "\n") + bs(event.subjectLongName)); - holder.eventListItemAddedDate.setText(Date.fromMillis(event.addedDate).getFormattedStringShort()); - holder.eventListItemType.setText(event.typeName); - holder.eventListItemTopic.setText(event.topic); - holder.eventListItemEdit.setVisibility((event.addedManually ? View.VISIBLE : View.GONE)); - holder.eventListItemHomework.setVisibility((event.type == Event.TYPE_HOMEWORK ? View.VISIBLE : View.GONE)); - holder.eventListItemEdit.setOnClickListener(v -> { - if (parentDialog != null) { - parentDialog.callDismissListener = false; - parentDialog.dialog.dismiss(); - } - d(TAG, "Event "+event); - if (event.type == Event.TYPE_HOMEWORK) { - new EventManualDialog(context, event.profileId) - .withDismissListener(parentDialog::performDismiss) - .show(app, event, null, null, EventManualDialog.DIALOG_HOMEWORK); - } - else { - new EventManualDialog(context, event.profileId) - .withDismissListener(parentDialog::performDismiss) - .show(app, event, null, null, EventManualDialog.DIALOG_EVENT); - } - }); - - if (event.sharedBy == null) { - holder.eventListItemSharedBy.setVisibility(View.GONE); - } - else if (event.sharedByName != null) { - holder.eventListItemSharedBy.setText(app.getString(R.string.event_shared_by_format, (event.sharedBy.equals("self") ? app.getString(R.string.event_shared_by_self) : event.sharedByName))); - } - } - - @Override - public int getItemCount() { - return examList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - ConstraintLayout eventListItemRoot; - TextView eventListItemStartTime; - TextView eventListItemTeacherName; - TextView eventListItemType; - TextView eventListItemTopic; - TextView eventListItemAddedDate; - TextView eventListItemTeamName; - IconicsImageView eventListItemEdit; - IconicsImageView eventListItemHomework; - IconicsTextView eventListItemSharedBy; - - - ViewHolder(View itemView) { - super(itemView); - eventListItemRoot = itemView.findViewById(R.id.eventListItemRoot); - eventListItemStartTime = itemView.findViewById(R.id.eventListItemStartTime); - eventListItemTeacherName = itemView.findViewById(R.id.eventListItemTeacherName); - eventListItemType = itemView.findViewById(R.id.eventListItemType); - eventListItemTopic = itemView.findViewById(R.id.eventListItemTopic); - eventListItemAddedDate = itemView.findViewById(R.id.eventListItemAddedDate); - eventListItemTeamName = itemView.findViewById(R.id.eventListItemTeamName); - eventListItemEdit = itemView.findViewById(R.id.eventListItemEdit); - eventListItemHomework = itemView.findViewById(R.id.eventListItemHomework); - eventListItemSharedBy = itemView.findViewById(R.id.eventListItemSharedBy); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/GradesListAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/adapters/GradesListAdapter.java deleted file mode 100644 index 722aa823..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/GradesListAdapter.java +++ /dev/null @@ -1,144 +0,0 @@ -package pl.szczodrzynski.edziennik.adapters; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Typeface; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.core.graphics.ColorUtils; -import androidx.recyclerview.widget.RecyclerView; - -import java.text.DecimalFormat; -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.datamodels.Grade; -import pl.szczodrzynski.edziennik.datamodels.GradeFull; -import pl.szczodrzynski.edziennik.dialogs.GradeDetailsDialog; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.utils.Colors; - -import static pl.szczodrzynski.edziennik.datamodels.Profile.COLOR_MODE_DEFAULT; - -public class GradesListAdapter extends RecyclerView.Adapter { - private Context mContext; - private List gradeList; - - //getting the context and product list with constructor - public GradesListAdapter(Context mCtx, List gradeList) { - this.mContext = mCtx; - this.gradeList = gradeList; - } - - @NonNull - @Override - public GradesListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(mContext); - View view = inflater.inflate(R.layout.row_grades_list_item, parent, false); - return new GradesListAdapter.ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull GradesListAdapter.ViewHolder holder, int position) { - App app = (App) mContext.getApplicationContext(); - - GradeFull grade = gradeList.get(position); - - holder.root.setOnClickListener((v -> { - new GradeDetailsDialog(v.getContext(), App.profileId).show(app, grade); - })); - - int gradeColor; - if (app.profile.getGradeColorMode() == COLOR_MODE_DEFAULT) { - gradeColor = grade.color; - } - else { - gradeColor = Colors.gradeToColor(grade); - } - - holder.gradesListName.setText(grade.name); - holder.gradesListName.setSelected(true); - holder.gradesListName.setTypeface(null, Typeface.BOLD); - holder.gradesListName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff); - holder.gradesListName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)); - - if (grade.description.trim().isEmpty()) { - holder.gradesListDescription.setText(grade.category); - holder.gradesListCategory.setText(grade.isImprovement ? app.getString(R.string.grades_improvement_category_format, "") : ""); - } - else { - holder.gradesListDescription.setText(grade.description); - holder.gradesListCategory.setText(grade.isImprovement ? app.getString(R.string.grades_improvement_category_format, grade.category) : grade.category); - } - - DecimalFormat format = new DecimalFormat("#.##"); - DecimalFormat formatWithZeroes = new DecimalFormat("#.00"); - - if (grade.weight < 0) { - grade.weight *= -1; - } - if (grade.type == Grade.TYPE_DESCRIPTIVE || grade.type == Grade.TYPE_TEXT || grade.type == Grade.TYPE_BEHAVIOUR) { - holder.gradesListWeight.setVisibility(View.GONE); - grade.weight = 0; - } - else { - holder.gradesListWeight.setVisibility(View.VISIBLE); - if (grade.type == Grade.TYPE_POINT) { - holder.gradesListWeight.setText(app.getString(R.string.grades_max_points_format, format.format(grade.valueMax))); - } - else if (grade.weight == 0) { - holder.gradesListWeight.setText(app.getString(R.string.grades_weight_not_counted)); - } - else { - holder.gradesListWeight.setText(app.getString(R.string.grades_weight_format, format.format(grade.weight) + (grade.classAverage != -1 ? ", " + formatWithZeroes.format(grade.classAverage) : ""))); - } - } - - - holder.gradesListTeacher.setText(grade.teacherFullName); - holder.gradesListAddedDate.setText(Date.fromMillis(grade.addedDate).getFormattedStringShort()); - - if (!grade.seen) { - holder.gradesListDescription.setBackground(mContext.getResources().getDrawable(R.drawable.bg_rounded_4dp)); - holder.gradesListDescription.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - } - else { - holder.gradesListDescription.setBackground(null); - } - } - - @Override - public int getItemCount() { - return gradeList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - View root; - TextView gradesListName; - TextView gradesListDescription; - TextView gradesListCategory; - TextView gradesListWeight; - TextView gradesListTeacher; - TextView gradesListAddedDate; - - ViewHolder(View itemView) { - super(itemView); - root = itemView.getRootView(); - gradesListName = itemView.findViewById(R.id.gradesListName); - gradesListDescription = itemView.findViewById(R.id.gradesListCategoryColumn); - gradesListCategory = itemView.findViewById(R.id.gradesListCategoryDescription); - gradesListWeight = itemView.findViewById(R.id.gradesListWeight); - gradesListTeacher = itemView.findViewById(R.id.gradesListTeacher); - gradesListAddedDate = itemView.findViewById(R.id.gradesListAddedDate); - } - } -} - diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/GradesSubjectAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/adapters/GradesSubjectAdapter.java deleted file mode 100644 index 0038612c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/GradesSubjectAdapter.java +++ /dev/null @@ -1,784 +0,0 @@ -package pl.szczodrzynski.edziennik.adapters; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Typeface; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.widget.ArrayAdapter; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.graphics.ColorUtils; -import androidx.core.widget.NestedScrollView; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.mikepenz.iconics.view.IconicsImageView; - -import java.text.DecimalFormat; -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.MainActivity; -import pl.szczodrzynski.edziennik.datamodels.AppDb; -import pl.szczodrzynski.edziennik.datamodels.GradeFull; -import pl.szczodrzynski.edziennik.datamodels.Subject; -import pl.szczodrzynski.edziennik.models.ItemGradesSubjectModel; -import pl.szczodrzynski.edziennik.utils.Anim; -import pl.szczodrzynski.edziennik.utils.Colors; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static pl.szczodrzynski.edziennik.MainActivity.TARGET_GRADES_EDITOR; -import static pl.szczodrzynski.edziennik.datamodels.Profile.COLOR_MODE_DEFAULT; -import static pl.szczodrzynski.edziennik.datamodels.Profile.YEAR_1_AVG_2_SEM; -import static pl.szczodrzynski.edziennik.datamodels.Profile.YEAR_1_SEM_2_AVG; -import static pl.szczodrzynski.edziennik.datamodels.Profile.YEAR_1_SEM_2_SEM; - -public class GradesSubjectAdapter extends ArrayAdapter implements View.OnClickListener { - private static final String TAG = "GradesSubjectAdapter"; - private MainActivity activity; - public List subjectList; - - private void updateBadges(Context context, ItemGradesSubjectModel model) { - // do not need this since we have an Observer for unread counters.. - //((App)getContext().getApplicationContext()).saveRegister(); // I don't like this. - } - - private boolean invalidAvg(float avg) { - return avg == 0.0f || Float.isNaN(avg); - } - - private void updateSubjectSemesterBadges(ViewHolder holder, ItemGradesSubjectModel model) { - if (model.semester1Unread > 0 || model.semester2Unread > 0) { - holder.gradesSubjectTitle.setBackground(getContext().getResources().getDrawable(R.drawable.bg_rounded_4dp)); - holder.gradesSubjectTitle.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - } - else { - holder.gradesSubjectTitle.setBackground(null); - } - if (model.semester1Unread > 0) { - holder.gradesSubjectSemester1Title.setBackground(getContext().getResources().getDrawable(R.drawable.bg_rounded_4dp)); - holder.gradesSubjectSemester1Title.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - } - else { - holder.gradesSubjectSemester1Title.setBackground(null); - } - if (model.semester2Unread > 0) { - holder.gradesSubjectSemester2Title.setBackground(getContext().getResources().getDrawable(R.drawable.bg_rounded_4dp)); - holder.gradesSubjectSemester2Title.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - } - else { - holder.gradesSubjectSemester2Title.setBackground(null); - } - } - - private boolean gradesSetAsRead(ViewHolder holder, ItemGradesSubjectModel model, int semester) { - boolean somethingChanged = false; - AppDb db = AppDb.getDatabase(null); - if (semester == 1) { - model.semester1Unread = 0; - for (GradeFull grade : model.grades1) { - if (!grade.seen) { - db.metadataDao().setSeen(App.profileId, grade, somethingChanged = true); - } - } - if (model.semester1Proposed != null && !model.semester1Proposed.seen) - db.metadataDao().setSeen(App.profileId, model.semester1Proposed, somethingChanged = true); - if (model.semester1Final != null && !model.semester1Final.seen) - db.metadataDao().setSeen(App.profileId, model.semester1Final, somethingChanged = true); - } - else if (semester == 2) { - model.semester2Unread = 0; - for (GradeFull grade : model.grades2) { - if (!grade.seen) { - db.metadataDao().setSeen(App.profileId, grade, somethingChanged = true); - } - } - if (model.semester2Proposed != null && !model.semester2Proposed.seen) - db.metadataDao().setSeen(App.profileId, model.semester2Proposed, somethingChanged = true); - if (model.semester2Final != null && !model.semester2Final.seen) - db.metadataDao().setSeen(App.profileId, model.semester2Final, somethingChanged = true); - if (model.yearProposed != null && !model.yearProposed.seen) - db.metadataDao().setSeen(App.profileId, model.yearProposed, somethingChanged = true); - if (model.yearFinal != null && !model.yearFinal.seen) - db.metadataDao().setSeen(App.profileId, model.yearFinal, somethingChanged = true); - } - if (somethingChanged) updateSubjectSemesterBadges(holder, model); - return somethingChanged; - } - - private void expandSubject(ViewHolder holder, ItemGradesSubjectModel model) { - Anim.fadeOut(holder.gradesSubjectPreviewContainer, 200, new Animation.AnimationListener() { - @Override public void onAnimationStart(Animation animation) { } - @Override public void onAnimationRepeat(Animation animation) { } - @Override - public void onAnimationEnd(Animation animation) { - boolean somethingChanged = false; - if (holder.gradesSubjectSemester1Container.getVisibility() != View.GONE) { - somethingChanged = gradesSetAsRead(holder, model, 1); - } - if (holder.gradesSubjectSemester2Container.getVisibility() != View.GONE) { - somethingChanged = gradesSetAsRead(holder, model, 2); - } - if (somethingChanged) updateBadges(getContext(), model); - //holder.gradesSubjectPreviewContent.setVisibility(View.INVISIBLE); - Anim.expand(holder.gradesSubjectContent, 500, null); - } - }); - } - - private void collapseSubject(ViewHolder holder, ItemGradesSubjectModel model) { - Anim.collapse(holder.gradesSubjectContent, 500, new Animation.AnimationListener() { - @Override public void onAnimationStart(Animation animation) { } - @Override public void onAnimationRepeat(Animation animation) { } - @Override - public void onAnimationEnd(Animation animation) { - //holder.gradesSubjectPreviewContent.setVisibility(View.VISIBLE); - Anim.fadeIn(holder.gradesSubjectPreviewContainer, 200, null); - } - }); - } - - class BuildGradeViews extends AsyncTask { - boolean findViews; - ItemGradesSubjectModel model; - //ViewGroup parent; - //int position; - ViewHolder holder; - - BuildGradeViews(ItemGradesSubjectModel model, ViewHolder holder, ViewGroup parent, int position, boolean findViews) { - this.model = model; - this.holder = holder; - this.findViews = findViews; - //this.parent = parent; - //this.position = position; - } - - protected Void doInBackground(Void... params) { - if (this.findViews) { - findViews(holder, holder.gradesSubjectRoot); - } - return null; - } - - protected void onPostExecute(Void aVoid) { - DecimalFormat df = new DecimalFormat("#.00"); - if (this.findViews) { - // TODO NIE WIEM CO TO ROBI XD - //this.viewHolder.semestrTitle1.setText(C0193R.string.semestr1); - //this.viewHolder.semestrTitle2.setText(C0193R.string.semestr2); - /*if (GradesSubjectAdapter.this.columns == 0) { - DisplayMetrics metrics = GradeListAdapterBySubject.this.context.getResources().getDisplayMetrics(); - if (GradeListAdapterBySubject.this.context.getResources().getBoolean(C0193R.bool.tablet)) { - GradeListAdapterBySubject.this.columns = ((int) ((((float) (this.parent.getWidth() / 2)) / metrics.density) - 30.0f)) / 58; - } else { - GradeListAdapterBySubject.this.columns = ((int) ((((float) this.parent.getWidth()) / metrics.density) - 30.0f)) / 58; - } - } - this.viewHolder.semestrGridView1.setColumnCount(GradeListAdapterBySubject.this.columns); - this.viewHolder.semestrGridView2.setColumnCount(GradeListAdapterBySubject.this.columns);*/ - } - if (model != null && model.subject != null && model.subject.id != holder.lastSubject) { - holder.gradesSubjectRoot.setBackground(Colors.getAdaptiveBackgroundDrawable(model.subject.color, model.subject.color)); - updateSubjectSemesterBadges(holder, model); - holder.gradesSubjectTitle.setText(model.subject.longName); - holder.gradesSubjectPreviewContent.removeAllViews(); - - if (model.expandView) { - // commented is without animations, do not use now (with unread badges) - //holder.gradesSubjectContent.setVisibility(View.VISIBLE); - //holder.gradesSubjectPreviewContent.setVisibility(View.INVISIBLE); - expandSubject(holder, model); - model.expandView = false; - } - int showSemester = model.profile.getCurrentSemester(); - - List gradeList = (showSemester == 1 ? model.grades1 : model.grades2); - if (gradeList.size() == 0) { - showSemester = (showSemester == 1 ? 2 : 1); - gradeList = (showSemester == 1 ? model.grades1 : model.grades2); - } - - App app = (App) getContext().getApplicationContext(); - - float scale = getContext().getResources().getDisplayMetrics().density; - int _5dp = (int) (5 * scale + 0.5f); - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT); - layoutParams.setMargins(0, 0, _5dp, 0); - - DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); - int maxWidthPx = displayMetrics.widthPixels - Utils.dpToPx((app.appConfig.miniDrawerVisible ? 72 : 0)/*miniDrawer size*/ + 8 + 8/*left and right offsets*/ + 24/*ellipsize width*/); - int totalWidthPx = 0; - boolean ellipsized = false; - - if (showSemester != model.profile.getCurrentSemester()) { - // showing different semester, because of no grades in the selected one - holder.gradesSubjectPreviewSemester.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewSemester.setText(getContext().getString(R.string.grades_semester_header_format, showSemester)); - // decrease the max preview width. DONE below - /*holder.gradesSubjectPreviewSemester.measure(WRAP_CONTENT, WRAP_CONTENT); - maxWidthPx -= holder.gradesSubjectPreviewSemester.getMeasuredWidth(); - maxWidthPx -= _5dp;*/ - } - else { - holder.gradesSubjectPreviewSemester.setVisibility(View.GONE); - } - - - if (model.grades1.size() > 0) { - holder.gradesSubjectSemester1Nest.setNestedScrollingEnabled(false); - holder.gradesSubjectSemester1Content.setHasFixedSize(false); - holder.gradesSubjectSemester1Content.setNestedScrollingEnabled(false); - holder.gradesSubjectSemester1Content.setLayoutManager(new LinearLayoutManager(activity)); - holder.gradesSubjectSemester1Content.setAdapter(new GradesListAdapter(activity, model.grades1)); - holder.gradesSubjectSemester1Header.setVisibility(View.VISIBLE); - if (showSemester == 1) { - holder.gradesSubjectSemester1Container.setVisibility(View.VISIBLE); - } - else { - holder.gradesSubjectSemester1Container.setVisibility(View.GONE); - } - - if (model.isDescriptiveSubject && !model.isNormalSubject && !model.isPointSubject && !model.isBehaviourSubject) { - holder.gradesSubjectPreviewAverage.setVisibility(View.GONE); - holder.gradesSubjectSemester1Average.setVisibility(View.GONE); - } - else { - holder.gradesSubjectPreviewAverage.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester1Average.setVisibility(View.VISIBLE); - int formatSingle = (model.isPointSubject ? R.string.grades_average_single_percent_format : model.isBehaviourSubject ? R.string.grades_average_single_point_format : R.string.grades_average_single_format); - int format = (model.isPointSubject ? R.string.grades_semester_average_percent_format : model.isBehaviourSubject ? R.string.grades_semester_average_point_format : R.string.grades_semester_average_format); - // PREVIEW AVERAGE - if (showSemester == 1) { - holder.gradesSubjectPreviewAverage.setText( - getContext().getString( - formatSingle, - invalidAvg(model.semester1Average) ? "-" : df.format(model.semester1Average) - ) - ); - } - // AVERAGE value - holder.gradesSubjectSemester1Average.setText( - getContext().getString( - format, - 1, - invalidAvg(model.semester1Average) ? "-" : df.format(model.semester1Average) - ) - ); - } - - // PROPOSED grade - if (model.semester1Proposed != null) { - holder.gradesSubjectSemester1Proposed.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester1Proposed.setText(model.semester1Proposed.name); - holder.gradesSubjectSemester1Proposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Proposed.name), PorterDuff.Mode.MULTIPLY)); - holder.gradesSubjectSemester1Proposed.setTextColor(Colors.gradeNameToColor(model.semester1Proposed.name)); - if (showSemester == 1) { - holder.gradesSubjectPreviewProposed.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewProposed.setText(model.semester1Proposed.name); - holder.gradesSubjectPreviewProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Proposed.name), PorterDuff.Mode.MULTIPLY)); - holder.gradesSubjectPreviewProposed.setTextColor(Colors.gradeNameToColor(model.semester1Proposed.name)); - } - } - else { - holder.gradesSubjectSemester1Proposed.setVisibility(View.GONE); - if (showSemester == 1) { - holder.gradesSubjectPreviewProposed.setVisibility(View.GONE); - } - } - // FINAL grade - if (model.semester1Final != null) { - holder.gradesSubjectSemester1Final.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester1Final.setText(model.semester1Final.name); - holder.gradesSubjectSemester1Final.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Final.name), PorterDuff.Mode.MULTIPLY)); - if (showSemester == 1) { - holder.gradesSubjectPreviewFinal.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewFinal.setText(model.semester1Final.name); - holder.gradesSubjectPreviewFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester1Final.name), PorterDuff.Mode.MULTIPLY)); - } - } - else { - holder.gradesSubjectSemester1Final.setVisibility(View.GONE); - if (showSemester == 1) { - holder.gradesSubjectPreviewFinal.setVisibility(View.GONE); - } - } - } - else { - holder.gradesSubjectSemester1Header.setVisibility(View.GONE); - holder.gradesSubjectSemester1Container.setVisibility(View.GONE); - } - - if (model.grades2.size() > 0) { - holder.gradesSubjectSemester2Nest.setNestedScrollingEnabled(false); - holder.gradesSubjectSemester2Content.setHasFixedSize(false); - holder.gradesSubjectSemester2Content.setNestedScrollingEnabled(false); - holder.gradesSubjectSemester2Content.setLayoutManager(new LinearLayoutManager(activity)); - holder.gradesSubjectSemester2Content.setAdapter(new GradesListAdapter(activity, model.grades2)); - holder.gradesSubjectSemester2Header.setVisibility(View.VISIBLE); - if (showSemester == 2) { - holder.gradesSubjectSemester2Container.setVisibility(View.VISIBLE); - } - else { - holder.gradesSubjectSemester2Container.setVisibility(View.GONE); - } - - if (model.isDescriptiveSubject && !model.isNormalSubject && !model.isPointSubject && !model.isBehaviourSubject) { - holder.gradesSubjectPreviewAverage.setVisibility(View.GONE); - holder.gradesSubjectSemester2Average.setVisibility(View.GONE); - } - else { - holder.gradesSubjectPreviewAverage.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester2Average.setVisibility(View.VISIBLE); - // PREVIEW AVERAGE - int formatDouble = (model.isPointSubject ? R.string.grades_average_double_percent_format : model.isBehaviourSubject ? R.string.grades_average_double_point_format : R.string.grades_average_double_format); - int format = (model.isPointSubject ? R.string.grades_semester_average_percent_format : model.isBehaviourSubject ? R.string.grades_semester_average_point_format : R.string.grades_semester_average_format); - if (showSemester == 2) { - if (model.semester2Proposed != null || model.semester2Final != null) { - holder.gradesSubjectPreviewAverage.setText(invalidAvg(model.semester2Average) ? "-" : df.format(model.semester2Average)); - holder.gradesSubjectPreviewYearAverage.setText(invalidAvg(model.yearAverage) ? "-" : df.format(model.yearAverage)); - holder.gradesSubjectPreviewYearAverage.setVisibility(View.VISIBLE); - } - else { - holder.gradesSubjectPreviewAverage.setText( - getContext().getString( - formatDouble, - invalidAvg(model.semester2Average) ? "-" : df.format(model.semester2Average), - invalidAvg(model.yearAverage) ? "-" : df.format(model.yearAverage) - ) - ); - holder.gradesSubjectPreviewYearAverage.setVisibility(View.GONE); - } - } - // AVERAGE value - holder.gradesSubjectSemester2Average.setText( - getContext().getString( - format, - 2, - invalidAvg(model.semester2Average) ? "-" : df.format(model.semester2Average) - ) - ); - } - - // PROPOSED grade - if (model.semester2Proposed != null) { - holder.gradesSubjectSemester2Proposed.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester2Proposed.setText(model.semester2Proposed.name); - holder.gradesSubjectSemester2Proposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Proposed.name), PorterDuff.Mode.MULTIPLY)); - holder.gradesSubjectSemester2Proposed.setTextColor(Colors.gradeNameToColor(model.semester2Proposed.name)); - if (showSemester == 2) { - holder.gradesSubjectPreviewProposed.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewProposed.setText(model.semester2Proposed.name); - holder.gradesSubjectPreviewProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Proposed.name), PorterDuff.Mode.MULTIPLY)); - holder.gradesSubjectPreviewProposed.setTextColor(Colors.gradeNameToColor(model.semester2Proposed.name)); - } - } - else { - holder.gradesSubjectSemester2Proposed.setVisibility(View.GONE); - if (showSemester == 2) { - holder.gradesSubjectPreviewProposed.setVisibility(View.GONE); - } - } - // FINAL grade - if (model.semester2Final != null) { - holder.gradesSubjectSemester2Final.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester2Final.setText(model.semester2Final.name); - holder.gradesSubjectSemester2Final.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Final.name), PorterDuff.Mode.MULTIPLY)); - if (showSemester == 2) { - holder.gradesSubjectPreviewFinal.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewFinal.setText(model.semester2Final.name); - holder.gradesSubjectPreviewFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.semester2Final.name), PorterDuff.Mode.MULTIPLY)); - } - } - else { - holder.gradesSubjectSemester2Final.setVisibility(View.GONE); - if (showSemester == 2) { - holder.gradesSubjectPreviewFinal.setVisibility(View.GONE); - } - } - - if (model.isDescriptiveSubject && !model.isNormalSubject && !model.isPointSubject && !model.isBehaviourSubject) { - holder.gradesSubjectYearAverage.setVisibility(View.GONE); - } - else { - holder.gradesSubjectYearAverage.setVisibility(View.VISIBLE); - // AVERAGE value - int format = (model.isPointSubject ? R.string.grades_year_average_percent_format : model.isBehaviourSubject ? R.string.grades_year_average_point_format : R.string.grades_year_average_format); - holder.gradesSubjectYearAverage.setText( - getContext().getString( - format, - invalidAvg(model.yearAverage) ? "-" : df.format(model.yearAverage) - ) - ); - } - - // PROPOSED grade - if (model.yearProposed != null) { - holder.gradesSubjectYearProposed.setVisibility(View.VISIBLE); - holder.gradesSubjectYearProposed.setText(model.yearProposed.name); - holder.gradesSubjectYearProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearProposed.name), PorterDuff.Mode.MULTIPLY)); - holder.gradesSubjectYearProposed.setTextColor(Colors.gradeNameToColor(model.yearProposed.name)); - if (showSemester == 2) { - holder.gradesSubjectPreviewYearProposed.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewYearProposed.setText(model.yearProposed.name); - holder.gradesSubjectPreviewYearProposed.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearProposed.name), PorterDuff.Mode.MULTIPLY)); - holder.gradesSubjectPreviewYearProposed.setTextColor(Colors.gradeNameToColor(model.yearProposed.name)); - } - } - else { - holder.gradesSubjectYearProposed.setVisibility(View.GONE); - if (showSemester == 2) { - holder.gradesSubjectPreviewYearProposed.setVisibility(View.GONE); - } - } - // FINAL grade - if (model.yearFinal != null) { - holder.gradesSubjectYearFinal.setVisibility(View.VISIBLE); - holder.gradesSubjectYearFinal.setText(model.yearFinal.name); - holder.gradesSubjectYearFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearFinal.name), PorterDuff.Mode.MULTIPLY)); - if (showSemester == 2) { - holder.gradesSubjectPreviewYearFinal.setVisibility(View.VISIBLE); - holder.gradesSubjectPreviewYearFinal.setText(model.yearFinal.name); - holder.gradesSubjectPreviewYearFinal.getBackground().setColorFilter(new PorterDuffColorFilter(Colors.gradeNameToColor(model.yearFinal.name), PorterDuff.Mode.MULTIPLY)); - } - } - else { - holder.gradesSubjectYearFinal.setVisibility(View.GONE); - if (showSemester == 2) { - holder.gradesSubjectPreviewYearFinal.setVisibility(View.GONE); - } - } - } - else { - holder.gradesSubjectSemester2Header.setVisibility(View.GONE); - holder.gradesSubjectSemester2Container.setVisibility(View.GONE); - } - - // decrease the width by average, proposed, final and semester TextViews - holder.gradesSubjectPreviewContainer.measure(WRAP_CONTENT, MATCH_PARENT); - //Log.d(TAG, "gradesSubjectPreviewContainer "+holder.gradesSubjectPreviewContainer.getMeasuredWidth()); - - /*holder.gradesSubjectPreviewAverage.measure(WRAP_CONTENT, WRAP_CONTENT); - maxWidthPx -= holder.gradesSubjectPreviewAverage.getMeasuredWidth(); - maxWidthPx -= 2*_5dp; - if (holder.gradesSubjectPreviewProposed.getVisibility() == View.VISIBLE) { - holder.gradesSubjectPreviewProposed.measure(WRAP_CONTENT, WRAP_CONTENT); - maxWidthPx -= holder.gradesSubjectPreviewProposed.getMeasuredWidth(); - maxWidthPx -= _5dp; - } - if (holder.gradesSubjectPreviewFinal.getVisibility() == View.VISIBLE) { - holder.gradesSubjectPreviewFinal.measure(WRAP_CONTENT, WRAP_CONTENT); - maxWidthPx -= holder.gradesSubjectPreviewFinal.getMeasuredWidth(); - maxWidthPx -= _5dp; - }*/ - maxWidthPx -= holder.gradesSubjectPreviewContainer.getMeasuredWidth(); - maxWidthPx -= _5dp; - - for (GradeFull grade: gradeList) { - if (grade.semester != showSemester) - continue; - - int gradeColor; - if (model.profile.getGradeColorMode() == COLOR_MODE_DEFAULT) { - gradeColor = grade.color; - } else { - gradeColor = Colors.gradeToColor(grade); - } - - TextView gradeName = new TextView(activity); - gradeName.setText(grade.name); - gradeName.setTextColor(ColorUtils.calculateLuminance(gradeColor) > 0.25 ? 0xff000000 : 0xffffffff); - gradeName.setPadding(_5dp, 0, _5dp, 0); - gradeName.setBackgroundResource(R.drawable.bg_rounded_4dp); - gradeName.getBackground().setColorFilter(new PorterDuffColorFilter(gradeColor, PorterDuff.Mode.MULTIPLY)); - gradeName.setTypeface(null, Typeface.BOLD); - - gradeName.measure(WRAP_CONTENT, WRAP_CONTENT); - totalWidthPx += gradeName.getMeasuredWidth() + _5dp; - //Log.d(TAG, "totalWidthPx " + totalWidthPx); - if (totalWidthPx >= maxWidthPx) { - if (ellipsized) - continue; - ellipsized = true; - TextView ellipsisText = new TextView(activity); - ellipsisText.setText(R.string.ellipsis); - ellipsisText.setTextAppearance(activity, R.style.NavView_TextView); - ellipsisText.setTypeface(null, Typeface.BOLD); - ellipsisText.setPadding(0, 0, 0, 0); - holder.gradesSubjectPreviewContent.addView(ellipsisText, layoutParams); - } - else { - holder.gradesSubjectPreviewContent.addView(gradeName, layoutParams); - } - } - } - if (findViews) { - //Log.d("GradesSubjectAdapter", "runOnUiThread"); - //this.viewHolder.gradesSubjectContent.setTag(View.VISIBLE); - //this.viewHolder.gradesSubjectPreviewContainer.setTag(View.VISIBLE); - activity.runOnUiThread(() -> { - - holder.gradesSubjectRoot.setOnClickListener(v -> { - if (holder.gradesSubjectContent.getVisibility() == View.GONE) { - expandSubject(holder, model); - } - else { - collapseSubject(holder, model); - } - }); - - holder.gradesSubjectSemester1Header.setOnClickListener(v -> { - if (holder.gradesSubjectSemester1Container.getVisibility() == View.GONE) { - if (gradesSetAsRead(holder, model, 1)) updateBadges(getContext(), model); - - Anim.expand(holder.gradesSubjectSemester1Container, 500, null); - if (holder.gradesSubjectSemester2Container.getVisibility() != View.GONE) { - Anim.collapse(holder.gradesSubjectSemester2Container, 500, null); - } - } - else { - Anim.collapse(holder.gradesSubjectSemester1Container, 500, null); - } - }); - holder.gradesSubjectSemester2Header.setOnClickListener(v -> { - if (holder.gradesSubjectSemester2Container.getVisibility() == View.GONE) { - if (gradesSetAsRead(holder, model, 2)) updateBadges(getContext(), model); - - Anim.expand(holder.gradesSubjectSemester2Container, 500, null); - if (holder.gradesSubjectSemester1Container.getVisibility() != View.GONE) { - Anim.collapse(holder.gradesSubjectSemester1Container, 500, null); - } - } - else { - Anim.collapse(holder.gradesSubjectSemester2Container, 500, null); - } - }); - - // hide the grade simulator when there are point, behaviour or descriptive grades - if (model.isPointSubject || model.isBehaviourSubject || (model.isDescriptiveSubject && !model.isNormalSubject)) { - holder.gradesSubjectSemester1EditButton.setVisibility(View.GONE); - holder.gradesSubjectSemester2EditButton.setVisibility(View.GONE); - } - else { - holder.gradesSubjectSemester1EditButton.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester1EditButton.setOnClickListener(v -> { - Bundle arguments = new Bundle(); - - if (model.subject != null) { - arguments.putLong("subjectId", model.subject.id); - } - arguments.putInt("semester", 1); - //d(TAG, "Model is " + model); - switch (model.profile.getYearAverageMode()) { - case YEAR_1_SEM_2_AVG: - case YEAR_1_SEM_2_SEM: - arguments.putInt("averageMode", -1); - break; - default: - arguments.putInt("averageMode", model.semester2Final == null && model.profile.getYearAverageMode() == YEAR_1_AVG_2_SEM ? -1 : model.profile.getYearAverageMode()); - arguments.putFloat("yearAverageBefore", model.yearAverage); - arguments.putFloat("gradeSumOtherSemester", model.gradeSumSemester2); - arguments.putFloat("gradeCountOtherSemester", model.gradeCountSemester2); - arguments.putFloat("averageOtherSemester", model.semester2Average); - arguments.putFloat("finalOtherSemester", model.semester2Final == null ? -1 : model.semester2Final.value); - break; - } - - activity.loadTarget(TARGET_GRADES_EDITOR, arguments); - }); - holder.gradesSubjectSemester2EditButton.setVisibility(View.VISIBLE); - holder.gradesSubjectSemester2EditButton.setOnClickListener(v -> { - Bundle arguments = new Bundle(); - - if (model.subject != null) { - arguments.putLong("subjectId", model.subject.id); - } - arguments.putInt("semester", 2); - //d(TAG, "Model is " + model); - switch (model.profile.getYearAverageMode()) { - case YEAR_1_AVG_2_SEM: - case YEAR_1_SEM_2_SEM: - arguments.putInt("averageMode", -1); - break; - default: - arguments.putInt("averageMode", model.semester1Final == null && model.profile.getYearAverageMode() == YEAR_1_SEM_2_AVG ? -1 : model.profile.getYearAverageMode()); - arguments.putFloat("yearAverageBefore", model.yearAverage); - arguments.putFloat("gradeSumOtherSemester", model.gradeSumSemester1); - arguments.putFloat("gradeCountOtherSemester", model.gradeCountSemester1); - arguments.putFloat("averageOtherSemester", model.semester1Average); - arguments.putFloat("finalOtherSemester", model.semester1Final == null ? -1 : model.semester1Final.value); - break; - } - - activity.loadTarget(TARGET_GRADES_EDITOR, arguments); - }); - } - - }); - } - if (model != null && model.subject != null) { - holder.lastSubject = model.subject.id; - } - super.onPostExecute(aVoid); - } - } - - //getting the context and product list with constructor - public GradesSubjectAdapter(List data, MainActivity context) { - super(context, R.layout.row_grades_subject_item, data); - this.activity = context; - this.subjectList = data; - } - - @Override - public void onClick(View v) { - int position = (Integer) v.getTag(); - Object object = getItem(position); - ItemGradesSubjectModel dataModel = (ItemGradesSubjectModel)object; - - } - - private void findViews(ViewHolder holder, View root) { - //holder.gradesSubjectRoot = root.findViewById(R.id.gradesSubjectRoot); - holder.gradesSubjectTitle = root.findViewById(R.id.gradesSubjectTitle); - holder.gradesSubjectExpandIndicator = root.findViewById(R.id.gradesSubjectExpandIndicator); - - holder.gradesSubjectPreviewContainer = root.findViewById(R.id.gradesSubjectPreviewContainer); - holder.gradesSubjectPreviewSemester = root.findViewById(R.id.gradesSubjectPreviewSemester); - holder.gradesSubjectPreviewContent = root.findViewById(R.id.gradesSubjectPreviewContent); - holder.gradesSubjectPreviewAverage = root.findViewById(R.id.gradesSubjectPreviewAverage); - holder.gradesSubjectPreviewProposed = root.findViewById(R.id.gradesSubjectPreviewProposed); - holder.gradesSubjectPreviewFinal = root.findViewById(R.id.gradesSubjectPreviewFinal); - holder.gradesSubjectPreviewYearAverage = root.findViewById(R.id.gradesSubjectPreviewYearAverage); - holder.gradesSubjectPreviewYearProposed = root.findViewById(R.id.gradesSubjectPreviewYearProposed); - holder.gradesSubjectPreviewYearFinal = root.findViewById(R.id.gradesSubjectPreviewYearFinal); - - holder.gradesSubjectContent = root.findViewById(R.id.gradesSubjectContent); - - holder.gradesSubjectSemester1Header = root.findViewById(R.id.gradesSubjectSemester1Header); - holder.gradesSubjectSemester1Title = root.findViewById(R.id.gradesSubjectSemester1Title); - holder.gradesSubjectSemester1ExpandIndicator = root.findViewById(R.id.gradesSubjectSemester1ExpandIndicator); - holder.gradesSubjectSemester1Average = root.findViewById(R.id.gradesSubjectSemester1Average); - holder.gradesSubjectSemester1Proposed = root.findViewById(R.id.gradesSubjectSemester1Proposed); - holder.gradesSubjectSemester1Final = root.findViewById(R.id.gradesSubjectSemester1Final); - holder.gradesSubjectSemester1EditButton = root.findViewById(R.id.gradesSubjectSemester1EditButton); - holder.gradesSubjectSemester1Container = root.findViewById(R.id.gradesSubjectSemester1Container); - holder.gradesSubjectSemester1Nest = root.findViewById(R.id.gradesSubjectSemester1Nest); - holder.gradesSubjectSemester1Content = root.findViewById(R.id.gradesSubjectSemester1Content); - - holder.gradesSubjectSemester2Header = root.findViewById(R.id.gradesSubjectSemester2Header); - holder.gradesSubjectSemester2Title = root.findViewById(R.id.gradesSubjectSemester2Title); - holder.gradesSubjectSemester2ExpandIndicator = root.findViewById(R.id.gradesSubjectSemester2ExpandIndicator); - holder.gradesSubjectSemester2Average = root.findViewById(R.id.gradesSubjectSemester2Average); - holder.gradesSubjectSemester2Proposed = root.findViewById(R.id.gradesSubjectSemester2Proposed); - holder.gradesSubjectSemester2Final = root.findViewById(R.id.gradesSubjectSemester2Final); - holder.gradesSubjectSemester2EditButton = root.findViewById(R.id.gradesSubjectSemester2EditButton); - holder.gradesSubjectSemester2Container = root.findViewById(R.id.gradesSubjectSemester2Container); - holder.gradesSubjectSemester2Nest = root.findViewById(R.id.gradesSubjectSemester2Nest); - holder.gradesSubjectSemester2Content = root.findViewById(R.id.gradesSubjectSemester2Content); - - holder.gradesSubjectYearAverage = root.findViewById(R.id.gradesSubjectYearAverage); - holder.gradesSubjectYearProposed = root.findViewById(R.id.gradesSubjectYearProposed); - holder.gradesSubjectYearFinal = root.findViewById(R.id.gradesSubjectYearFinal); - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - ItemGradesSubjectModel model = subjectList.get(position); - if (model == null) { - //Toast.makeText(activity, "return convertView;", Toast.LENGTH_SHORT).show(); - return convertView; - } - ViewHolder holder; - if (convertView == null) { - try { - convertView = LayoutInflater.from(activity).inflate(R.layout.row_grades_subject_item, parent, false); - holder = new ViewHolder(); - holder.gradesSubjectRoot = convertView.findViewById(R.id.gradesSubjectRoot); - convertView.setTag(holder); - new BuildGradeViews(model, holder, parent, position, true).execute(); - return convertView; - } catch (Exception e) { - return new View(getContext()); - } - } - holder = (ViewHolder) convertView.getTag(); - Subject subject = model.subject; - holder.gradesSubjectTitle.setText(subject != null ? subject.longName : ""); - /*if (model.getNotSeen() > 0) { - viewHolder.notseen.setVisibility(0); - viewHolder.notseen.setText(model.getNotSeen() + ""); - } else { - viewHolder.notseen.setVisibility(8); - }*/ - new BuildGradeViews(model, holder, parent, position, false).execute(); - return convertView; - } - - public int getViewTypeCount() { - return getCount(); - } - - public int getItemViewType(int position) { - return position; - } - - class ViewHolder { - long lastSubject; - - TextView gradesSubjectTitle; - ConstraintLayout gradesSubjectRoot; - IconicsImageView gradesSubjectExpandIndicator; - - LinearLayout gradesSubjectPreviewContainer; - TextView gradesSubjectPreviewSemester; - LinearLayout gradesSubjectPreviewContent; - TextView gradesSubjectPreviewAverage; - TextView gradesSubjectPreviewProposed; - TextView gradesSubjectPreviewFinal; - TextView gradesSubjectPreviewYearAverage; - TextView gradesSubjectPreviewYearProposed; - TextView gradesSubjectPreviewYearFinal; - - LinearLayout gradesSubjectContent; - - LinearLayout gradesSubjectSemester1Header; - TextView gradesSubjectSemester1Title; - IconicsImageView gradesSubjectSemester1ExpandIndicator; - TextView gradesSubjectSemester1Average; - TextView gradesSubjectSemester1Proposed; - TextView gradesSubjectSemester1Final; - IconicsImageView gradesSubjectSemester1EditButton; - LinearLayout gradesSubjectSemester1Container; - NestedScrollView gradesSubjectSemester1Nest; - RecyclerView gradesSubjectSemester1Content; - - LinearLayout gradesSubjectSemester2Header; - TextView gradesSubjectSemester2Title; - IconicsImageView gradesSubjectSemester2ExpandIndicator; - TextView gradesSubjectSemester2Average; - TextView gradesSubjectSemester2Proposed; - TextView gradesSubjectSemester2Final; - IconicsImageView gradesSubjectSemester2EditButton; - LinearLayout gradesSubjectSemester2Container; - NestedScrollView gradesSubjectSemester2Nest; - RecyclerView gradesSubjectSemester2Content; - - TextView gradesSubjectYearAverage; - TextView gradesSubjectYearProposed; - TextView gradesSubjectYearFinal; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/HomeworksAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/adapters/HomeworksAdapter.java deleted file mode 100644 index c66baf98..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/HomeworksAdapter.java +++ /dev/null @@ -1,132 +0,0 @@ -package pl.szczodrzynski.edziennik.adapters; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.cardview.widget.CardView; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.datamodels.EventFull; -import pl.szczodrzynski.edziennik.dialogs.EventManualDialog; -import pl.szczodrzynski.edziennik.fragments.HomeFragment; -import pl.szczodrzynski.edziennik.models.Date; - -import static pl.szczodrzynski.edziennik.utils.Utils.bs; - -public class HomeworksAdapter extends RecyclerView.Adapter { - private Context context; - private List homeworkList; - - //getting the context and product list with constructor - public HomeworksAdapter(Context mCtx, List homeworkList) { - this.context = mCtx; - this.homeworkList = homeworkList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_homeworks_item, parent, false); - return new ViewHolder(view); - } - - public static String dayDiffString(Context context, int dayDiff) { - if (dayDiff > 0) { - if (dayDiff == 1) { - return context.getString(R.string.tomorrow); - } - else if (dayDiff == 2) { - return context.getString(R.string.the_day_after); - } - return HomeFragment.plural(context, R.plurals.time_till_days, Math.abs(dayDiff)); - } - else if (dayDiff < 0) { - if (dayDiff == -1) { - return context.getString(R.string.yesterday); - } - else if (dayDiff == -2) { - return context.getString(R.string.the_day_before); - } - return context.getString(R.string.ago_format, HomeFragment.plural(context, R.plurals.time_till_days, Math.abs(dayDiff))); - } - return context.getString(R.string.today); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - EventFull homework = homeworkList.get(position); - - int diffDays = Date.diffDays(homework.eventDate, Date.getToday()); - - holder.homeworksItemHomeworkDate.setText(app.getString(R.string.date_relative_format, homework.eventDate.getFormattedString(), dayDiffString(context, diffDays))); - holder.homeworksItemTopic.setText(homework.topic); - holder.homeworksItemSubjectTeacher.setText(context.getString(R.string.homeworks_subject_teacher_format, bs(homework.subjectLongName), bs(homework.teacherFullName))); - holder.homeworksItemTeamDate.setText(context.getString(R.string.homeworks_team_date_format, bs(homework.teamName), Date.fromMillis(homework.addedDate).getFormattedStringShort())); - - if (!homework.seen) { - holder.homeworksItemTopic.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); - holder.homeworksItemTopic.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - homework.seen = true; - AsyncTask.execute(() -> { - app.db.metadataDao().setSeen(App.profileId, homework, true); - }); - } - else { - holder.homeworksItemTopic.setBackground(null); - } - - holder.homeworksItemEdit.setVisibility((homework.addedManually ? View.VISIBLE : View.GONE)); - holder.homeworksItemEdit.setOnClickListener(v -> { - new EventManualDialog(context).show(app, homework, null, null, EventManualDialog.DIALOG_HOMEWORK); - }); - - if (homework.sharedBy == null) { - holder.homeworksItemSharedBy.setVisibility(View.GONE); - } - else if (homework.sharedByName != null) { - holder.homeworksItemSharedBy.setText(app.getString(R.string.event_shared_by_format, (homework.sharedBy.equals("self") ? app.getString(R.string.event_shared_by_self) : homework.sharedByName))); - } - } - - @Override - public int getItemCount() { - return homeworkList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - CardView homeworksItemCard; - TextView homeworksItemTopic; - TextView homeworksItemHomeworkDate; - TextView homeworksItemSharedBy; - TextView homeworksItemSubjectTeacher; - TextView homeworksItemTeamDate; - Button homeworksItemEdit; - - ViewHolder(View itemView) { - super(itemView); - homeworksItemCard = itemView.findViewById(R.id.homeworksItemCard); - homeworksItemTopic = itemView.findViewById(R.id.homeworksItemTopic); - homeworksItemHomeworkDate = itemView.findViewById(R.id.homeworksItemHomeworkDate); - homeworksItemSharedBy = itemView.findViewById(R.id.homeworksItemSharedBy); - homeworksItemSubjectTeacher = itemView.findViewById(R.id.homeworksItemSubjectTeacher); - homeworksItemTeamDate = itemView.findViewById(R.id.homeworksItemTeamDate); - homeworksItemEdit = itemView.findViewById(R.id.homeworksItemEdit); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/NotificationsAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/adapters/NotificationsAdapter.java deleted file mode 100644 index 8ec253b0..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/NotificationsAdapter.java +++ /dev/null @@ -1,104 +0,0 @@ -package pl.szczodrzynski.edziennik.adapters; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.cardview.widget.CardView; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Notification; - -import static pl.szczodrzynski.edziennik.utils.Utils.d; - -public class NotificationsAdapter extends RecyclerView.Adapter { - private static final String TAG = "NotificationsAdapter"; - private Context context; - private List notificationList; - - //getting the context and product list with constructor - public NotificationsAdapter(Context mCtx, List notificationList) { - this.context = mCtx; - this.notificationList = notificationList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_notifications_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - Notification notification = notificationList.get(position); - - holder.notificationsItemDate.setText(Date.fromMillis(notification.addedDate).getFormattedString()); - holder.notificationsItemText.setText(notification.text); - holder.notificationsItemTitle.setText(notification.title); - holder.notificationsItemType.setText(Notification.stringType(context, notification.type)); - - holder.notificationsItemCard.setOnClickListener((v -> { - Intent intent = new Intent("android.intent.action.MAIN"); - notification.fillIntent(intent); - - d(TAG, "notification with item "+notification.redirectFragmentId+" extras "+(intent.getExtras() == null ? "null" : intent.getExtras().toString())); - - //Log.d(TAG, "Got date "+intent.getLongExtra("timetableDate", 0)); - - if (notification.profileId != -1 && notification.profileId != app.profile.getId() && context instanceof Activity) { - Toast.makeText(app, app.getString(R.string.toast_changing_profile), Toast.LENGTH_LONG).show(); - } - app.sendBroadcast(intent); - })); - - if (!notification.seen) { - holder.notificationsItemText.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_8dp)); - holder.notificationsItemText.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - } - else { - holder.notificationsItemText.setBackground(null); - } - - } - - @Override - public int getItemCount() { - return notificationList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - CardView notificationsItemCard; - TextView notificationsItemDate; - TextView notificationsItemText; - TextView notificationsItemTitle; - TextView notificationsItemType; - - ViewHolder(View itemView) { - super(itemView); - notificationsItemCard = itemView.findViewById(R.id.notificationsItemCard); - notificationsItemDate = itemView.findViewById(R.id.notificationsItemDate); - notificationsItemText = itemView.findViewById(R.id.notificationsItemText); - notificationsItemTitle = itemView.findViewById(R.id.notificationsItemTitle); - notificationsItemType = itemView.findViewById(R.id.notificationsItemType); - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/TimetableAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/adapters/TimetableAdapter.java deleted file mode 100644 index d056c26c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/TimetableAdapter.java +++ /dev/null @@ -1,202 +0,0 @@ -package pl.szczodrzynski.edziennik.adapters; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Typeface; -import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.cardview.widget.CardView; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.RecyclerView; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -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.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.datamodels.EventFull; -import pl.szczodrzynski.edziennik.datamodels.LessonChange; -import pl.szczodrzynski.edziennik.datamodels.LessonFull; -import pl.szczodrzynski.edziennik.dialogs.EventListDialog; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.utils.SpannableHtmlTagHandler; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_HOMEWORK; - -public class TimetableAdapter extends RecyclerView.Adapter { - private static final String TAG = "TimetableAdapter"; - private Context context; - private Date lessonDate; - private List lessonList; - private List eventList; - public boolean setAsRead = false; - - //getting the context and product list with constructor - public TimetableAdapter(Context mCtx, Date lessonDate, List lessonList, List eventList) { - this.context = mCtx; - this.lessonDate = lessonDate; - this.lessonList = lessonList; - this.eventList = eventList; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - //inflating and returning our view holder - LayoutInflater inflater = LayoutInflater.from(context); - View view = inflater.inflate(R.layout.row_timetable_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - App app = (App) context.getApplicationContext(); - - LessonFull lesson = lessonList.get(position); - holder.timetableItemStartTime.setText(lesson.startTime.getStringHM()); - holder.timetableItemEndTime.setText(lesson.endTime.getStringHM()); - holder.timetableItemSubjectName.setText(lesson.subjectLongName); - holder.timetableItemClassroomName.setText(lesson.getClassroomName()); - holder.timetableItemTeamName.setText(lesson.getTeamName()); - holder.timetableItemTeacherName.setText(lesson.getTeacherFullName()); - - holder.timetableItemCard.setCardBackgroundColor(Utils.getAttr(context, R.attr.colorSurface)); - - if (lesson.changeId != 0) - { - if (!lesson.seen) { - holder.timetableItemSubjectName.setBackground(context.getResources().getDrawable(R.drawable.bg_rounded_4dp)); - holder.timetableItemSubjectName.getBackground().setColorFilter(new PorterDuffColorFilter(0x692196f3, PorterDuff.Mode.MULTIPLY)); - AsyncTask.execute(() -> app.db.metadataDao().setSeen(App.profileId, lesson, true)); - } - if (lesson.changeType == LessonChange.TYPE_CANCELLED) - { - holder.timetableItemSubjectName.setTypeface(null, Typeface.NORMAL); - holder.timetableItemSubjectChange.setVisibility(View.GONE); - - holder.timetableItemSubjectName.setText(Html.fromHtml(""+lesson.subjectLongName+"", null, new SpannableHtmlTagHandler())); - } - else if (lesson.changeType == LessonChange.TYPE_CHANGE) - { - holder.timetableItemSubjectName.setTypeface(null, Typeface.BOLD_ITALIC); - if (lesson.changedSubjectLongName()) { - holder.timetableItemSubjectChange.setText(lesson.subjectLongName); - holder.timetableItemSubjectName.setText(lesson.changeSubjectLongName); - holder.timetableItemSubjectChange.setVisibility(View.VISIBLE); - - holder.timetableItemSubjectChange.setText(Html.fromHtml(""+lesson.subjectLongName+"", null, new SpannableHtmlTagHandler())); - } - else { - holder.timetableItemSubjectChange.setVisibility(View.GONE); - } - holder.timetableItemTeacherName.setText(lesson.getTeacherFullName(true)); - holder.timetableItemTeamName.setText(lesson.getTeamName(true)); - holder.timetableItemClassroomName.setText(lesson.getClassroomName(true)); - } - else if (lesson.changeType == LessonChange.TYPE_ADDED) - { - - } - - - holder.timetableItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.CLEAR)); - - if (lesson.changeType == LessonChange.TYPE_CANCELLED) { - holder.timetableItemCard.setCardBackgroundColor(Themes.INSTANCE.isDark() ? 0x60000000 : 0xffeeeeee); - } - else if (lesson.changeType == LessonChange.TYPE_CHANGE) { - holder.timetableItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(Utils.getAttr(context, R.attr.colorPrimary), PorterDuff.Mode.SRC_ATOP));//.setBackgroundColor(App.getAttr(context, R.attr.cardBackgroundHighlight)); - } - } - else - { - holder.timetableItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.CLEAR)); - holder.timetableItemSubjectName.setBackground(null); - holder.timetableItemSubjectName.setTypeface(null, Typeface.NORMAL); - holder.timetableItemSubjectChange.setVisibility(View.GONE); - } - - int eventCount = 0; - - for (EventFull event: eventList) { - if (event.eventDate.getValue() == lessonDate.getValue() - && event.startTime != null - && event.startTime.getValue() == lesson.startTime.getValue()) { - eventCount++; - if (eventCount == 1) { - holder.timetableItemEvent1.setVisibility(View.VISIBLE); - if (event.type == TYPE_HOMEWORK) - holder.timetableItemEvent1.setBackground(new IconicsDrawable(context).color(IconicsColor.colorRes(R.color.md_red_500)).size(IconicsSize.dp(10)).icon(CommunityMaterial.Icon2.cmd_home)); - else - holder.timetableItemEvent1.setBackgroundColor(event.getColor()); - } - else if (eventCount == 2) { - holder.timetableItemEvent2.setVisibility(View.VISIBLE); - if (event.type == TYPE_HOMEWORK) - holder.timetableItemEvent2.setBackground(new IconicsDrawable(context).color(IconicsColor.colorRes(R.color.md_red_500)).size(IconicsSize.dp(10)).icon(CommunityMaterial.Icon2.cmd_home)); - else - holder.timetableItemEvent2.setBackgroundColor(event.getColor()); - } - else if (eventCount == 3) { - holder.timetableItemEvent3.setVisibility(View.VISIBLE); - if (event.type == TYPE_HOMEWORK) - holder.timetableItemEvent3.setBackground(new IconicsDrawable(context).color(IconicsColor.colorRes(R.color.md_red_500)).size(IconicsSize.dp(10)).icon(CommunityMaterial.Icon2.cmd_home)); - else - holder.timetableItemEvent3.setBackgroundColor(event.getColor()); - } - } - } - - holder.timetableItemCard.setOnClickListener(v -> new EventListDialog(context).show(app, lessonDate, lesson.startTime)); - } - - @Override - public int getItemCount() { - return lessonList.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - - CardView timetableItemCard; - ConstraintLayout timetableItemLayout; - TextView timetableItemStartTime; - TextView timetableItemClassroomName; - TextView timetableItemTeamName; - TextView timetableItemSubjectChange; - TextView timetableItemSubjectName; - TextView timetableItemTeacherName; - TextView timetableItemEndTime; - View timetableItemEvent1; - View timetableItemEvent2; - View timetableItemEvent3; - - ViewHolder(View itemView) { - super(itemView); - timetableItemCard = itemView.findViewById(R.id.timetableItemCard); - timetableItemLayout = itemView.findViewById(R.id.timetableItemLayout); - timetableItemStartTime = itemView.findViewById(R.id.timetableItemStartTime); - timetableItemClassroomName = itemView.findViewById(R.id.timetableItemClassroomName); - timetableItemTeamName = itemView.findViewById(R.id.timetableItemTeamName); - timetableItemSubjectChange = itemView.findViewById(R.id.timetableItemSubjectChange); - timetableItemSubjectName = itemView.findViewById(R.id.timetableItemSubjectName); - timetableItemTeacherName = itemView.findViewById(R.id.noticesItemTeacherName); - timetableItemEndTime = itemView.findViewById(R.id.timetableItemEndTime); - timetableItemEvent1 = itemView.findViewById(R.id.timetableItemEvent1); - timetableItemEvent2 = itemView.findViewById(R.id.timetableItemEvent2); - timetableItemEvent3 = itemView.findViewById(R.id.timetableItemEvent3); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/AppError.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/AppError.java deleted file mode 100644 index d5f6c2a7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/AppError.java +++ /dev/null @@ -1,321 +0,0 @@ -package pl.szczodrzynski.edziennik.api; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Build; -import android.util.Log; - -import com.google.gson.JsonObject; - -import java.io.InterruptedIOException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLException; - -import im.wangchao.mhttp.Request; -import im.wangchao.mhttp.Response; -import pl.szczodrzynski.edziennik.R; - - -public class AppError { - public static final int CODE_OTHER = 0; - public static final int CODE_OK = 1; - public static final int CODE_NO_INTERNET = 10; - public static final int CODE_SSL_ERROR = 13; - public static final int CODE_ARCHIVED = 5; - public static final int CODE_MAINTENANCE = 6; - public static final int CODE_LOGIN_ERROR = 7; - public static final int CODE_ACCOUNT_MISMATCH = 8; - public static final int CODE_APP_SERVER_ERROR = 9; - public static final int CODE_MULTIACCOUNT_SETUP = 12; - public static final int CODE_TIMEOUT = 11; - public static final int CODE_PROFILE_NOT_FOUND = 14; - public static final int CODE_ATTACHMENT_NOT_AVAILABLE = 28; - // user's fault - public static final int CODE_INVALID_LOGIN = 2; - public static final int CODE_INVALID_SERVER_ADDRESS = 21; - public static final int CODE_INVALID_SCHOOL_NAME = 22; - public static final int CODE_INVALID_DEVICE = 23; - public static final int CODE_OLD_PASSWORD = 4; - public static final int CODE_INVALID_TOKEN = 24; - public static final int CODE_EXPIRED_TOKEN = 27; - public static final int CODE_INVALID_SYMBOL = 25; - public static final int CODE_INVALID_PIN = 26; - public static final int CODE_LIBRUS_NOT_ACTIVATED = 29; - public static final int CODE_SYNERGIA_NOT_ACTIVATED = 32; - public static final int CODE_LIBRUS_DISCONNECTED = 31; - public static final int CODE_PROFILE_ARCHIVED = 30; - - public static final int CODE_INTERNAL_MISSING_DATA = 100; - // internal errors - not for user's information. - // these error codes are processed in API main classes - public static final int CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120; - public static final int CODE_INTERNAL_LIBRUS_ACCOUNT_410_ = 120; - - public String TAG; - public int line; - public int errorCode; - public String errorText; - public Response response; - public Request request; - public Throwable throwable; - public String apiResponse; - - public AppError(String TAG, int line, int errorCode, String errorText, Response response, Request request, Throwable throwable, String apiResponse) { - this.TAG = TAG; - this.line = line; - this.errorCode = errorCode; - this.errorText = errorText; - this.response = response; - this.request = request; - this.throwable = throwable; - this.apiResponse = apiResponse; - } - - public AppError(String TAG, int line, int errorCode) { - this(TAG, line, errorCode, null, null, null, null, null); - } - public AppError(String TAG, int line, int errorCode, Response response, Throwable throwable) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), throwable, null); - } - public AppError(String TAG, int line, int errorCode, Response response) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), null, null); - } - public AppError(String TAG, int line, int errorCode, Throwable throwable, String apiResponse) { - this(TAG, line, errorCode, null, null, null, throwable, apiResponse); - } - public AppError(String TAG, int line, int errorCode, Throwable throwable, JsonObject apiResponse) { - this(TAG, line, errorCode, null, null, null, throwable, apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, String errorText, Response response, JsonObject apiResponse) { - this(TAG, line, errorCode, errorText, response, response == null ? null : response.request(), null, apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, String errorText, Response response, String apiResponse) { - this(TAG, line, errorCode, errorText, response, response == null ? null : response.request(), null, apiResponse); - } - public AppError(String TAG, int line, int errorCode, String errorText, String apiResponse) { - this(TAG, line, errorCode, errorText, null, null, null, apiResponse); - } - public AppError(String TAG, int line, int errorCode, String errorText, JsonObject apiResponse) { - this(TAG, line, errorCode, errorText, null, null, null, apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, String errorText) { - this(TAG, line, errorCode, errorText, null, null, null, null); - } - public AppError(String TAG, int line, int errorCode, JsonObject apiResponse) { - this(TAG, line, errorCode, null, null, null, null, apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, Response response, Throwable throwable, JsonObject apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), throwable, apiResponse.toString()); - } - public AppError(String TAG, int line, int errorCode, Response response, Throwable throwable, String apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), throwable, apiResponse); - } - public AppError(String TAG, int line, int errorCode, Response response, String apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), null, apiResponse); - } - public AppError(String TAG, int line, int errorCode, Response response, JsonObject apiResponse) { - this(TAG, line, errorCode, null, response, response == null ? null : response.request(), null, apiResponse.toString()); - } - - public String getDetails(Context context) { - StringBuilder sb = new StringBuilder(); - sb.append(stringErrorCode(context, errorCode, errorText)).append("\n"); - sb.append("(").append(stringErrorType(errorCode)).append("#").append(errorCode).append(")\n"); - sb.append("at ").append(TAG).append(":").append(line).append("\n"); - sb.append("\n"); - if (throwable == null) - sb.append("Throwable is null"); - else - sb.append(Log.getStackTraceString(throwable)); - sb.append("\n"); - sb.append(Build.MANUFACTURER).append(" ").append(Build.BRAND).append(" ").append(Build.MODEL).append(" ").append(Build.DEVICE).append("\n"); - - return sb.toString(); - } - - public interface GetApiResponseCallback { - void onSuccess(String apiResponse); - } - /** - * - * @param context a Context - * @param apiResponseCallback a callback executed on a worker thread - */ - public void getApiResponse(Context context, GetApiResponseCallback apiResponseCallback) { - StringBuilder sb = new StringBuilder(); - sb.append("Request:\n"); - if (request != null) { - sb.append(request.method()).append(" ").append(request.url().toString()).append("\n"); - sb.append(request.headers().toString()).append("\n"); - sb.append("\n"); - sb.append(request.bodyToString()).append("\n\n"); - } - else - sb.append("null\n\n"); - - if (apiResponse == null && response != null) - apiResponse = response.parserErrorBody; - - sb.append("Response:\n"); - if (response != null) { - sb.append(response.code()).append(" ").append(response.message()).append("\n"); - sb.append(response.headers().toString()).append("\n"); - sb.append("\n"); - if (apiResponse == null) { - if (Thread.currentThread().getName().equals("main")) { - AsyncTask.execute(() -> { - if (response.raw().body() != null) { - try { - sb.append(response.raw().body().string()); - } catch (Exception e) { - sb.append("Exception while getting response body:\n").append(Log.getStackTraceString(e)); - } - } - else { - sb.append("null"); - } - apiResponseCallback.onSuccess(sb.toString()); - }); - } - else { - if (response.raw().body() != null) { - try { - sb.append(response.raw().body().string()); - } catch (Exception e) { - sb.append("Exception while getting response body:\n").append(Log.getStackTraceString(e)); - } - } - else { - sb.append("null"); - } - apiResponseCallback.onSuccess(sb.toString()); - } - return; - } - } - else - sb.append("null\n\n"); - - sb.append("API Response:\n"); - if (apiResponse != null) { - sb.append(apiResponse).append("\n\n"); - } - else { - sb.append("null\n\n"); - } - - apiResponseCallback.onSuccess(sb.toString()); - } - - public AppError changeIfCodeOther() { - if (errorCode != CODE_OTHER && errorCode != CODE_MAINTENANCE) - return this; - if (throwable instanceof UnknownHostException) - errorCode = CODE_NO_INTERNET; - else if (throwable instanceof SSLException) - errorCode = CODE_SSL_ERROR; - else if (throwable instanceof SocketTimeoutException) - errorCode = CODE_TIMEOUT; - else if (throwable instanceof InterruptedIOException) - errorCode = CODE_NO_INTERNET; - else if (response != null && - (response.code() == 424 - || response.code() == 400 - || response.code() == 401 - || response.code() == 500 - || response.code() == 503 - || response.code() == 404)) - errorCode = CODE_MAINTENANCE; - return this; - } - - public String asReadableString(Context context) { - return stringErrorCode(context, errorCode, errorText) + (errorCode == CODE_MAINTENANCE && errorText != null && !errorText.isEmpty() ? " ("+errorText+")" : ""); - } - - public static String stringErrorCode(Context context, int errorCode, String errorText) - { - switch (errorCode) { - case CODE_OK: - return context.getString(R.string.sync_error_ok); - case CODE_INVALID_LOGIN: - return context.getString(R.string.sync_error_invalid_login); - case CODE_LOGIN_ERROR: - return context.getString(R.string.sync_error_login_error); - case CODE_INVALID_DEVICE: - return context.getString(R.string.sync_error_invalid_device); - case CODE_OLD_PASSWORD: - return context.getString(R.string.sync_error_old_password); - case CODE_ARCHIVED: - return context.getString(R.string.sync_error_archived); - case CODE_MAINTENANCE: - return context.getString(R.string.sync_error_maintenance); - case CODE_NO_INTERNET: - return context.getString(R.string.sync_error_no_internet); - case CODE_ACCOUNT_MISMATCH: - return context.getString(R.string.sync_error_account_mismatch); - case CODE_APP_SERVER_ERROR: - return context.getString(R.string.sync_error_app_server); - case CODE_TIMEOUT: - return context.getString(R.string.sync_error_timeout); - case CODE_SSL_ERROR: - return context.getString(R.string.sync_error_ssl); - case CODE_INVALID_SERVER_ADDRESS: - return context.getString(R.string.sync_error_invalid_server_address); - case CODE_INVALID_SCHOOL_NAME: - return context.getString(R.string.sync_error_invalid_school_name); - case CODE_PROFILE_NOT_FOUND: - return context.getString(R.string.sync_error_profile_not_found); - case CODE_INVALID_TOKEN: - return context.getString(R.string.sync_error_invalid_token); - case CODE_ATTACHMENT_NOT_AVAILABLE: - return context.getString(R.string.sync_error_attachment_not_available); - case CODE_LIBRUS_NOT_ACTIVATED: - return context.getString(R.string.sync_error_librus_not_activated); - case CODE_PROFILE_ARCHIVED: - return context.getString(R.string.sync_error_profile_archived); - case CODE_LIBRUS_DISCONNECTED: - return context.getString(R.string.sync_error_librus_disconnected); - case CODE_SYNERGIA_NOT_ACTIVATED: - return context.getString(R.string.sync_error_synergia_not_activated); - default: - case CODE_MULTIACCOUNT_SETUP: - case CODE_OTHER: - return errorText != null ? errorText : context.getString(R.string.sync_error_unknown); - } - } - public static String stringErrorType(int errorCode) - { - switch (errorCode) { - default: - case CODE_OTHER: return "CODE_OTHER"; - case CODE_OK: return "CODE_OK"; - case CODE_NO_INTERNET: return "CODE_NO_INTERNET"; - case CODE_SSL_ERROR: return "CODE_SSL_ERROR"; - case CODE_ARCHIVED: return "CODE_ARCHIVED"; - case CODE_MAINTENANCE: return "CODE_MAINTENANCE"; - case CODE_LOGIN_ERROR: return "CODE_LOGIN_ERROR"; - case CODE_ACCOUNT_MISMATCH: return "CODE_ACCOUNT_MISMATCH"; - case CODE_APP_SERVER_ERROR: return "CODE_APP_SERVER_ERROR"; - case CODE_MULTIACCOUNT_SETUP: return "CODE_MULTIACCOUNT_SETUP"; - case CODE_TIMEOUT: return "CODE_TIMEOUT"; - case CODE_PROFILE_NOT_FOUND: return "CODE_PROFILE_NOT_FOUND"; - case CODE_INVALID_LOGIN: return "CODE_INVALID_LOGIN"; - case CODE_INVALID_SERVER_ADDRESS: return "CODE_INVALID_SERVER_ADDRESS"; - case CODE_INVALID_SCHOOL_NAME: return "CODE_INVALID_SCHOOL_NAME"; - case CODE_INVALID_DEVICE: return "CODE_INVALID_DEVICE"; - case CODE_OLD_PASSWORD: return "CODE_OLD_PASSWORD"; - case CODE_INVALID_TOKEN: return "CODE_INVALID_TOKEN"; - case CODE_EXPIRED_TOKEN: return "CODE_EXPIRED_TOKEN"; - case CODE_INVALID_SYMBOL: return "CODE_INVALID_SYMBOL"; - case CODE_INVALID_PIN: return "CODE_INVALID_PIN"; - case CODE_ATTACHMENT_NOT_AVAILABLE: return "CODE_ATTACHMENT_NOT_AVAILABLE"; - case CODE_LIBRUS_NOT_ACTIVATED: return "CODE_LIBRUS_NOT_ACTIVATED"; - case CODE_PROFILE_ARCHIVED: return "CODE_PROFILE_ARCHIVED"; - case CODE_LIBRUS_DISCONNECTED: return "CODE_LIBRUS_DISCONNECTED"; - case CODE_SYNERGIA_NOT_ACTIVATED: return "CODE_SYNERGIA_NOT_ACTIVATED"; - } - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/Edziennik.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/Edziennik.java deleted file mode 100644 index e3091ac9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/Edziennik.java +++ /dev/null @@ -1,1171 +0,0 @@ -package pl.szczodrzynski.edziennik.api; - -import android.app.Activity; -import android.appwidget.AppWidgetManager; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.text.Html; -import android.util.Base64; -import android.util.Log; -import android.webkit.CookieManager; -import android.webkit.CookieSyncManager; -import android.webkit.WebView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; -import com.danimahardhika.cafebar.CafeBar; -import com.google.android.gms.common.util.ArrayUtils; -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.MainActivity; -import pl.szczodrzynski.edziennik.WidgetTimetable; -import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; -import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; -import pl.szczodrzynski.edziennik.datamodels.AnnouncementFull; -import pl.szczodrzynski.edziennik.datamodels.Attendance; -import pl.szczodrzynski.edziennik.datamodels.AttendanceFull; -import pl.szczodrzynski.edziennik.datamodels.Event; -import pl.szczodrzynski.edziennik.datamodels.EventFull; -import pl.szczodrzynski.edziennik.datamodels.EventType; -import pl.szczodrzynski.edziennik.datamodels.GradeFull; -import pl.szczodrzynski.edziennik.datamodels.LessonFull; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.MessageFull; -import pl.szczodrzynski.edziennik.datamodels.Metadata; -import pl.szczodrzynski.edziennik.datamodels.Notice; -import pl.szczodrzynski.edziennik.datamodels.NoticeFull; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.datamodels.Team; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Notification; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.sync.SyncJob; -import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.widgets.luckynumber.WidgetLuckyNumber; -import pl.szczodrzynski.edziennik.widgets.notifications.WidgetNotifications; - -import static android.content.Context.CLIPBOARD_SERVICE; -import static com.mikepenz.iconics.utils.IconicsConvertersKt.colorInt; -import static com.mikepenz.iconics.utils.IconicsConvertersKt.sizeDp; -import static pl.szczodrzynski.edziennik.App.APP_URL; -import static pl.szczodrzynski.edziennik.MainActivity.DRAWER_ITEM_HOME; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_OK; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_PROFILE_ARCHIVED; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_PROFILE_NOT_FOUND; -import static pl.szczodrzynski.edziennik.api.AppError.stringErrorCode; -import static pl.szczodrzynski.edziennik.api.AppError.stringErrorType; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_AGENDA; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_ALL; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_ANNOUNCEMENTS; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_ATTENDANCES; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_GRADES; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_HOMEWORKS; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_MESSAGES_INBOX; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_MESSAGES_OUTBOX; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_NOTICES; -import static pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface.FEATURE_TIMETABLE; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_HOMEWORK; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_IUCZNIOWIE; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_LIBRUS; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_MOBIDZIENNIK; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_VULCAN; -import static pl.szczodrzynski.edziennik.datamodels.Profile.REGISTRATION_ENABLED; -import static pl.szczodrzynski.edziennik.sync.SyncService.PROFILE_MAX_PROGRESS; -import static pl.szczodrzynski.edziennik.utils.Utils.d; -import static pl.szczodrzynski.edziennik.utils.Utils.ns; - -public class Edziennik { - //public static final int CODE_NULL = 0; - private App app; - - private static final String TAG = "Edziennik"; - private static boolean registerEmpty; - public static int oldLuckyNumber; - - public static EdziennikInterface getApi(App app, int loginType) { - switch (loginType) { - default: - case LOGIN_TYPE_MOBIDZIENNIK: - return app.apiMobidziennik; - case LOGIN_TYPE_LIBRUS: - return app.apiLibrus; - case LOGIN_TYPE_IUCZNIOWIE: - return app.apiIuczniowie; - case LOGIN_TYPE_VULCAN: - return app.apiVulcan; - } - } - - public Edziennik(App app) { - this.app = app; - } - - @SuppressWarnings("deprecation") - public static void clearCookies(Context context, String url) { - //Log.d(TAG, "Cookies: " + yahooCookies); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - //Log.d(TAG, "Using clearCookies code for API >=" + String.valueOf(Build.VERSION_CODES.LOLLIPOP_MR1)); - CookieManager.getInstance().removeAllCookies(null); - CookieManager.getInstance().flush(); - } else { - //Log.d(TAG, "Using clearCookies code for API <" + String.valueOf(Build.VERSION_CODES.LOLLIPOP_MR1)); - CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(context); - cookieSyncManager.startSync(); - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.removeAllCookie(); - cookieManager.removeSessionCookie(); - cookieSyncManager.stopSync(); - cookieSyncManager.sync(); - } - } - - public void initMessagesWebView(WebView webView, App app, boolean fullVersion, boolean clearCookies) { - if (!app.profile.getEmpty() && app.profile.getLoginStoreType() == LoginStore.LOGIN_TYPE_MOBIDZIENNIK) { - String url; - if (fullVersion) { - url = "https://" + app.profile.getLoginData("serverName", "") + ".mobidziennik.pl/mobile/wiadomosci"; - } else { - url = "https://" + app.profile.getLoginData("serverName", "") + ".mobidziennik.pl/api/"; - } - - String str1 = "login=" + app.profile.getLoginData("username", "") + "&haslo=" + app.profile.getLoginData("password", ""); - - if (!fullVersion) { - str1 += "&ip=" + app.deviceId + "&wersja=20&token=&webview_wiadomosci=1"; - } - - - if (-1L != -1L) { - str1 += "&id_wiadomosci=" + -1L; - } - - if (clearCookies) - clearCookies(app, "https://" + app.profile.getLoginData("serverName", "") + ".mobidziennik.pl"); - - //Toast.makeText(app, "URL "+url, Toast.LENGTH_SHORT).show(); - webView.postUrl(url, str1.getBytes()); - } else if (!app.profile.getEmpty() && app.profile.getLoginStoreType() == LoginStore.LOGIN_TYPE_IUCZNIOWIE) { - String url = "https://iuczniowie.progman.pl/idziennik/mod_panelRodzica/Komunikator.aspx"; - webView.loadUrl(url); - /* - if (app.profile.loginServerName.equals("") || app.profile.loginUsername.equals("") || app.profile.loginPassword.equals("")) { - webView.loadData("

"+app.getString(R.string.api_error_code_invalid_login)+"

", "text/html", "UTF-8"); - return; - } - - if (app.appConfig.deviceId == null || app.appConfig.deviceId.equals("")) { - app.appConfig.deviceId = Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID); - app.appConfig.savePending = true; - } - - //Ion.getDefault(app.getContext()).getCookieMiddleware().getCookieStore().removeAll(); // TODO remove only cookies for this domain - String finalLoginServerName = app.profile.loginServerName; - String finalLoginUsername = app.profile.loginUsername; - String finalLoginPassword = app.profile.loginPassword; - Ion.with(app.getContext()) - .load("https://iuczniowie.progman.pl/idziennik/login.aspx") - .setTimeout(REQUEST_TIMEOUT) - .setHeader("User-Agent", Iuczniowie.userAgent) - //.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") - .asString() - .setCallback((e, result) -> { - if (e instanceof java.util.concurrent.TimeoutException) { - webView.loadData("

"+app.getString(R.string.api_error_code_timeout)+"

", "text/html", "UTF-8"); - return; - } - app.profile.loggedIn = (result != null && result.equals("ok")); - if (result == null || result.equals("")) { // for safety - webView.loadData("

"+app.getString(R.string.api_error_code_no_internet)+"

", "text/html", "UTF-8"); - return; - } - if (clearCookies) - clearCookies(app, "https://iuczniowie.progman.pl"); - - String post = ""; - try { - post += "ctl00$ContentPlaceHolder$nazwaPrzegladarki="+URLEncoder.encode(Iuczniowie.userAgent, "UTF-8"); - post += "ctl00$ContentPlaceHolder$NazwaSzkoly="+finalLoginServerName; - post += "ctl00$ContentPlaceHolder$UserName="+URLEncoder.encode(finalLoginUsername, "UTF-8"); - post += "ctl00$ContentPlaceHolder$Password="+URLEncoder.encode(finalLoginPassword, "UTF-8"); - post += "ctl00$ContentPlaceHolder$Logowanie=Zaloguj"; - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } - - webView.getSettings().setUserAgentString(Iuczniowie.userAgent); - - });*/ - } else if (app.profile.getEmpty()) { - webView.loadData("

" + app.getString(R.string.sync_error_invalid_login) + "

", "text/html", "UTF-8"); - } else { - webView.loadData("

" + app.getString(R.string.settings_register_login_not_implemented_text) + "

", "text/html", "UTF-8"); - } - } - - - - /* _____ _______ _ - | __ \ /\ |__ __| | | - | |__) | __ ___ ___ ___ ___ ___ / \ ___ _ _ _ __ ___| | __ _ ___| | __ - | ___/ '__/ _ \ / __/ _ \/ __/ __| / /\ \ / __| | | | '_ \ / __| |/ _` / __| |/ / - | | | | | (_) | (_| __/\__ \__ \/ ____ \\__ \ |_| | | | | (__| | (_| \__ \ < - |_| |_| \___/ \___\___||___/___/_/ \_\___/\__, |_| |_|\___|_|\__,_|___/_|\_\ - __/ | - |__*/ - - /** - * A task for creating notifications and downloading shared events. - */ - private static class ProcessAsyncTask extends AsyncTask { - private App app; - private WeakReference activityContext; - private SyncCallback callback; - private Exception e = null; - private String apiResponse = null; - private int profileId; - private ProfileFull profile; - - public ProcessAsyncTask(App app, Context activityContext, SyncCallback callback, int profileId, ProfileFull profile) { - //d(TAG, "Thread/ProcessAsyncTask/constructor/"+Thread.currentThread().getName()); - this.app = app; - this.activityContext = new WeakReference<>(activityContext); - this.callback = callback; - this.profileId = profileId; - this.profile = profile; - } - - @Override - protected Integer doInBackground(Void... voids) { - Context activityContext = this.activityContext.get(); - //d(TAG, "Thread/ProcessAsyncTask/doInBackground/"+Thread.currentThread().getName()); - try { - - // UPDATE FCM TOKEN IF EMPTY - if (app.appConfig.fcmToken == null || app.appConfig.fcmToken.equals("")) { - FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(instanceIdResult -> { - app.appConfig.fcmToken = instanceIdResult.getToken(); - app.appConfig.savePending = true; - }); - } - - callback.onProgress(1); - if (profile.getSyncNotifications()) { - new Handler(activityContext.getMainLooper()).post(() -> { - callback.onActionStarted(R.string.sync_action_creating_notifications); - }); - - for (LessonFull change : app.db.lessonChangeDao().getNotNotifiedNow(profileId)) { - String text = app.getContext().getString(R.string.notification_lesson_change_format, change.changeTypeStr(app.getContext()), change.lessonDate == null ? "" : change.lessonDate.getFormattedString(), change.subjectLongName); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_TIMETABLE_LESSON_CHANGE) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_TIMETABLE) - .withLongExtra("timetableDate", change.lessonDate.getValue()) - .withAddedDate(change.addedDate) - ); - } - for (EventFull event : app.db.eventDao().getNotNotifiedNow(profileId)) { - String text; - if (event.type == TYPE_HOMEWORK) - text = app.getContext().getString(R.string.notification_homework_format, ns(app.getString(R.string.notification_event_no_subject), event.subjectLongName), event.eventDate.getFormattedString()); - else - text = app.getContext().getString(R.string.notification_event_format, event.typeName, event.eventDate.getFormattedString(), ns(app.getString(R.string.notification_event_no_subject), event.subjectLongName)); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(event.type == TYPE_HOMEWORK ? Notification.TYPE_NEW_HOMEWORK : Notification.TYPE_NEW_EVENT) - .withFragmentRedirect(event.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORKS : MainActivity.DRAWER_ITEM_AGENDA) - .withLongExtra("eventId", event.id) - .withLongExtra("eventDate", event.eventDate.getValue()) - .withAddedDate(event.addedDate) - ); - // student's rights abuse - disabled, because this was useless - /*if (!event.addedManually && event.type == RegisterEvent.TYPE_EXAM && event.eventDate.combineWith(event.startTime) - event.addedDate < 7 * 24 * 60 * 60 * 1000) { - text = app.getContext().getString(R.string.notification_abuse_format, event.typeString(app, app.profile), event.subjectLongName, event.eventDate.getFormattedString()); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.id, profile.name) - .withType(Notification.TYPE_GENERAL) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_NOTIFICATIONS) - ); - }*/ - } - - Date today = Date.getToday(); - int todayValue = today.getValue(); - profile.setCurrentSemester(profile.dateToSemester(today)); - - for (GradeFull grade : app.db.gradeDao().getNotNotifiedNow(profileId)) { - String gradeName = grade.name; - if (grade.type == TYPE_SEMESTER1_PROPOSED - || grade.type == TYPE_SEMESTER2_PROPOSED) { - gradeName = (app.getString(R.string.grade_semester_proposed_format_2, grade.name)); - } else if (grade.type == TYPE_SEMESTER1_FINAL - || grade.type == TYPE_SEMESTER2_FINAL) { - gradeName = (app.getString(R.string.grade_semester_final_format_2, grade.name)); - } else if (grade.type == TYPE_YEAR_PROPOSED) { - gradeName = (app.getString(R.string.grade_year_proposed_format_2, grade.name)); - } else if (grade.type == TYPE_YEAR_FINAL) { - gradeName = (app.getString(R.string.grade_year_final_format_2, grade.name)); - } - String text = app.getContext().getString(R.string.notification_grade_format, gradeName, grade.subjectLongName); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_NEW_GRADE) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_GRADES) - .withLongExtra("gradesSubjectId", grade.subjectId) - .withAddedDate(grade.addedDate) - ); - } - for (NoticeFull notice : app.db.noticeDao().getNotNotifiedNow(profileId)) { - String noticeTypeStr = (notice.type == Notice.TYPE_POSITIVE ? app.getString(R.string.notification_notice_praise) : (notice.type == Notice.TYPE_NEGATIVE ? app.getString(R.string.notification_notice_warning) : app.getString(R.string.notification_notice_new))); - String text = app.getContext().getString(R.string.notification_notice_format, noticeTypeStr, notice.teacherFullName, Date.fromMillis(notice.addedDate).getFormattedString()); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_NEW_NOTICE) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_NOTICES) - .withLongExtra("noticeId", notice.id) - .withAddedDate(notice.addedDate) - ); - } - for (AttendanceFull attendance : app.db.attendanceDao().getNotNotifiedNow(profileId)) { - String attendanceTypeStr = app.getString(R.string.notification_type_attendance); - switch (attendance.type) { - case Attendance.TYPE_ABSENT: - attendanceTypeStr = app.getString(R.string.notification_absence); - break; - case Attendance.TYPE_ABSENT_EXCUSED: - attendanceTypeStr = app.getString(R.string.notification_absence_excused); - break; - case Attendance.TYPE_BELATED: - attendanceTypeStr = app.getString(R.string.notification_belated); - break; - case Attendance.TYPE_BELATED_EXCUSED: - attendanceTypeStr = app.getString(R.string.notification_belated_excused); - break; - case Attendance.TYPE_RELEASED: - attendanceTypeStr = app.getString(R.string.notification_release); - break; - } - String text = app.getContext().getString(R.string.notification_attendance_format, attendanceTypeStr, attendance.subjectLongName, attendance.lessonDate.getFormattedString()); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_NEW_ATTENDANCE) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_ATTENDANCES) - .withLongExtra("attendanceId", attendance.id) - .withAddedDate(attendance.addedDate) - ); - } - for (AnnouncementFull announcement : app.db.announcementDao().getNotNotifiedNow(profileId)) { - String text = app.getContext().getString(R.string.notification_announcement_format, announcement.subject); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_NEW_ANNOUNCEMENT) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_ANNOUNCEMENTS) - .withLongExtra("announcementId", announcement.id) - .withAddedDate(announcement.addedDate) - ); - } - for (MessageFull message : app.db.messageDao().getReceivedNotNotifiedNow(profileId)) { - String text = app.getContext().getString(R.string.notification_message_format, message.senderFullName, message.subject); - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_NEW_MESSAGE) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_MESSAGES) - .withLongExtra("messageType", Message.TYPE_RECEIVED) - .withLongExtra("messageId", message.id) - .withAddedDate(message.addedDate) - ); - } - - if (profile.getLuckyNumber() != oldLuckyNumber - && profile.getLuckyNumber() != -1 - && profile.getLuckyNumberDate() != null - && profile.getLuckyNumberDate().getValue() >= todayValue) { - String text; - if (profile.getLuckyNumberDate().getValue() == todayValue) { // LN for today - text = app.getString((profile.getStudentNumber() != -1 && profile.getStudentNumber() == profile.getLuckyNumber() ? R.string.notification_lucky_number_yours_format : R.string.notification_lucky_number_format), profile.getLuckyNumber()); - } else if (profile.getLuckyNumberDate().getValue() == todayValue + 1) { // LN for tomorrow - text = app.getString((profile.getStudentNumber() != -1 && profile.getStudentNumber() == profile.getLuckyNumber() ? R.string.notification_lucky_number_yours_tomorrow_format : R.string.notification_lucky_number_tomorrow_format), profile.getLuckyNumber()); - } else { // LN for later - text = app.getString((profile.getStudentNumber() != -1 && profile.getStudentNumber() == profile.getLuckyNumber() ? R.string.notification_lucky_number_yours_later_format : R.string.notification_lucky_number_later_format), profile.getLuckyNumberDate().getFormattedString(), profile.getLuckyNumber()); - } - app.notifier.add(new Notification(app.getContext(), text) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_LUCKY_NUMBER) - .withFragmentRedirect(MainActivity.DRAWER_ITEM_HOME) - ); - oldLuckyNumber = profile.getLuckyNumber(); - } - } - - - app.db.metadataDao().setAllNotified(profileId, true); - callback.onProgress(1); - - // SEND WEB PUSH, if registration allowed - // otherwise, UNREGISTER THE USER - if (profile.getRegistration() == REGISTRATION_ENABLED) { - new Handler(activityContext.getMainLooper()).post(() -> { - callback.onActionStarted(R.string.sync_action_syncing_shared_events); - }); - //if (profile.registrationUsername == null || profile.registrationUsername.equals("")) { - //} - ServerRequest syncRequest = new ServerRequest(app, app.requestScheme + APP_URL + "main.php?sync", "Edziennik/REG", profile); - - if (registerEmpty) { - syncRequest.setBodyParameter("first_run", "true"); - } - - // ALSO SEND NEW DATA TO BROWSER *excluding* all Shared Events !!! - // because they will be sent by the server, as soon as it's shared, by FCM - - if (app.appConfig.webPushEnabled) { - int position = 0; - for (Notification notification : app.appConfig.notifications) { - //Log.d(TAG, notification.text); - if (!notification.notified) { - if (notification.type != Notification.TYPE_NEW_SHARED_EVENT - && notification.type != Notification.TYPE_SERVER_MESSAGE - && notification.type != Notification.TYPE_NEW_SHARED_HOMEWORK) // these are automatically sent to the browser by the server - { - //Log.d(TAG, "Adding notify[" + position + "]"); - syncRequest.setBodyParameter("notify[" + position + "][type]", Integer.toString(notification.type)); - syncRequest.setBodyParameter("notify[" + position + "][title]", notification.title); - syncRequest.setBodyParameter("notify[" + position + "][text]", notification.text); - position++; - } - } - } - } - - callback.onProgress(1); - - if (app.appConfig.webPushEnabled || profile.getEnableSharedEvents()) { - JsonObject result = syncRequest.runSync(); - callback.onProgress(1); - //Log.d(TAG, "Executed request"); - if (result == null) { - return AppError.CODE_APP_SERVER_ERROR; - } - apiResponse = result.toString(); - if (!result.get("success").getAsString().equals("true")) { - return AppError.CODE_APP_SERVER_ERROR; - } - // HERE PROCESS ALL THE RECEIVED EVENTS - // add them to the profile and create appropriate notifications - for (JsonElement jEventEl : result.getAsJsonArray("events")) { - JsonObject jEvent = jEventEl.getAsJsonObject(); - String teamCode = jEvent.get("team").getAsString(); - //d(TAG, "An event is there! "+jEvent.toString()); - // get the target Team from teamCode - Team team = app.db.teamDao().getByCodeNow(profile.getId(), teamCode); - if (team != null) { - //d(TAG, "The target team is "+team.name+", ID "+team.id); - // create the event from Json. Add the missing teamId and !!profileId!! - Event event = app.gson.fromJson(jEvent.toString(), Event.class); - // proguard. disable for Event.class - if (event.eventDate == null) { - apiResponse += "\n\nEventDate == null\n" + jEvent.toString(); - throw new Exception("null eventDate"); - } - event.profileId = profile.getId(); - event.teamId = team.id; - event.addedManually = true; - //d(TAG, "Created the event! "+event); - - if (event.sharedBy != null && event.sharedBy.equals(profile.getUsernameId())) { - //d(TAG, "Shared by self! Changing name"); - event.sharedBy = "self"; - event.sharedByName = profile.getStudentNameLong(); - } - - EventType type = app.db.eventTypeDao().getByIdNow(profileId, event.type); - - //d(TAG, "Finishing adding event "+event); - app.db.eventDao().add(event); - Metadata metadata = new Metadata(profile.getId(), event.type == TYPE_HOMEWORK ? Metadata.TYPE_HOMEWORK : Metadata.TYPE_EVENT, event.id, registerEmpty, true, jEvent.get("addedDate").getAsLong()); - long metadataId = app.db.metadataDao().add(metadata); - if (metadataId != -1 && !registerEmpty) { - app.notifier.add(new Notification(app.getContext(), app.getString(R.string.notification_shared_event_format, event.sharedByName, type != null ? type.name : "wydarzenie", event.eventDate == null ? "nieznana data" : event.eventDate.getFormattedString(), event.topic)) - .withProfileData(profile.getId(), profile.getName()) - .withType(event.type == TYPE_HOMEWORK ? Notification.TYPE_NEW_SHARED_HOMEWORK : Notification.TYPE_NEW_SHARED_EVENT) - .withFragmentRedirect(event.type == TYPE_HOMEWORK ? MainActivity.DRAWER_ITEM_HOMEWORKS : MainActivity.DRAWER_ITEM_AGENDA) - .withLongExtra("eventDate", event.eventDate.getValue()) - ); - } - } - } - callback.onProgress(5); - return CODE_OK; - } else { - callback.onProgress(6); - return CODE_OK; - } - } else { - // the user does not want to be registered - callback.onProgress(7); - return CODE_OK; - } - } catch (Exception e) { - e.printStackTrace(); - this.e = e; - return null; - } - //return null; - } - - @Override - protected void onPostExecute(Integer errorCode) { - //d(TAG, "Thread/ProcessAsyncTask/onPostExecute/"+Thread.currentThread().getName()); - Context activityContext = this.activityContext.get(); - app.profileSaveFull(profile); - if (app.profile != null && profile.getId() == app.profile.getId()) { - app.profile = profile; - } - if (errorCode == null) { - // this means an Exception was thrown - callback.onError(activityContext, new AppError(TAG, 513, CODE_OTHER, e, apiResponse)); - return; - } - //Log.d(TAG, "Finishing"); - - - callback.onProgress(1); - - if (errorCode == CODE_OK) - callback.onSuccess(activityContext, profile); - else { - try { - // oh that's useless - throw new RuntimeException(stringErrorCode(app, errorCode, "")); - } catch (Exception e) { - callback.onError(activityContext, new AppError(TAG, 528, errorCode, e, (String) null)); - } - } - super.onPostExecute(errorCode); - } - } - - public void notifyAndReload() { - app.notifier.postAll(null); - app.saveConfig(); - SyncJob.schedule(app); - Intent i = new Intent(Intent.ACTION_MAIN) - .putExtra("reloadProfileId", -1); - app.sendBroadcast(i); - - Intent intent = new Intent(app.getContext(), WidgetTimetable.class); - intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - int[] ids = AppWidgetManager.getInstance(app).getAppWidgetIds(new ComponentName(app, WidgetTimetable.class)); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); - app.sendBroadcast(intent); - - intent = new Intent(app.getContext(), WidgetNotifications.class); - intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - ids = AppWidgetManager.getInstance(app).getAppWidgetIds(new ComponentName(app, WidgetNotifications.class)); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); - app.sendBroadcast(intent); - - intent = new Intent(app.getContext(), WidgetLuckyNumber.class); - intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - ids = AppWidgetManager.getInstance(app).getAppWidgetIds(new ComponentName(app, WidgetLuckyNumber.class)); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); - app.sendBroadcast(intent); - } - - /* _____ - / ____| - | (___ _ _ _ __ ___ - \___ \| | | | '_ \ / __| - ____) | |_| | | | | (__ - |_____/ \__, |_| |_|\___| - __/ | - |__*/ - // DataCallbacks that are *not* in Edziennik.sync need to be executed on the main thread. - // EdziennikInterface.sync is executed on a worker thread - // in Edziennik.sync/newCallback methods are called on a worker thread - - // callback passed to Edziennik.sync is executed on the main thread - // thus, callback which is in guiSync is also on the main thread - - /** - * Sync all Edziennik data. - * Used in services, login form and {@code guiSync} - *

- * May be ran on worker thread. - * {@link EdziennikInterface}.sync is ran always on worker thread. - * Every callback is ran on the UI thread. - * - * @param app - * @param activityContext - * @param callback - * @param profileId - */ - public void sync(@NonNull App app, @NonNull Context activityContext, @NonNull SyncCallback callback, int profileId) { - sync(app, activityContext, callback, profileId, (int[])null); - } - public void sync(@NonNull App app, @NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable int ... featureList) { - // empty: no unread notifications, all shared events (current+past) - // only if there is no data, and we are not logged in yet - SyncCallback newCallback = new SyncCallback() { - @Override - public void onLoginFirst(List profileList, LoginStore loginStore) { - new Handler(activityContext.getMainLooper()).post(() -> { - callback.onLoginFirst(profileList, loginStore); - }); - } - - @Override - public void onSuccess(Context activityContext, ProfileFull profileFull) { - new Handler(activityContext.getMainLooper()).post(() -> { - new ProcessAsyncTask(app, activityContext, callback, profileId, profileFull).execute(); - }); - } - - @Override - public void onError(Context activityContext, AppError error) { - new Handler(activityContext.getMainLooper()).post(() -> { - callback.onError(activityContext, error); - }); - } - - @Override - public void onProgress(int progressStep) { - new Handler(activityContext.getMainLooper()).post(() -> { - callback.onProgress(progressStep); - }); - } - - @Override - public void onActionStarted(int stringResId) { - new Handler(activityContext.getMainLooper()).post(() -> { - callback.onActionStarted(stringResId); - }); - } - }; - AsyncTask.execute(() -> { - ProfileFull profile = app.db.profileDao().getByIdNow(profileId); - if (profile != null) { - - if (profile.getArchived()) { - newCallback.onError(activityContext, new AppError(TAG, 678, CODE_PROFILE_ARCHIVED, profile.getName())); - return; - } - else if (profile.getDateYearEnd() != null && Date.getToday().getValue() >= profile.getDateYearEnd().getValue()) { - profile.setArchived(true); - app.notifier.add(new Notification(app.getContext(), app.getString(R.string.profile_auto_archiving_format, profile.getName(), profile.getDateYearEnd().getFormattedString())) - .withProfileData(profile.getId(), profile.getName()) - .withType(Notification.TYPE_AUTO_ARCHIVING) - .withFragmentRedirect(DRAWER_ITEM_HOME) - .withLongExtra("autoArchiving", 1L) - ); - app.notifier.postAll(null); - app.db.profileDao().add(profile); - if (App.profileId == profile.getId()) { - app.profile.setArchived(true); - } - newCallback.onSuccess(activityContext, profile); - return; - } - - registerEmpty = profile.getEmpty(); - oldLuckyNumber = profile.getLuckyNumber(); - getApi(app, profile.getLoginStoreType()).syncFeature(activityContext, newCallback, profile, featureList); - } else { - new Handler(activityContext.getMainLooper()).post(() -> callback.onError(activityContext, new AppError(TAG, 609, CODE_PROFILE_NOT_FOUND, (String) null))); - } - }); - } - - /* _____ _ _ _____ - / ____| | | |_ _| - | | __| | | | | | __ ___ __ __ _ _ __ _ __ ___ _ __ ___ - | | |_ | | | | | | \ \ /\ / / '__/ _` | '_ \| '_ \ / _ \ '__/ __| - | |__| | |__| |_| |_ \ V V /| | | (_| | |_) | |_) | __/ | \__ \ - \_____|\____/|_____| \_/\_/ |_| \__,_| .__/| .__/ \___|_| |___/ - | | | | - |_| |*/ - /** - * Sync all Edziennik data while showing a progress dialog. - * A wrapper for {@code sync} - * - * Does not switch between threads. - * All callbacks have to be executed on the UI thread. - * - * @param app an App singleton instance - * @param activity a parent activity - * @param profileId ID of the profile to sync - * @param dialogTitle a title of the dialog to show - * @param dialogText dialog's content - * @param successText a toast to show on success - */ - public void guiSync(@NonNull App app, @NonNull Activity activity, int profileId, @StringRes int dialogTitle, @StringRes int dialogText, @StringRes int successText) { - guiSync(app, activity, profileId, dialogTitle, dialogText, successText, (int[])null); - } - public void guiSync(@NonNull App app, @NonNull Activity activity, int profileId, @StringRes int dialogTitle, @StringRes int dialogText, @StringRes int successText, int ... featureList) { - MaterialDialog progressDialog = new MaterialDialog.Builder(activity) - .title(dialogTitle) - .content(dialogText) - .progress(false, PROFILE_MAX_PROGRESS, false) - .canceledOnTouchOutside(false) - .show(); - SyncCallback guiSyncCallback = new SyncCallback() { - @Override - public void onLoginFirst(List profileList, LoginStore loginStore) { - - } - - @Override - public void onSuccess(Context activityContext, ProfileFull profileFull) { - progressDialog.dismiss(); - Toast.makeText(activityContext, successText, Toast.LENGTH_SHORT).show(); - notifyAndReload(); - // profiles are saved automatically, during app.saveConfig in processFinish - /*if (activityContext instanceof MainActivity) { - //((MainActivity) activityContext).reloadCurrentFragment("GuiSync"); - ((MainActivity) activityContext).accountHeaderAddProfiles(); - }*/ - } - - @Override - public void onError(Context activityContext, AppError error) { - progressDialog.dismiss(); - guiShowErrorDialog((Activity) activityContext, error, R.string.sync_error_dialog_title); - } - - @Override - public void onProgress(int progressStep) { - progressDialog.incrementProgress(progressStep); - } - - @Override - public void onActionStarted(int stringResId) { - progressDialog.setContent(activity.getString(R.string.sync_action_format, activity.getString(stringResId))); - } - }; - app.apiEdziennik.sync(app, activity, guiSyncCallback, profileId, featureList); - } - /** - * Sync all Edziennik data in background. - * A callback is executed on main thread. - * A wrapper for {@code sync} - * - * @param app an App singleton instance - * @param activity a parent activity - * @param profileId ID of the profile to sync - * @param syncCallback a callback - * @param feature a feature to sync - */ - public void guiSyncSilent(@NonNull App app, @NonNull Activity activity, int profileId, SyncCallback syncCallback, int feature) { - SyncCallback guiSyncCallback = new SyncCallback() { - @Override - public void onLoginFirst(List profileList, LoginStore loginStore) { - - } - - @Override - public void onSuccess(Context activityContext, ProfileFull profileFull) { - notifyAndReload(); - syncCallback.onSuccess(activityContext, profileFull); - } - - @Override - public void onError(Context activityContext, AppError error) { - syncCallback.onError(activityContext, error); - } - - @Override - public void onProgress(int progressStep) { - syncCallback.onProgress(progressStep); - } - - @Override - public void onActionStarted(int stringResId) { - syncCallback.onActionStarted(stringResId); - } - }; - app.apiEdziennik.sync(app, activity, guiSyncCallback, profileId, feature == FEATURE_ALL ? null : new int[]{feature}); - } - - /** - * Show a dialog allowing the user to choose which features to sync. - * Handles everything including pre-selecting the features basing on the current fragment. - * - * Will execute {@code sync} after the selection is made. - * - * A normal progress dialog is shown during the sync. - * - * @param app an App singleton instance - * @param activity a parent activity - * @param profileId ID of the profile to sync - * @param dialogTitle a title of the dialog to show - * @param dialogText dialog's content - * @param successText a toast to show on success - * @param currentFeature a feature id representing the currently opened fragment or caller - */ - public void guiSyncFeature(@NonNull App app, - @NonNull Activity activity, - int profileId, - @StringRes int dialogTitle, - @StringRes int dialogText, - @StringRes int successText, - int currentFeature) { - - String[] items = new 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_attendances), - app.getString(R.string.title_messages_inbox_single), - app.getString(R.string.title_messages_sent_single), - app.getString(R.string.menu_announcements) - }; - int[] itemsIds = new int[]{ - FEATURE_TIMETABLE, - FEATURE_AGENDA, - FEATURE_GRADES, - FEATURE_HOMEWORKS, - FEATURE_NOTICES, - FEATURE_ATTENDANCES, - FEATURE_MESSAGES_INBOX, - FEATURE_MESSAGES_OUTBOX, - FEATURE_ANNOUNCEMENTS - }; - int[] selectedIndices; - if (currentFeature == FEATURE_ALL) { - selectedIndices = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8}; - } - else { - selectedIndices = new int[]{Arrays.binarySearch(itemsIds, currentFeature)}; - } - - MaterialDialog dialog = new MaterialDialog.Builder(activity) - .title(R.string.sync_feature_title) - .content(R.string.sync_feature_text) - .positiveText(R.string.ok) - .negativeText(R.string.cancel) - .neutralText(R.string.sync_feature_all) - .items(items) - .itemsIds(itemsIds) - .itemsCallbackMultiChoice(ArrayUtils.toWrapperArray(selectedIndices), (dialog1, which, text) -> { - dialog1.getActionButton(DialogAction.POSITIVE).setEnabled(which.length > 0); - return true; - }) - .alwaysCallMultiChoiceCallback() - .onPositive(((dialog1, which) -> { - List featureList = new ArrayList<>(); - for (int i: dialog1.getSelectedIndices()) { - featureList.add(itemsIds[i]); - } - guiSync(app, activity, profileId, dialogTitle, dialogText, successText, ArrayUtils.toPrimitiveArray(featureList)); - })) - .onNeutral(((dialog1, which) -> { - guiSync(app, activity, profileId, dialogTitle, dialogText, successText); - })) - .show(); - - - - } - - public void guiShowArchivedDialog(Activity activity, String profileName) { - new MaterialDialog.Builder(activity) - .title(R.string.profile_archived_dialog_title) - .content(activity.getString(R.string.profile_archived_dialog_text_format, profileName)) - .positiveText(R.string.ok) - .onPositive(((dialog, which) -> dialog.dismiss())) - .autoDismiss(false) - .show(); - } - - /* _____ _ _ _____ - / ____| | | |_ _| - | | __| | | | | | ___ _ __ _ __ ___ _ __ ___ - | | |_ | | | | | | / _ \ '__| '__/ _ \| '__/ __| - | |__| | |__| |_| |_ | __/ | | | | (_) | | \__ \ - \_____|\____/|_____| \___|_| |_| \___/|_| |__*/ - /** - * Used for reporting an exception somewhere in the code that is not part of Edziennik APIs. - * - * @param activity a parent activity - * @param errorLine the line of code where the error occurred - * @param e an Exception object - */ - public void guiReportException(Activity activity, int errorLine, Exception e) { - guiReportError(activity, new AppError(TAG, errorLine, CODE_OTHER, "Błąd wewnętrzny aplikacji ("+errorLine+")", null, null, e, null), null); - } - - public void guiShowErrorDialog(Activity activity, @NonNull AppError error, @StringRes int dialogTitle) { - if (error.errorCode == CODE_PROFILE_ARCHIVED) { - guiShowArchivedDialog(activity, error.errorText); - return; - } - error.changeIfCodeOther(); - new MaterialDialog.Builder(activity) - .title(dialogTitle) - .content(error.asReadableString(activity)) - .positiveText(R.string.ok) - .onPositive(((dialog, which) -> dialog.dismiss())) - .neutralText(R.string.sync_error_dialog_report_button) - .onNeutral(((dialog, which) -> { - guiReportError(activity, error, dialog); - })) - .autoDismiss(false) - .show(); - } - public void guiShowErrorSnackbar(MainActivity activity, @NonNull AppError error) { - if (error.errorCode == CODE_PROFILE_ARCHIVED) { - guiShowArchivedDialog(activity, error.errorText); - return; - } - - // TODO: 2019-08-28 - IconicsDrawable icon = new IconicsDrawable(activity) - .icon(CommunityMaterial.Icon.cmd_alert_circle); - sizeDp(icon, 20); - colorInt(icon, Themes.INSTANCE.getPrimaryTextColor(activity)); - - error.changeIfCodeOther(); - CafeBar.builder(activity) - .to(activity.findViewById(R.id.coordinator)) - .content(error.asReadableString(activity)) - .icon(icon) - .positiveText(R.string.more) - .positiveColor(0xff4caf50) - .negativeText(R.string.ok) - .negativeColor(0x66ffffff) - .onPositive((cafeBar -> guiReportError(activity, error, null))) - .onNegative((cafeBar -> cafeBar.dismiss())) - .autoDismiss(false) - .swipeToDismiss(true) - .floating(true) - .show(); - } - public void guiReportError(Activity activity, AppError error, @Nullable MaterialDialog parentDialogToDisableNeutral) { - String errorDetails = error.getDetails(activity); - String htmlErrorDetails = ""+errorDetails+""; - htmlErrorDetails = htmlErrorDetails.replaceAll(activity.getPackageName(), ""+activity.getPackageName()+""); - htmlErrorDetails = htmlErrorDetails.replaceAll("\n", "
"); - - new MaterialDialog.Builder(activity) - .title(R.string.sync_report_dialog_title) - .content(Html.fromHtml(htmlErrorDetails)) - .typeface(null, "RobotoMono-Regular.ttf") - .negativeText(R.string.close) - .onNegative(((dialog1, which1) -> dialog1.dismiss())) - .neutralText(R.string.copy_to_clipboard) - .onNeutral((dialog1, which1) -> { - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(CLIPBOARD_SERVICE); - if (clipboard != null) { - ClipData clip = ClipData.newPlainText("Error report", errorDetails); - clipboard.setPrimaryClip(clip); - Toast.makeText(activity, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - }) - .autoDismiss(false) - .positiveText(R.string.sync_report_dialog_button) - .checkBoxPromptRes(R.string.sync_report_dialog_include_api_response, true, null) - .onPositive(((dialog1, which1) -> AsyncTask.execute(() -> error.getApiResponse(activity, apiResponse -> { - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?report", "Edziennik/Report") - .setBodyParameter("base64_encoded", Base64.encodeToString(errorDetails.getBytes(), Base64.DEFAULT)) - .setBodyParameter("api_response", dialog1.isPromptCheckBoxChecked() ? Base64.encodeToString(apiResponse.getBytes(), Base64.DEFAULT) : "VW5jaGVja2Vk"/*Unchecked*/) - .run((e, result) -> { - new Handler(activity.getMainLooper()).post(() -> { - if (result != null) - { - if (result.get("success").getAsBoolean()) { - Toast.makeText(activity, activity.getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show(); - dialog1.getActionButton(DialogAction.POSITIVE).setEnabled(false); - if (parentDialogToDisableNeutral != null) - parentDialogToDisableNeutral.getActionButton(DialogAction.NEUTRAL).setEnabled(false); - } - else { - Toast.makeText(activity, activity.getString(R.string.crash_report_cannot_send) + ": " + result.get("reason").getAsString(), Toast.LENGTH_LONG).show(); - } - } - else - { - Toast.makeText(activity, activity.getString(R.string.crash_report_cannot_send)+" brak internetu", Toast.LENGTH_LONG).show(); - } - }); - }); - })))) - .show(); - } - - /** - * A method that displays a dialog allowing the user to report an error that has occurred. - * - * @param activity a parent activity - * @param errorCode self-explanatory - * @param errorText additional error information, that replaces text based on {@code errorCode} if it's {@code CODE_OTHER} - * @param throwable a {@link Throwable} containing the error details - * @param apiResponse response of the Edziennik API - * @param parentDialogToDisableNeutral if not null, an instance of {@link MaterialDialog} in which the neutral button should be disabled after submitting an error report - */ - public void guiReportError(Activity activity, int errorCode, String errorText, Throwable throwable, String apiResponse, @Nullable MaterialDialog parentDialogToDisableNeutral) { - // build a string containing the stack trace and the device name + user's registration data - String contentPlain = "Application Internal Error "+stringErrorType(errorCode)+":\n"+stringErrorCode(activity, errorCode, "")+"\n"+errorText+"\n\n"; - contentPlain += Log.getStackTraceString(throwable); - String content = ""+contentPlain+""; - content = content.replaceAll(activity.getPackageName(), ""+activity.getPackageName()+""); - content = content.replaceAll("\n", "
"); - - contentPlain += "\n"+Build.MANUFACTURER+"\n"+Build.BRAND+"\n"+Build.MODEL+"\n"+Build.DEVICE+"\n"; - if (app.profile != null && app.profile.getRegistration() == REGISTRATION_ENABLED) { - contentPlain += "U: "+app.profile.getUsernameId()+"\nS: "+ app.profile.getStudentNameLong() +"\nT: "+app.profile.loginStoreType()+"\n"; - } - contentPlain += BuildConfig.VERSION_NAME+" "+BuildConfig.BUILD_TYPE+"\nAndroid "+Build.VERSION.RELEASE; - - d(TAG, contentPlain); - d(TAG, apiResponse == null ? "API Response = null" : apiResponse); - - - // show a dialog containing the error details in HTML - String finalContentPlain = contentPlain; - new MaterialDialog.Builder(activity) - .title(R.string.sync_report_dialog_title) - .content(Html.fromHtml(content)) - .typeface(null, "RobotoMono-Regular.ttf") - .negativeText(R.string.close) - .onNegative(((dialog1, which1) -> dialog1.dismiss())) - .neutralText(R.string.copy_to_clipboard) - .onNeutral((dialog1, which1) -> { - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(CLIPBOARD_SERVICE); - if (clipboard != null) { - ClipData clip = ClipData.newPlainText("Error report", finalContentPlain); - clipboard.setPrimaryClip(clip); - Toast.makeText(activity, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - }) - .autoDismiss(false) - .positiveText(R.string.sync_report_dialog_button) - .checkBoxPromptRes(R.string.sync_report_dialog_include_api_response, true, null) - .onPositive(((dialog1, which1) -> { - // send the error report - new ServerRequest(app, app.requestScheme + APP_URL + "main.php?report", "Edziennik/Report") - .setBodyParameter("base64_encoded", Base64.encodeToString(finalContentPlain.getBytes(), Base64.DEFAULT)) - .setBodyParameter("api_response", dialog1.isPromptCheckBoxChecked() ? apiResponse == null ? Base64.encodeToString("NULL XD".getBytes(), Base64.DEFAULT) : Base64.encodeToString(apiResponse.getBytes(), Base64.DEFAULT) : "VW5jaGVja2Vk"/*Unchecked*/) - .run((e, result) -> { - new Handler(Looper.getMainLooper()).post(() -> { - if (result != null) - { - if (result.get("success").getAsBoolean()) { - Toast.makeText(activity, activity.getString(R.string.crash_report_sent), Toast.LENGTH_SHORT).show(); - dialog1.getActionButton(DialogAction.POSITIVE).setEnabled(false); - if (parentDialogToDisableNeutral != null) - parentDialogToDisableNeutral.getActionButton(DialogAction.NEUTRAL).setEnabled(false); - } - else { - Toast.makeText(activity, activity.getString(R.string.crash_report_cannot_send) + ": " + result.get("reason").getAsString(), Toast.LENGTH_LONG).show(); - } - } - else - { - Toast.makeText(activity, activity.getString(R.string.crash_report_cannot_send)+" JsonObject equals null", Toast.LENGTH_LONG).show(); - } - }); - }); - })) - .show(); - } - - /* _____ __ _ _ _ - | __ \ / _(_) | | | - | |__) | __ ___ | |_ _| | ___ _ __ ___ _ __ ___ _____ ____ _| | - | ___/ '__/ _ \| _| | |/ _ \ | '__/ _ \ '_ ` _ \ / _ \ \ / / _` | | - | | | | | (_) | | | | | __/ | | | __/ | | | | | (_) \ V / (_| | | - |_| |_| \___/|_| |_|_|\___| |_| \___|_| |_| |_|\___/ \_/ \__,_|*/ - public void guiRemoveProfile(MainActivity activity, int profileId, String profileName) { - new MaterialDialog.Builder(activity) - .title(R.string.profile_menu_remove_confirm) - .content(activity.getString(R.string.profile_menu_remove_confirm_text_format, profileName, profileName)) - .positiveText(R.string.remove) - .negativeText(R.string.cancel) - .onPositive(((dialog, which) -> { - AsyncTask.execute(() -> { - removeProfile(profileId); - activity.runOnUiThread(() -> { - //activity.drawer.loadItem(DRAWER_ITEM_HOME, null, "ProfileRemoving"); - //activity.recreate(DRAWER_ITEM_HOME); - activity.reloadTarget(); - Toast.makeText(activity, "Profil został usunięty.", Toast.LENGTH_LONG).show(); - }); - }); - })) - .show(); - } - public void removeProfile(int profileId) { - Profile profileObject = app.db.profileDao().getByIdNow(profileId); - if (profileObject == null) - return; - 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); - - int loginStoreId = profileObject.getLoginStoreId(); - List 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); - - List toRemove = new ArrayList<>(); - for (Notification notification: app.appConfig.notifications) { - if (notification.profileId == profileId) { - toRemove.add(notification); - } - } - app.appConfig.notifications.removeAll(toRemove); - - app.profile = null; - App.profileId = -1; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/Iuczniowie.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/Iuczniowie.java deleted file mode 100644 index ca74dee7..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/Iuczniowie.java +++ /dev/null @@ -1,1704 +0,0 @@ -package pl.szczodrzynski.edziennik.api; - -import android.content.Context; -import android.graphics.Color; -import android.os.AsyncTask; -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.util.Pair; - -import com.crashlytics.android.Crashlytics; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import im.wangchao.mhttp.Request; -import im.wangchao.mhttp.Response; -import im.wangchao.mhttp.body.MediaTypeUtils; -import im.wangchao.mhttp.callback.JsonArrayCallbackHandler; -import im.wangchao.mhttp.callback.JsonCallbackHandler; -import im.wangchao.mhttp.callback.TextCallbackHandler; -import okhttp3.Cookie; -import okhttp3.HttpUrl; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; -import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; -import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; -import pl.szczodrzynski.edziennik.datamodels.Announcement; -import pl.szczodrzynski.edziennik.datamodels.Attendance; -import pl.szczodrzynski.edziennik.datamodels.Event; -import pl.szczodrzynski.edziennik.datamodels.Grade; -import pl.szczodrzynski.edziennik.datamodels.Lesson; -import pl.szczodrzynski.edziennik.datamodels.LessonChange; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.LuckyNumber; -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.MessageFull; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipient; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipientFull; -import pl.szczodrzynski.edziennik.datamodels.Metadata; -import pl.szczodrzynski.edziennik.datamodels.Notice; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.datamodels.Subject; -import pl.szczodrzynski.edziennik.datamodels.Teacher; -import pl.szczodrzynski.edziennik.datamodels.Team; -import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Endpoint; -import pl.szczodrzynski.edziennik.models.Time; -import pl.szczodrzynski.edziennik.models.Week; - -import static pl.szczodrzynski.edziennik.api.AppError.CODE_INVALID_LOGIN; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_INVALID_SCHOOL_NAME; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_MAINTENANCE; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_RELEASED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.LessonChange.TYPE_CANCELLED; -import static pl.szczodrzynski.edziennik.datamodels.LessonChange.TYPE_CHANGE; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_DELETED; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_RECEIVED; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_SENT; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_NEGATIVE; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_NEUTRAL; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_POSITIVE; -import static pl.szczodrzynski.edziennik.utils.Utils.crc16; -import static pl.szczodrzynski.edziennik.utils.Utils.crc32; -import static pl.szczodrzynski.edziennik.utils.Utils.d; -import static pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue; - -public class Iuczniowie implements EdziennikInterface { - public Iuczniowie(App app) { - this.app = app; - } - - private static final String TAG = "api.Iuczniowie"; - private static String IDZIENNIK_URL = "https://iuczniowie.progman.pl/idziennik"; - private static final String userAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36"; - - private App app; - private Context activityContext = null; - private SyncCallback callback = null; - private int profileId = -1; - private Profile profile = null; - private LoginStore loginStore = null; - private boolean fullSync = true; - private Date today = Date.getToday(); - private List targetEndpoints = new ArrayList<>(); - - // PROGRESS - private static final int PROGRESS_LOGIN = 10; - private int PROGRESS_COUNT = 1; - private int PROGRESS_STEP = (90/PROGRESS_COUNT); - - private int onlyFeature = FEATURE_ALL; - - private List teamList; - private List teacherList; - private List subjectList; - private List lessonList; - private List lessonChangeList; - private List gradeList; - private List eventList; - private List noticeList; - private List attendanceList; - private List announcementList; - private List messageList; - private List messageRecipientList; - private List messageRecipientIgnoreList; - private List metadataList; - private List messageMetadataList; - - private static boolean fakeLogin = false; - private String lastLogin = ""; - private long lastLoginTime = -1; - private String lastResponse = null; - private String loginSchoolName = null; - private String loginUsername = null; - private String loginPassword = null; - private String loginBearerToken = null; - private int loginRegisterId = -1; - private int loginSchoolYearId = -1; - private String loginStudentId = null; - private int teamClassId = -1; - - private boolean prepare(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - this.activityContext = activityContext; - this.callback = callback; - this.profileId = profileId; - // here we must have a login store: either with a correct ID or -1 - // there may be no profile and that's when onLoginFirst happens - this.profile = profile; - this.loginStore = loginStore; - this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext); - this.today = Date.getToday(); - - this.loginSchoolName = loginStore.getLoginData("schoolName", ""); - this.loginUsername = loginStore.getLoginData("username", ""); - this.loginPassword = loginStore.getLoginData("password", ""); - if (loginSchoolName.equals("") || loginUsername.equals("") || loginPassword.equals("")) { - finishWithError(new AppError(TAG, 162, CODE_INVALID_LOGIN, "Login field is empty")); - return false; - } - fakeLogin = BuildConfig.DEBUG && loginUsername.startsWith("FAKE"); - IDZIENNIK_URL = fakeLogin ? "http://szkolny.eu/idziennik" : "https://iuczniowie.progman.pl/idziennik"; - - teamList = profileId == -1 ? new ArrayList<>() : app.db.teamDao().getAllNow(profileId); - teacherList = profileId == -1 ? new ArrayList<>() : app.db.teacherDao().getAllNow(profileId); - subjectList = profileId == -1 ? new ArrayList<>() : app.db.subjectDao().getAllNow(profileId); - lessonList = new ArrayList<>(); - lessonChangeList = new ArrayList<>(); - gradeList = new ArrayList<>(); - eventList = new ArrayList<>(); - noticeList = new ArrayList<>(); - attendanceList = new ArrayList<>(); - announcementList = new ArrayList<>(); - messageList = new ArrayList<>(); - messageRecipientList = new ArrayList<>(); - messageRecipientIgnoreList = new ArrayList<>(); - metadataList = new ArrayList<>(); - messageMetadataList = new ArrayList<>(); - - return true; - } - - @Override - public void sync(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - if (!prepare(activityContext, callback, profileId, profile, loginStore)) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - targetEndpoints.add("LuckyNumberAndSemesterDates"); - targetEndpoints.add("Timetable"); - targetEndpoints.add("Grades"); - targetEndpoints.add("PropositionGrades"); - targetEndpoints.add("Exams"); - targetEndpoints.add("Notices"); - targetEndpoints.add("Announcements"); - targetEndpoints.add("Attendances"); - targetEndpoints.add("MessagesInbox"); - targetEndpoints.add("MessagesOutbox"); - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - @Override - public void syncFeature(@NonNull Context activityContext, @NonNull SyncCallback callback, @NonNull ProfileFull profile, int ... featureList) { - if (featureList == null) { - sync(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile)); - return; - } - if (!prepare(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - if (featureList.length == 1) - onlyFeature = featureList[0]; - targetEndpoints.add("LuckyNumberAndSemesterDates"); - for (int feature: featureList) { - switch (feature) { - case FEATURE_TIMETABLE: - targetEndpoints.add("Timetable"); - break; - case FEATURE_AGENDA: - targetEndpoints.add("Exams"); - break; - case FEATURE_GRADES: - targetEndpoints.add("Grades"); - targetEndpoints.add("PropositionGrades"); - break; - case FEATURE_HOMEWORKS: - targetEndpoints.add("Homeworks"); - break; - case FEATURE_NOTICES: - targetEndpoints.add("Notices"); - break; - case FEATURE_ATTENDANCES: - targetEndpoints.add("Attendances"); - break; - case FEATURE_MESSAGES_INBOX: - targetEndpoints.add("MessagesInbox"); - break; - case FEATURE_MESSAGES_OUTBOX: - targetEndpoints.add("MessagesOutbox"); - break; - case FEATURE_ANNOUNCEMENTS: - targetEndpoints.add("Announcements"); - break; - } - } - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - - private void begin() { - List cookieList = app.cookieJar.loadForRequest(HttpUrl.get(IDZIENNIK_URL)); - for (Cookie cookie: cookieList) { - if (cookie.name().equalsIgnoreCase("Bearer")) { - loginBearerToken = cookie.value(); - } - } - loginStudentId = profile.getStudentData("studentId", null); - loginSchoolYearId = profile.getStudentData("schoolYearId", -1); - loginRegisterId = profile.getStudentData("registerId", -1); - - if (loginRegisterId == -1) { - finishWithError(new AppError(TAG, 212, CODE_OTHER, app.getString(R.string.error_register_id_not_found), "loginRegisterId == -1")); - return; - } - if (loginSchoolYearId == -1) { - finishWithError(new AppError(TAG, 216, CODE_OTHER, app.getString(R.string.error_school_year_not_found), "loginSchoolYearId == -1")); - return; - } - if (loginStudentId == null) { - if (lastResponse == null) { - lastLoginTime = -1; - lastLogin = ""; - finishWithError(new AppError(TAG, 223, CODE_OTHER, app.getString(R.string.error_student_id_not_found), "loginStudentId == null && lastResponse == null")); - return; - } - Matcher selectMatcher = Pattern.compile("", Pattern.DOTALL).matcher(lastResponse); - if (!selectMatcher.find()) { - finishWithError(new AppError(TAG, 228, CODE_OTHER, app.getString(R.string.error_register_id_not_found), lastResponse)); - return; - } - Matcher idMatcher = Pattern.compile(".*?", Pattern.DOTALL).matcher(selectMatcher.group(0)); - while (idMatcher.find()) { - loginStudentId = idMatcher.group(1); - profile.putStudentData("studentId", loginStudentId); - } - } - - this.attendancesMonth = today.month; - this.attendancesYear = today.year; - this.attendancesPrevMonthChecked = false; - this.examsMonth = today.month; - this.examsYear = today.year; - this.examsMonthsChecked = 0; - this.examsNextMonthChecked = false; - - callback.onProgress(PROGRESS_LOGIN); - - r("get", null); - } - - private void r(String type, String endpoint) { - // endpoint == null when beginning - if (endpoint == null) - endpoint = targetEndpoints.get(0); - int index = -1; - for (String request: targetEndpoints) { - index++; - if (request.equals(endpoint)) { - break; - } - } - if (type.equals("finish")) { - // called when finishing the action - callback.onProgress(PROGRESS_STEP); - index++; - } - d(TAG, "Called r("+type+", "+endpoint+"). Getting "+targetEndpoints.get(index)); - switch (targetEndpoints.get(index)) { - case "LuckyNumberAndSemesterDates": - getLuckyNumberAndSemesterDates(); - break; - case "Timetable": - getTimetable(); - break; - case "Grades": - getGrades(); - break; - case "PropositionGrades": - getPropositionGrades(); - break; - case "Exams": - getExams(); - break; - case "Notices": - getNotices(); - break; - case "Announcements": - getAnnouncements(); - break; - case "Attendances": - getAttendances(); - break; - case "MessagesInbox": - getMessagesInbox(); - break; - case "MessagesOutbox": - getMessagesOutbox(); - break; - case "Finish": - finish(); - break; - } - } - private void saveData() { - if (teamList.size() > 0) { - //app.db.teamDao().clear(profileId); - app.db.teamDao().addAll(teamList); - } - if (teacherList.size() > 0) - app.db.teacherDao().addAll(teacherList); - if (subjectList.size() > 0) - app.db.subjectDao().addAll(subjectList); - if (lessonList.size() > 0) { - app.db.lessonDao().clear(profileId); - app.db.lessonDao().addAll(lessonList); - } - if (lessonChangeList.size() > 0) - app.db.lessonChangeDao().addAll(lessonChangeList); - if (gradeList.size() > 0) { - app.db.gradeDao().clear(profileId); - app.db.gradeDao().addAll(gradeList); - } - if (eventList.size() > 0) { - app.db.eventDao().removeFuture(profileId, today); - app.db.eventDao().addAll(eventList); - } - if (noticeList.size() > 0) { - app.db.noticeDao().clear(profileId); - app.db.noticeDao().addAll(noticeList); - } - if (attendanceList.size() > 0) - app.db.attendanceDao().addAll(attendanceList); - if (announcementList.size() > 0) - app.db.announcementDao().addAll(announcementList); - if (messageList.size() > 0) - app.db.messageDao().addAllIgnore(messageList); - if (messageRecipientList.size() > 0) - app.db.messageRecipientDao().addAll(messageRecipientList); - if (messageRecipientIgnoreList.size() > 0) - app.db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList); - if (metadataList.size() > 0) - app.db.metadataDao().addAllIgnore(metadataList); - if (messageMetadataList.size() > 0) - app.db.metadataDao().setSeen(messageMetadataList); - } - private void finish() { - try { - saveData(); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 363, CODE_OTHER, app.getString(R.string.sync_error_saving_data), null, null, e, null)); - } - if (fullSync) { - profile.setLastFullSync(System.currentTimeMillis()); - fullSync = false; - } - profile.setEmpty(false); - callback.onSuccess(activityContext, new ProfileFull(profile, loginStore)); - } - private void finishWithError(AppError error) { - try { - saveData(); - } - catch (Exception e) { - Crashlytics.logException(e); - } - callback.onError(activityContext, error); - } - - /* _ _ - | | (_) - | | ___ __ _ _ _ __ - | | / _ \ / _` | | '_ \ - | |___| (_) | (_| | | | | | - |______\___/ \__, |_|_| |_| - __/ | - |__*/ - private void login(@NonNull LoginCallback loginCallback) { - if (lastLogin.equals(loginSchoolName +":"+ loginUsername) - && System.currentTimeMillis() - lastLoginTime < 5 * 60 * 1000 - && profile != null) { // less than 5 minutes, use the already logged in account - loginCallback.onSuccess(); - return; - } - app.cookieJar.clearForDomain("iuczniowie.progman.pl"); - callback.onActionStarted(R.string.sync_action_logging_in); - Request.builder() - .url(IDZIENNIK_URL +"/login.aspx") - .userAgent(userAgent) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 389, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data1, Response response1) { - if (data1 == null || data1.equals("")) { // for safety - finishWithError(new AppError(TAG, 395, CODE_MAINTENANCE, response1)); - return; - } - //Log.d(TAG, "r:"+data); - Request.Builder builder = Request.builder() - .url(IDZIENNIK_URL +"/login.aspx") - .userAgent(userAgent) - //.withClient(app.httpLazy) - .addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/png,*/*;q=0.8") - .addHeader("Cache-Control", "max-age=0") - .addHeader("Origin", "https://iuczniowie.progman.pl") - .addHeader("Referer", "https://iuczniowie.progman.pl/idziennik/login.aspx") - .addHeader("Upgrade-Insecure-Requests", "1") - .contentType(MediaTypeUtils.APPLICATION_FORM) - .addParameter("ctl00$ContentPlaceHolder$nazwaPrzegladarki", userAgent) - .addParameter("ctl00$ContentPlaceHolder$NazwaSzkoly", loginSchoolName) - .addParameter("ctl00$ContentPlaceHolder$UserName", loginUsername) - .addParameter("ctl00$ContentPlaceHolder$Password", loginPassword) - .addParameter("ctl00$ContentPlaceHolder$captcha", "") - .addParameter("ctl00$ContentPlaceHolder$Logowanie", "Zaloguj") - .post(); - - // extract hidden form fields __VIEWSTATE __VIEWSTATEGENERATOR __EVENTVALIDATION - //Pattern pattern = Pattern.compile("<.+?name=\"__VIEWSTATE\".+?value=\"([A-z0-9+/=]+)\".+?name=\"__VIEWSTATEGENERATOR\".+?value=\"([A-z0-9+/=]+)\".+?name=\"__EVENTVALIDATION\".+?value=\"([A-z0-9+/=]+)\".+?>", Pattern.DOTALL); - //Pattern pattern = Pattern.compile("]* name=[\"']([^'\"]*)|)(?=[^>]* value=[\"']([^'\"]*)|)", Pattern.DOTALL); - Pattern pattern = Pattern.compile("", Pattern.DOTALL); - Matcher matcher = pattern.matcher(data1); - while (matcher.find()) { - //Log.d(TAG, "Match: "+matcher.group(1)+"="+matcher.group(2)); - builder.addParameter(matcher.group(1), matcher.group(2)); - } - - builder.callback(new TextCallbackHandler() { - @Override - public void onSuccess(String data2, Response response2) { - callback.onProgress(PROGRESS_LOGIN); - Pattern errorPattern = Pattern.compile("id=\"spanErrorMessage\">(.*?)", Pattern.DOTALL).matcher(data2); - if (!selectMatcher.find()) { - finishWithError(new AppError(TAG, 473, CODE_OTHER, app.getString(R.string.error_register_id_not_found), response2, data2)); - return; - } - Log.d(TAG, "g" + selectMatcher.group(0)); - Matcher idMatcher = Pattern.compile("(.+?)\\s(.+?)\\s*\\((.+?),\\s*(.+?)\\)", Pattern.DOTALL).matcher(selectMatcher.group(0)); - while (idMatcher.find()) { - if (loginRegisterId != Integer.parseInt(idMatcher.group(1))) - continue; - String teamClassName = idMatcher.group(4) + " " + idMatcher.group(5); - teamClassId = crc16(teamClassName.getBytes()); - app.db.teamDao().add(new Team( - profileId, - teamClassId, - teamClassName, - 1, - loginSchoolName+":"+teamClassName, - -1 - )); - } - loginCallback.onSuccess(); - return; - } - try { - Matcher yearMatcher = Pattern.compile("name=\"ctl00\\$dxComboRokSzkolny\".+?selected=\"selected\".*?value=\"([0-9]+)\"", Pattern.DOTALL).matcher(data2); - if (yearMatcher.find()) { - try { - loginSchoolYearId = Integer.parseInt(yearMatcher.group(1)); - } catch (Exception ex) { - finishWithError(new AppError(TAG, 501, CODE_OTHER, response2, ex, data2)); - return; - } - } else { - if (data2.contains("Hasło dostępu do systemu wygasło")) { - finishWithError(new AppError(TAG, 504, CODE_OTHER, app.getString(R.string.error_must_change_password), response2, data2)); - return; - } - finishWithError(new AppError(TAG, 507, CODE_OTHER, app.getString(R.string.error_school_year_not_found), response2, data2)); - return; - } - - List studentIds = new ArrayList<>(); - List registerIds = new ArrayList<>(); - List studentNamesLong = new ArrayList<>(); - List studentNamesShort = new ArrayList<>(); - List studentTeams = new ArrayList<>(); - - Matcher selectMatcher = Pattern.compile("", Pattern.DOTALL).matcher(data2); - if (!selectMatcher.find()) { - finishWithError(new AppError(TAG, 519, CODE_OTHER, app.getString(R.string.error_register_id_not_found), response2, data2)); - return; - } - Log.d(TAG, "g" + selectMatcher.group(0)); - Matcher idMatcher = Pattern.compile("(.+?)\\s(.+?)\\s*\\((.+?),\\s*(.+?)\\)", Pattern.DOTALL).matcher(selectMatcher.group(0)); - while (idMatcher.find()) { - registerIds.add(Integer.parseInt(idMatcher.group(1))); - String studentId = idMatcher.group(2); - String studentFirstName = idMatcher.group(3); - String studentLastName = idMatcher.group(4); - String teamClassName = idMatcher.group(5) + " " + idMatcher.group(6); - studentIds.add(studentId); - studentNamesLong.add(studentFirstName + " " + studentLastName); - studentNamesShort.add(studentFirstName + " " + studentLastName.charAt(0) + "."); - studentTeams.add(teamClassName); - } - Collections.reverse(studentIds); - Collections.reverse(registerIds); - Collections.reverse(studentNamesLong); - Collections.reverse(studentNamesShort); - Collections.reverse(studentTeams); - - List profileList = new ArrayList<>(); - for (int index = 0; index < registerIds.size(); index++) { - Profile newProfile = new Profile(); - newProfile.setStudentNameLong(studentNamesLong.get(index)); - newProfile.setStudentNameShort(studentNamesShort.get(index)); - newProfile.setName(newProfile.getStudentNameLong()); - newProfile.setSubname(loginUsername); - newProfile.setEmpty(true); - newProfile.setLoggedIn(true); - newProfile.putStudentData("studentId", studentIds.get(index)); - newProfile.putStudentData("registerId", registerIds.get(index)); - newProfile.putStudentData("schoolYearId", loginSchoolYearId); - profileList.add(newProfile); - } - - callback.onLoginFirst(profileList, loginStore); - } catch (Exception ex) { - finishWithError(new AppError(TAG, 557, CODE_OTHER, response2, ex, data2)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 563, CODE_OTHER, response, throwable, data1)); - } - }).build().enqueue(); - } - }).build().enqueue(); - } - - /* _ _ _ _ _ _ _ - | | | | | | ___ | | | | | | - | |__| | ___| |_ __ ___ _ __ ___ ( _ ) ___ __ _| | | |__ __ _ ___| | _____ - | __ |/ _ \ | '_ \ / _ \ '__/ __| / _ \/\ / __/ _` | | | '_ \ / _` |/ __| |/ / __| - | | | | __/ | |_) | __/ | \__ \ | (_> < | (_| (_| | | | |_) | (_| | (__| <\__ \ - |_| |_|\___|_| .__/ \___|_| |___/ \___/\/ \___\__,_|_|_|_.__/ \__,_|\___|_|\_\___/ - | | - |*/ - private interface ApiRequestCallback { - void onSuccess(JsonObject result, Response response); - } - private void apiRequest(Request.Builder requestBuilder, ApiRequestCallback apiRequestCallback) { - requestBuilder.callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 578, CODE_MAINTENANCE, response)); - return; - } - try { - apiRequestCallback.onSuccess(data, response); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 583, CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 592, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - private interface ApiRequestArrayCallback { - void onSuccess(JsonArray result, Response response); - } - private void apiRequestArray(Request.Builder requestBuilder, ApiRequestArrayCallback apiRequestArrayCallback) { - requestBuilder.callback(new JsonArrayCallbackHandler() { - @Override - public void onSuccess(JsonArray data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 603, CODE_MAINTENANCE, response)); - return; - } - try { - apiRequestArrayCallback.onSuccess(data, response); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 610, CODE_OTHER, response, e, data.toString())); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 616, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private Subject searchSubject(String name, long id, String shortName) { - Subject subject; - if (id == -1) - subject = Subject.getByName(subjectList, name); - else - subject = Subject.getById(subjectList, id); - - if (subject == null) { - subject = new Subject(profileId, (id == -1 ? crc16(name.getBytes()) : id), name, shortName); - subjectList.add(subject); - } - return subject; - } - - private Teacher searchTeacher(String firstName, String lastName) { - Teacher teacher = Teacher.getByFullName(teacherList, firstName+" "+lastName); - return validateTeacher(teacher, firstName, lastName); - } - private Teacher searchTeacher(char firstNameChar, String lastName) { - Teacher teacher = Teacher.getByShortName(teacherList, firstNameChar+"."+lastName); - return validateTeacher(teacher, String.valueOf(firstNameChar), lastName); - } - @NonNull - private Teacher validateTeacher(Teacher teacher, String firstName, String lastName) { - if (teacher == null) { - teacher = new Teacher(profileId, -1, firstName, lastName); - teacher.id = crc16(teacher.getShortName().getBytes()); - teacherList.add(teacher); - } - if (firstName.length() > 1) - teacher.name = firstName; - teacher.surname = lastName; - return teacher; - } - - private Teacher searchTeacherByLastFirst(String nameLastFirst) { - String[] nameParts = nameLastFirst.split(" ", Integer.MAX_VALUE); - if (nameParts.length == 1) - return searchTeacher(nameParts[0], ""); - return searchTeacher(nameParts[1], nameParts[0]); - } - private Teacher searchTeacherByFirstLast(String nameFirstLast) { - String[] nameParts = nameFirstLast.split(" ", Integer.MAX_VALUE); - if (nameParts.length == 1) - return searchTeacher(nameParts[0], ""); - return searchTeacher(nameParts[0], nameParts[1]); - } - private Teacher searchTeacherByFDotLast(String nameFDotLast) { - String[] nameParts = nameFDotLast.split("\\.", Integer.MAX_VALUE); - if (nameParts.length == 1) - return searchTeacher(nameParts[0], ""); - return searchTeacher(nameParts[0].charAt(0), nameParts[1]); - } - private Teacher searchTeacherByFDotSpaceLast(String nameFDotSpaceLast) { - String[] nameParts = nameFDotSpaceLast.split("\\. ", Integer.MAX_VALUE); - if (nameParts.length == 1) - return searchTeacher(nameParts[0], ""); - return searchTeacher(nameParts[0].charAt(0), nameParts[1]); - } - - /* _____ _ _____ _ - | __ \ | | | __ \ | | - | | | | __ _| |_ __ _ | |__) |___ __ _ _ _ ___ ___| |_ ___ - | | | |/ _` | __/ _` | | _ // _ \/ _` | | | |/ _ \/ __| __/ __| - | |__| | (_| | || (_| | | | \ \ __/ (_| | |_| | __/\__ \ |_\__ \ - |_____/ \__,_|\__\__,_| |_| \_\___|\__, |\__,_|\___||___/\__|___/ - | | - |*/ - private void getTimetable() { - callback.onActionStarted(R.string.sync_action_syncing_timetable); - Date weekStart = Week.getWeekStart(); - if (Date.getToday().getWeekDay() > 4) { - weekStart.stepForward(0, 0, 7); - } - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec") - .userAgent(userAgent) - .addParameter("idPozDziennika", loginRegisterId) - .addParameter("pidRokSzkolny", loginSchoolYearId) - .addParameter("data", weekStart.getStringY_m_d()+"T10:00:00.000Z") - .postJson(), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 697, CODE_MAINTENANCE, response, result)); - return; - } - List> lessonHours = new ArrayList<>(); - for (JsonElement jLessonHourEl : data.getAsJsonArray("GodzinyLekcyjne")) { - JsonObject jLessonHour = jLessonHourEl.getAsJsonObject(); - // jLessonHour - lessonHours.add(new Pair<>(Time.fromH_m(jLessonHour.get("Poczatek").getAsString()), Time.fromH_m(jLessonHour.get("Koniec").getAsString()))); - } - - for (JsonElement jLessonEl : data.getAsJsonArray("Przedmioty")) { - JsonObject jLesson = jLessonEl.getAsJsonObject(); - // jLesson - Subject rSubject = searchSubject(jLesson.get("Nazwa").getAsString(), jLesson.get("Id").getAsInt(), jLesson.get("Skrot").getAsString()); - Teacher rTeacher = searchTeacherByFDotLast(jLesson.get("Nauczyciel").getAsString()); - - int weekDay = jLesson.get("DzienTygodnia").getAsInt() - 1; - Pair lessonHour = lessonHours.get(jLesson.get("Godzina").getAsInt()); - if (lessonHour == null || lessonHour.first == null || lessonHour.second == null) - continue; - Lesson lessonObject = new Lesson( - profileId, - weekDay, - lessonHour.first, - lessonHour.second - ); - lessonObject.subjectId = rSubject.id; - lessonObject.teacherId = rTeacher.id; - lessonObject.teamId = teamClassId; - lessonObject.classroomName = jLesson.get("NazwaSali").getAsString(); - - lessonList.add(lessonObject); - - int type = jLesson.get("TypZastepstwa").getAsInt(); - if (type != -1) { - // we have a lesson change to process - LessonChange lessonChangeObject = new LessonChange( - profileId, - weekStart.clone().stepForward(0, 0, weekDay), - lessonObject.startTime, - lessonObject.endTime - ); - - lessonChangeObject.teamId = lessonObject.teamId; - lessonChangeObject.teacherId = lessonObject.teacherId; - lessonChangeObject.subjectId = lessonObject.subjectId; - lessonChangeObject.classroomName = lessonObject.classroomName; - switch (type) { - case 0: - lessonChangeObject.type = TYPE_CANCELLED; - break; - case 1: - case 2: - case 3: - case 4: - case 5: - lessonChangeObject.type = TYPE_CHANGE; - String newTeacher = jLesson.get("NauZastepujacy").getAsString(); - String newSubject = jLesson.get("PrzedmiotZastepujacy").getAsString(); - if (!newTeacher.equals("")) { - lessonChangeObject.teacherId = searchTeacherByFDotLast(newTeacher).id; - } - if (!newSubject.equals("")) { - lessonChangeObject.subjectId = searchSubject(newSubject, -1, "").id; - } - break; - } - - lessonChangeList.add(lessonChangeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonChangeObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - } - r("finish", "Timetable"); - }); - } - - private void getGrades() { - callback.onActionStarted(R.string.sync_action_syncing_grades); - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia") - .userAgent(userAgent) - .addParameter("idPozDziennika", loginRegisterId) - .postJson(), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 782, CODE_MAINTENANCE, response, result)); - return; - } - JsonArray jSubjects = data.getAsJsonArray("Przedmioty"); - for (JsonElement jSubjectEl : jSubjects) { - JsonObject jSubject = jSubjectEl.getAsJsonObject(); - // jSubject - Subject rSubject = searchSubject(jSubject.get("Przedmiot").getAsString(), jSubject.get("IdPrzedmiotu").getAsInt(), jSubject.get("Przedmiot").getAsString()); - for (JsonElement jGradeEl : jSubject.getAsJsonArray("Oceny")) { - JsonObject jGrade = jGradeEl.getAsJsonObject(); - // jGrade - Teacher rTeacher = searchTeacherByLastFirst(jGrade.get("Wystawil").getAsString()); - - boolean countToTheAverage = jGrade.get("DoSredniej").getAsBoolean(); - float value = jGrade.get("WartoscDoSred").getAsFloat(); - - Grade gradeObject = new Grade( - profileId, - jGrade.get("idK").getAsLong(), - jGrade.get("Kategoria").getAsString(), - Color.parseColor("#"+jGrade.get("Kolor").getAsString()), - "", - jGrade.get("Ocena").getAsString(), - value, - value > 0 && countToTheAverage ? jGrade.get("Waga").getAsFloat() : 0, - jGrade.get("Semestr").getAsInt(), - rTeacher.id, - rSubject.id); - - switch (jGrade.get("Typ").getAsInt()) { - case 0: - JsonElement historyEl = jGrade.get("Historia"); - JsonArray history; - if (historyEl instanceof JsonArray && (history = historyEl.getAsJsonArray()).size() > 0) { - float sum = gradeObject.value * gradeObject.weight; - float count = gradeObject.weight; - for (JsonElement historyItemEl: history) { - JsonObject historyItem = historyItemEl.getAsJsonObject(); - - countToTheAverage = historyItem.get("DoSredniej").getAsBoolean(); - value = historyItem.get("WartoscDoSred").getAsFloat(); - float weight = historyItem.get("Waga").getAsFloat(); - - if (value > 0 && countToTheAverage) { - sum += value * weight; - count += weight; - } - - Grade historyObject = new Grade( - profileId, - gradeObject.id * -1, - historyItem.get("Kategoria").getAsString(), - Color.parseColor("#"+historyItem.get("Kolor").getAsString()), - historyItem.get("Uzasadnienie").getAsString(), - historyItem.get("Ocena").getAsString(), - value, - value > 0 && countToTheAverage ? weight * -1 : 0, - historyItem.get("Semestr").getAsInt(), - rTeacher.id, - rSubject.id); - historyObject.parentId = gradeObject.id; - - gradeList.add(historyObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, historyObject.id, true, true, Date.fromY_m_d(historyItem.get("Data_wystaw").getAsString()).getInMillis())); - } - // update the current grade's value with an average of all historical grades and itself - if (sum > 0 && count > 0) { - gradeObject.value = sum / count; - } - gradeObject.isImprovement = true; // gradeObject is the improved grade. Originals are historyObjects - } - break; - case 1: - gradeObject.type = TYPE_SEMESTER1_FINAL; - gradeObject.name = Integer.toString((int) gradeObject.value); - gradeObject.weight = 0; - break; - case 2: - gradeObject.type = TYPE_YEAR_FINAL; - gradeObject.name = Integer.toString((int) gradeObject.value); - gradeObject.weight = 0; - break; - } - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), Date.fromY_m_d(jGrade.get("Data_wystaw").getAsString()).getInMillis())); - } - } - r("finish", "Grades"); - }); - } - - private void getPropositionGrades() { - callback.onActionStarted(R.string.sync_action_syncing_proposition_grades); - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia") - .userAgent(userAgent) - .addParameter("idPozDziennika", loginRegisterId) - .postJson(), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 836, CODE_MAINTENANCE, response, result)); - return; - } - JsonArray jSubjects = data.getAsJsonArray("Przedmioty"); - for (JsonElement jSubjectEl : jSubjects) { - JsonObject jSubject = jSubjectEl.getAsJsonObject(); - // jSubject - Subject rSubject = searchSubject(jSubject.get("Przedmiot").getAsString(), -1, jSubject.get("Przedmiot").getAsString()); - String semester1Proposed = jSubject.get("OcenaSem1").getAsString(); - String semester2Proposed = jSubject.get("OcenaSem2").getAsString(); - int semester1Value = getWordGradeValue(semester1Proposed); - int semester2Value = getWordGradeValue(semester2Proposed); - long semester1Id = rSubject.id * -100 - 1; - long semester2Id = rSubject.id * -100 - 2; - - if (!semester1Proposed.equals("")) { - Grade gradeObject = new Grade( - profileId, - semester1Id, - "", - -1, - "", - Integer.toString(semester1Value), - semester1Value, - 0, - 1, - -1, - rSubject.id); - - gradeObject.type = TYPE_SEMESTER1_PROPOSED; - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - - if (!semester2Proposed.equals("")) { - Grade gradeObject = new Grade( - profileId, - semester2Id, - "", - -1, - "", - Integer.toString(semester2Value), - semester2Value, - 0, - 2, - -1, - rSubject.id); - - gradeObject.type = TYPE_YEAR_PROPOSED; - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - } - r("finish", "PropositionGrades"); - }); - } - - private int examsYear = Date.getToday().year; - private int examsMonth = Date.getToday().month; - private int examsMonthsChecked = 0; - private boolean examsNextMonthChecked = false; // TO DO temporary // no more // idk - private void getExams() { - callback.onActionStarted(R.string.sync_action_syncing_exams); - JsonObject postData = new JsonObject(); - postData.addProperty("idP", loginRegisterId); - postData.addProperty("rok", examsYear); - postData.addProperty("miesiac", examsMonth); - JsonObject param = new JsonObject(); - param.addProperty("strona", 1); - param.addProperty("iloscNaStrone", "99"); - param.addProperty("iloscRekordow", -1); - param.addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu"); - param.addProperty("kierunekSort", 0); - param.addProperty("maxIloscZaznaczonych", 0); - param.addProperty("panelFiltrow", 0); - postData.add("param", param); - - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe") - .userAgent(userAgent) - .setJsonBody(postData), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 921, CODE_MAINTENANCE, response, result)); - return; - } - for (JsonElement jExamEl : data.getAsJsonArray("ListK")) { - JsonObject jExam = jExamEl.getAsJsonObject(); - // jExam - long eventId = jExam.get("_recordId").getAsLong(); - Subject rSubject = searchSubject(jExam.get("przedmiot").getAsString(), -1, ""); - Teacher rTeacher = searchTeacherByLastFirst(jExam.get("wpisal").getAsString()); - Date examDate = Date.fromY_m_d(jExam.get("data").getAsString()); - Lesson lessonObject = Lesson.getByWeekDayAndSubject(lessonList, examDate.getWeekDay(), rSubject.id); - Time examTime = lessonObject == null ? null : lessonObject.startTime; - - int eventType = (jExam.get("rodzaj").getAsString().equals("sprawdzian/praca klasowa") ? Event.TYPE_EXAM : Event.TYPE_SHORT_QUIZ); - Event eventObject = new Event( - profileId, - eventId, - examDate, - examTime, - jExam.get("zakres").getAsString(), - -1, - eventType, - false, - rTeacher.id, - rSubject.id, - teamClassId - ); - - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - - if (profile.getEmpty() && examsMonthsChecked < 3 /* how many months backwards to check? */) { - examsMonthsChecked++; - examsMonth--; - if (examsMonth < 1) { - examsMonth = 12; - examsYear--; - } - r("get", "Exams"); - } else if (!examsNextMonthChecked /* get also one month forward */) { - Date showDate = Date.getToday().stepForward(0, 1, 0); - examsYear = showDate.year; - examsMonth = showDate.month; - examsNextMonthChecked = true; - r("get", "Exams"); - } else { - r("finish", "Exams"); - } - }); - } - - private void getNotices() { - callback.onActionStarted(R.string.sync_action_syncing_notices); - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia") - .userAgent(userAgent) - .addParameter("idPozDziennika", loginRegisterId) - .postJson(), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 982, CODE_MAINTENANCE, response, result)); - return; - } - for (JsonElement jNoticeEl : data.getAsJsonArray("SUwaga")) { - JsonObject jNotice = jNoticeEl.getAsJsonObject(); - // jExam - long noticeId = crc16(jNotice.get("id").getAsString().getBytes()); - - Teacher rTeacher = searchTeacherByLastFirst(jNotice.get("Nauczyciel").getAsString()); - Date addedDate = Date.fromY_m_d(jNotice.get("Data").getAsString()); - - int nType = TYPE_NEUTRAL; - String jType = jNotice.get("Typ").getAsString(); - if (jType.equals("n")) { - nType = TYPE_NEGATIVE; - } else if (jType.equals("p")) { - nType = TYPE_POSITIVE; - } - - Notice noticeObject = new Notice( - profileId, - noticeId, - jNotice.get("Tresc").getAsString(), - jNotice.get("Semestr").getAsInt(), - nType, - rTeacher.id); - noticeList.add(noticeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate.getInMillis())); - } - r("finish", "Notices"); - }); - } - - private void getAnnouncements() { - callback.onActionStarted(R.string.sync_action_syncing_announcements); - if (loginStudentId == null) { - r("finish", "Announcements"); - return; - } - JsonObject postData = new JsonObject(); - postData.addProperty("uczenId", loginStudentId); - JsonObject param = new JsonObject(); - param.add("parametryFiltrow", new JsonArray()); - postData.add("param", param); - - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia") - .userAgent(userAgent) - .setJsonBody(postData), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 1033, CODE_MAINTENANCE, response, result)); - return; - } - for (JsonElement jAnnouncementEl : data.getAsJsonArray("ListK")) { - JsonObject jAnnouncement = jAnnouncementEl.getAsJsonObject(); - // jAnnouncement - long announcementId = jAnnouncement.get("Id").getAsLong(); - - Teacher rTeacher = searchTeacherByFirstLast(jAnnouncement.get("Autor").getAsString()); - long addedDate = Long.parseLong(jAnnouncement.get("DataDodania").getAsString().replaceAll("[^\\d]", "")); - Date startDate = Date.fromMillis(Long.parseLong(jAnnouncement.get("DataWydarzenia").getAsString().replaceAll("[^\\d]", ""))); - - Announcement announcementObject = new Announcement( - profileId, - announcementId, - jAnnouncement.get("Temat").getAsString(), - jAnnouncement.get("Tresc").getAsString(), - startDate, - null, - rTeacher.id - ); - announcementList.add(announcementObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_ANNOUNCEMENT, announcementObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "Announcements"); - }); - } - - private int attendancesYear; - private int attendancesMonth; - private boolean attendancesPrevMonthChecked = false; - private void getAttendances() { - callback.onActionStarted(R.string.sync_action_syncing_attendances); - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia") - .userAgent(userAgent) - .addParameter("idPozDziennika", loginRegisterId) - .addParameter("mc", attendancesMonth) - .addParameter("rok", attendancesYear) - .addParameter("dataTygodnia", "") - .postJson(), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 1076, CODE_MAINTENANCE, response, result)); - return; - } - for (JsonElement jAttendanceEl : data.getAsJsonArray("Obecnosci")) { - JsonObject jAttendance = jAttendanceEl.getAsJsonObject(); - // jAttendance - int attendanceTypeIdziennik = jAttendance.get("TypObecnosci").getAsInt(); - if (attendanceTypeIdziennik == 5 || attendanceTypeIdziennik == 7) - continue; - Date attendanceDate = Date.fromY_m_d(jAttendance.get("Data").getAsString()); - Time attendanceTime = Time.fromH_m(jAttendance.get("OdDoGodziny").getAsString()); - if (attendanceDate.combineWith(attendanceTime) > System.currentTimeMillis()) - continue; - - long attendanceId = crc16(jAttendance.get("IdLesson").getAsString().getBytes()); - Subject rSubject = searchSubject(jAttendance.get("Przedmiot").getAsString(), jAttendance.get("IdPrzedmiot").getAsLong(), ""); - Teacher rTeacher = searchTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").getAsString()); - - String attendanceName = "obecność"; - int attendanceType = Attendance.TYPE_CUSTOM; - - switch (attendanceTypeIdziennik) { - case 1: /* nieobecność usprawiedliwiona */ - attendanceName = "nieobecność usprawiedliwiona"; - attendanceType = TYPE_ABSENT_EXCUSED; - break; - case 2: /* spóźnienie */ - attendanceName = "spóźnienie"; - attendanceType = TYPE_BELATED; - break; - case 3: /* nieobecność nieusprawiedliwiona */ - attendanceName = "nieobecność nieusprawiedliwiona"; - attendanceType = TYPE_ABSENT; - break; - case 4: /* zwolnienie */ - case 9: /* zwolniony / obecny */ - attendanceType = TYPE_RELEASED; - if (attendanceTypeIdziennik == 4) - attendanceName = "zwolnienie"; - if (attendanceTypeIdziennik == 9) - attendanceName = "zwolnienie / obecność"; - break; - case 0: /* obecny */ - case 8: /* Wycieczka */ - attendanceType = TYPE_PRESENT; - if (attendanceTypeIdziennik == 8) - attendanceName = "wycieczka"; - break; - } - - int semester = profile.dateToSemester(attendanceDate); - - Attendance attendanceObject = new Attendance( - profileId, - attendanceId, - rTeacher.id, - rSubject.id, - semester, - attendanceName, - attendanceDate, - attendanceTime, - attendanceType - ); - - attendanceList.add(attendanceObject); - if (attendanceObject.type != TYPE_PRESENT) { - metadataList.add(new Metadata(profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - } - - int attendancesDateValue = attendancesYear*10000 + attendancesMonth*100; - if (profile.getEmpty() && attendancesDateValue > profile.getSemesterStart(1).getValue()) { - attendancesPrevMonthChecked = true; // do not need to check prev month later - attendancesMonth--; - if (attendancesMonth < 1) { - attendancesMonth = 12; - attendancesYear--; - } - r("get", "Attendances"); - } else if (!attendancesPrevMonthChecked /* get also the previous month */) { - attendancesMonth--; - if (attendancesMonth < 1) { - attendancesMonth = 12; - attendancesYear--; - } - attendancesPrevMonthChecked = true; - r("get", "Attendances"); - } else { - r("finish", "Attendances"); - } - }); - } - - private void getLuckyNumberAndSemesterDates() { - if (profile.getLuckyNumberDate() != null && profile.getLuckyNumber() != -1 && profile.getLuckyNumberDate().getValue() == Date.getToday().getValue()) { - r("finish", "LuckyNumberAndSemesterDates"); - return; - } - if (loginBearerToken == null || loginStudentId == null) { - r("finish", "LuckyNumberAndSemesterDates"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_lucky_number); - apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/api/Uczniowie/"+loginStudentId+"/AktualnyDziennik") - .header("Authorization", "Bearer "+loginBearerToken) - .userAgent(userAgent), (data, response) -> { - JsonObject settings = data.getAsJsonObject("ustawienia"); - if (settings == null) { - finishWithError(new AppError(TAG, 1188, CODE_MAINTENANCE, response, data)); - return; - } - // data, settings - profile.setLuckyNumber(-1); - profile.setLuckyNumberDate(today); - JsonElement luckyNumberEl = data.get("szczesliwyNumerek"); - if (luckyNumberEl != null && !(luckyNumberEl instanceof JsonNull)) { - profile.setLuckyNumber(luckyNumberEl.getAsInt()); - Time publishTime = Time.fromH_m(settings.get("godzinaPublikacjiSzczesliwegoLosu").getAsString()); - if (Time.getNow().getValue() > publishTime.getValue()) { - profile.getLuckyNumberDate().stepForward(0, 0, 1); // the lucky number is already for tomorrow - } - app.db.luckyNumberDao().add(new LuckyNumber(profileId, profile.getLuckyNumberDate(), profile.getLuckyNumber())); - } - - profile.setDateSemester1Start(Date.fromY_m_d(settings.get("poczatekSemestru1").getAsString())); - profile.setDateSemester2Start(Date.fromY_m_d(settings.get("koniecSemestru1").getAsString()).stepForward(0, 0, 1)); - profile.setDateYearEnd(Date.fromY_m_d(settings.get("koniecSemestru2").getAsString())); - - r("finish", "LuckyNumberAndSemesterDates"); - }); - } - - private void getMessagesInbox() { - if (loginBearerToken == null) { - r("finish", "MessagesInbox"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_messages); - apiRequestArray(Request.builder() - .url(IDZIENNIK_URL +"/api/Wiadomosci/Odebrane") - .header("Authorization", "Bearer "+loginBearerToken) - .userAgent(userAgent), (data, response) -> { - for (JsonElement jMessageEl: data) { - JsonObject jMessage = jMessageEl.getAsJsonObject(); - - String subject = jMessage.get("tytul").getAsString(); - if (subject.contains("(") && subject.startsWith("iDziennik - ")) - continue; - if (subject.startsWith("Uwaga dla ucznia (klasa:")) - continue; - - String messageIdStr = jMessage.get("id").getAsString(); - long messageId = crc32((messageIdStr+"0").getBytes()); - - String body = "[META:"+messageIdStr+";-1]"; - body += jMessage.get("tresc").getAsString().replaceAll("\n", "
"); - - long readDate = jMessage.get("odczytana").getAsBoolean() ? Date.fromIso(jMessage.get("wersjaRekordu").getAsString()) : 0; - long sentDate = Date.fromIso(jMessage.get("dataWyslania").getAsString()); - - JsonObject sender = jMessage.getAsJsonObject("nadawca"); - Teacher rTeacher = searchTeacher(sender.get("imie").getAsString(), sender.get("nazwisko").getAsString()); - rTeacher.loginId = sender.get("id").getAsString()+":"+sender.get("usr").getAsString(); - - Message message = new Message( - profileId, - messageId, - subject, - body, - jMessage.get("rekordUsuniety").getAsBoolean() ? TYPE_DELETED : TYPE_RECEIVED, - rTeacher.id, - -1 - ); - - MessageRecipient messageRecipient = new MessageRecipient( - profileId, - -1 /* me */, - -1, - readDate, - /*messageId*/ messageId - ); - - messageList.add(message); - messageRecipientList.add(messageRecipient); - messageMetadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, readDate > 0, readDate > 0 || profile.getEmpty(), sentDate)); - } - - r("finish", "MessagesInbox"); - }); - } - - private void getMessagesOutbox() { - if (loginBearerToken == null) { - r("finish", "MessagesOutbox"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_messages); - apiRequestArray(Request.builder() - .url(IDZIENNIK_URL +"/api/Wiadomosci/Wyslane") - .header("Authorization", "Bearer "+loginBearerToken) - .userAgent(userAgent), (data, response) -> { - for (JsonElement jMessageEl: data) { - JsonObject jMessage = jMessageEl.getAsJsonObject(); - - String messageIdStr = jMessage.get("id").getAsString(); - long messageId = crc32((messageIdStr+"1").getBytes()); - - String subject = jMessage.get("tytul").getAsString(); - - String body = "[META:"+messageIdStr+";-1]"; - body += jMessage.get("tresc").getAsString().replaceAll("\n", "
"); - - long sentDate = Date.fromIso(jMessage.get("dataWyslania").getAsString()); - - Message message = new Message( - profileId, - messageId, - subject, - body, - TYPE_SENT, - -1, - -1 - ); - - for (JsonElement recipientEl: jMessage.getAsJsonArray("odbiorcy")) { - JsonObject recipient = recipientEl.getAsJsonObject(); - String firstName = recipient.get("imie").getAsString(); - String lastName = recipient.get("nazwisko").getAsString(); - if (firstName.isEmpty() || lastName.isEmpty()) { - firstName = "usunięty"; - lastName = "użytkownik"; - } - Teacher rTeacher = searchTeacher(firstName, lastName); - rTeacher.loginId = recipient.get("id").getAsString()+":"+recipient.get("usr").getAsString(); - - MessageRecipient messageRecipient = new MessageRecipient( - profileId, - rTeacher.id, - -1, - -1, - /*messageId*/ messageId - ); - messageRecipientIgnoreList.add(messageRecipient); - } - - messageList.add(message); - metadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)); - } - - r("finish", "MessagesOutbox"); - }); - } - - @Override - public Map getConfigurableEndpoints(Profile profile) { - return null; - } - - @Override - public boolean isEndpointEnabled(Profile profile, boolean defaultActive, String name) { - return defaultActive; - } - - @Override - public void syncMessages(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile) - { - } - - /* __ __ - | \/ | - | \ / | ___ ___ ___ __ _ __ _ ___ ___ - | |\/| |/ _ \/ __/ __|/ _` |/ _` |/ _ \/ __| - | | | | __/\__ \__ \ (_| | (_| | __/\__ \ - |_| |_|\___||___/___/\__,_|\__, |\___||___/ - __/ | - |__*/ - @Override - public void getMessage(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, @NonNull MessageGetCallback messageCallback) { - if (message.body == null) - return; - String messageIdStr = null; - long messageIdBefore = -1; - Matcher matcher = Pattern.compile("\\[META:([A-z0-9]+);([0-9-]+)]").matcher(message.body); - if (matcher.find()) { - messageIdStr = matcher.group(1); - messageIdBefore = Long.parseLong(matcher.group(2)); - } - if (messageIdBefore != -1) { - boolean readByAll = true; - // load this message's recipient(s) data - message.recipients = app.db.messageRecipientDao().getAllByMessageId(profile.getId(), message.id); - for (MessageRecipientFull recipient: message.recipients) { - if (recipient.id == -1) - recipient.fullName = profile.getStudentNameLong(); - if (message.type == TYPE_SENT && recipient.readDate < 1) - readByAll = false; - } - if (!message.seen) { - app.db.metadataDao().setSeen(profile.getId(), message, true); - } - if (readByAll) { - // if a sent msg is not read by everyone, download it again to check the read status - new Handler(activityContext.getMainLooper()).post(() -> messageCallback.onSuccess(message)); - return; - } - } - - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - String finalMessageIdStr = messageIdStr; - login(() -> apiRequest(Request.builder() - .url(IDZIENNIK_URL +"/mod_komunikator/WS_wiadomosci.asmx/PobierzWiadomosc") - .userAgent(userAgent) - .addParameter("idWiadomosci", finalMessageIdStr) - .addParameter("typWiadomosci", message.type == TYPE_SENT ? 1 : 0) - .postJson(), (result, response) -> { - JsonObject data = result.getAsJsonObject("d"); - if (data == null) { - finishWithError(new AppError(TAG, 1418, CODE_MAINTENANCE, response, result)); - return; - } - JsonObject jMessage = data.getAsJsonObject("Wiadomosc"); - if (jMessage == null) { - finishWithError(new AppError(TAG, 1423, CODE_MAINTENANCE, response, result)); - return; - } - - List messageRecipientList = new ArrayList<>(); - - long messageId = jMessage.get("_recordId").getAsLong(); - - message.body = message.body.replaceAll("\\[META:[A-z0-9]+;[0-9-]+]", "[META:"+ finalMessageIdStr +";"+ messageId +"]"); - - message.clearAttachments(); - for (JsonElement jAttachmentEl: jMessage.getAsJsonArray("ListaZal")) { - JsonObject jAttachment = jAttachmentEl.getAsJsonObject(); - message.addAttachment(jAttachment.get("Id").getAsLong(), jAttachment.get("Nazwa").getAsString(), -1); - } - - if (message.type == TYPE_RECEIVED) { - MessageRecipientFull recipient = new MessageRecipientFull(profileId, -1, message.id); - - String readDateStr = jMessage.get("DataOdczytania").getAsString(); - recipient.readDate = readDateStr.isEmpty() ? System.currentTimeMillis() : Date.fromIso(readDateStr); - - recipient.fullName = profile.getStudentNameLong(); - messageRecipientList.add(recipient); - } - else if (message.type == TYPE_SENT) { - teacherList = app.db.teacherDao().getAllNow(profileId); - for (JsonElement jReceiverEl: jMessage.getAsJsonArray("ListaOdbiorcow")) { - JsonObject jReceiver = jReceiverEl.getAsJsonObject(); - String receiverLastFirstName = jReceiver.get("NazwaOdbiorcy").getAsString(); - - Teacher teacher = searchTeacherByLastFirst(receiverLastFirstName); - - MessageRecipientFull recipient = new MessageRecipientFull(profileId, teacher.id, message.id); - - recipient.readDate = jReceiver.get("Status").getAsInt(); - - recipient.fullName = teacher.getFullName(); - messageRecipientList.add(recipient); - } - } - - if (!message.seen) { - app.db.metadataDao().setSeen(profileId, message, true); - } - app.db.messageDao().add(message); - app.db.messageRecipientDao().addAll((List)(List) messageRecipientList); // not addAllIgnore - - message.recipients = messageRecipientList; - - new Handler(activityContext.getMainLooper()).post(() -> messageCallback.onSuccess(message)); - })); - } - - @Override - public void getAttachment(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, long attachmentId, @NonNull AttachmentGetCallback attachmentCallback) { - if (message.body == null) - return; - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - String fileName = message.attachmentNames.get(message.attachmentIds.indexOf(attachmentId)); - - long messageId = -1; - Matcher matcher = Pattern.compile("\\[META:([A-z0-9]+);([0-9-]+)]").matcher(message.body); - if (matcher.find()) { - messageId = Long.parseLong(matcher.group(2)); - } - - Request.Builder builder = Request.builder() - .url("https://iuczniowie.progman.pl/idziennik/mod_komunikator/Download.ashx") - .post() - .contentType(MediaTypeUtils.APPLICATION_FORM) - .addParameter("id", messageId) - .addParameter("fileName", fileName); - - new Handler(activityContext.getMainLooper()).post(() -> attachmentCallback.onSuccess(builder)); - }); - } - - @Override - public void getRecipientList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull RecipientListGetCallback recipientListGetCallback) { - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - if (System.currentTimeMillis() - profile.getLastReceiversSync() < 24 * 60 * 60 * 1000) { - AsyncTask.execute(() -> { - List teacherList = app.db.teacherDao().getAllNow(profileId); - new Handler(activityContext.getMainLooper()).post(() -> recipientListGetCallback.onSuccess(teacherList)); - }); - return; - } - - login(() -> { - List cookieList = app.cookieJar.loadForRequest(HttpUrl.get(IDZIENNIK_URL)); - for (Cookie cookie: cookieList) { - if (cookie.name().equalsIgnoreCase("Bearer")) { - loginBearerToken = cookie.value(); - } - } - loginStudentId = profile.getStudentData("studentId", null);// TODO: 2019-06-12 temporary duplicated token & ID extraction - - apiRequestArray(Request.builder() - .url(IDZIENNIK_URL + "/api/Wiadomosci/Odbiorcy?idUcznia="+loginStudentId) - .header("Authorization", "Bearer " + loginBearerToken) - .userAgent(userAgent), (result, response) -> { - teacherList = app.db.teacherDao().getAllNow(profileId); - for (JsonElement recipientEl: result) { - JsonObject recipient = recipientEl.getAsJsonObject(); - String name = recipient.get("nazwaKontaktu").getAsString(); - String loginId = recipient.get("idUzytkownika").getAsString(); - JsonArray typesArray = recipient.getAsJsonArray("typOsoby"); - List types = new ArrayList<>(); - for (JsonElement typeEl: typesArray) { - types.add(typeEl.getAsInt()); - } - String delimiter; - if (types.size() == 1 && types.get(0) >= 6) /* parent or student */ - delimiter = " ("; - else - delimiter = ": "; - String nameFirstLast = name.substring(0, name.indexOf(delimiter)); - Teacher teacher = searchTeacherByFirstLast(nameFirstLast); - teacher.loginId = loginId; - teacher.type = 0; - for (int type: types) { - switch (type) { - case 0: - teacher.setType(Teacher.TYPE_SCHOOL_ADMIN); - break; - case 1: - teacher.setType(Teacher.TYPE_SECRETARIAT); - break; - case 2: - teacher.setType(Teacher.TYPE_TEACHER); - teacher.typeDescription = name.substring(name.indexOf(": ")+2); - break; - case 3: - teacher.setType(Teacher.TYPE_PRINCIPAL); - break; - case 4: - teacher.setType(Teacher.TYPE_EDUCATOR); - break; - case 5: - teacher.setType(Teacher.TYPE_PEDAGOGUE); - break; - case 6: - teacher.setType(Teacher.TYPE_PARENT); - teacher.typeDescription = name.substring(name.indexOf(" (")+2, name.lastIndexOf(" (")); - break; - case 7: - teacher.setType(Teacher.TYPE_STUDENT); - break; - } - } - } - app.db.teacherDao().addAll(teacherList); - - profile.setLastReceiversSync(System.currentTimeMillis()); - app.db.profileDao().add(profile); - - new Handler(activityContext.getMainLooper()).post(() -> recipientListGetCallback.onSuccess(new ArrayList<>(teacherList))); - }); - }); - } - - @Override - public MessagesComposeInfo getComposeInfo(@NonNull ProfileFull profile) { - return new MessagesComposeInfo(0, 0, 180, 1983); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/Librus.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/Librus.java deleted file mode 100644 index c3d01bb3..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/Librus.java +++ /dev/null @@ -1,3667 +0,0 @@ -package pl.szczodrzynski.edziennik.api; - -import android.content.Context; -import android.graphics.Color; -import android.os.AsyncTask; -import android.os.Handler; -import android.util.Base64; -import android.util.Pair; -import android.util.SparseArray; -import android.util.SparseIntArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.crashlytics.android.Crashlytics; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.parser.Parser; -import org.jsoup.select.Elements; - -import java.text.DateFormat; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import im.wangchao.mhttp.Request; -import im.wangchao.mhttp.Response; -import im.wangchao.mhttp.body.MediaTypeUtils; -import im.wangchao.mhttp.callback.JsonCallbackHandler; -import im.wangchao.mhttp.callback.TextCallbackHandler; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; -import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; -import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; -import pl.szczodrzynski.edziennik.api.v2.models.DataStore; -import pl.szczodrzynski.edziennik.datamodels.Announcement; -import pl.szczodrzynski.edziennik.datamodels.Attendance; -import pl.szczodrzynski.edziennik.datamodels.Event; -import pl.szczodrzynski.edziennik.datamodels.EventType; -import pl.szczodrzynski.edziennik.datamodels.Grade; -import pl.szczodrzynski.edziennik.datamodels.GradeCategory; -import pl.szczodrzynski.edziennik.datamodels.Lesson; -import pl.szczodrzynski.edziennik.datamodels.LessonChange; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.LuckyNumber; -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.MessageFull; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipient; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipientFull; -import pl.szczodrzynski.edziennik.datamodels.Metadata; -import pl.szczodrzynski.edziennik.datamodels.Notice; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.datamodels.Subject; -import pl.szczodrzynski.edziennik.datamodels.Teacher; -import pl.szczodrzynski.edziennik.datamodels.Team; -import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Endpoint; -import pl.szczodrzynski.edziennik.models.Time; -import pl.szczodrzynski.edziennik.models.Week; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; -import static java.net.HttpURLConnection.HTTP_GONE; -import static java.net.HttpURLConnection.HTTP_NOT_FOUND; -import static java.net.HttpURLConnection.HTTP_OK; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_INVALID_LOGIN; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_LIBRUS_DISCONNECTED; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_LIBRUS_NOT_ACTIVATED; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_MAINTENANCE; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_PROFILE_NOT_FOUND; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_SYNERGIA_NOT_ACTIVATED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_RELEASED; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_PT_MEETING; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_TEACHER_ABSENCE; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_NORMAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.LessonChange.TYPE_CANCELLED; -import static pl.szczodrzynski.edziennik.datamodels.LessonChange.TYPE_CHANGE; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_RECEIVED; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_SENT; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_NEGATIVE; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_NEUTRAL; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_POSITIVE; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_EDUCATOR; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_LIBRARIAN; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_OTHER; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_PARENTS_COUNCIL; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_PEDAGOGUE; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_SCHOOL_ADMIN; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_SCHOOL_PARENTS_COUNCIL; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_SECRETARIAT; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_SUPER_ADMIN; -import static pl.szczodrzynski.edziennik.datamodels.Teacher.TYPE_TEACHER; -import static pl.szczodrzynski.edziennik.utils.Utils.bs; -import static pl.szczodrzynski.edziennik.utils.Utils.c; -import static pl.szczodrzynski.edziennik.utils.Utils.contains; -import static pl.szczodrzynski.edziennik.utils.Utils.crc16; -import static pl.szczodrzynski.edziennik.utils.Utils.d; -import static pl.szczodrzynski.edziennik.utils.Utils.getGradeValue; -import static pl.szczodrzynski.edziennik.utils.Utils.strToInt; - -public class Librus implements EdziennikInterface { - public Librus(App app) { - this.app = app; - } - - private static final String TAG = "api.Librus"; - private static final String CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv"; - private static final String REDIRECT_URL = "http://localhost/bar"; - private static final String AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id="+CLIENT_ID+"&redirect_uri="+REDIRECT_URL+"&response_type=code"; - private static final String LOGIN_URL = "https://portal.librus.pl/rodzina/login/action"; - private static final String TOKEN_URL = "https://portal.librus.pl/oauth2/access_token"; - private static final String ACCOUNTS_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts"; - private static final String ACCOUNT_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts/fresh/"; // + login - private static final String API_URL = "https://api.librus.pl/2.0/"; - private static final String SYNERGIA_URL = "https://wiadomosci.librus.pl/module/"; - private static final String SYNERGIA_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action="; - private static final String userAgent = "Dalvik/2.1.0 Android LibrusMobileApp"; - private static final String synergiaUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0"; - - private App app; - private Context activityContext = null; - private SyncCallback callback = null; - private int profileId = -1; - private Profile profile = null; - private LoginStore loginStore = null; - private boolean fullSync = true; - private Date today; - private List targetEndpoints = new ArrayList<>(); - - // PROGRESS - private static final int PROGRESS_LOGIN = 10; - private int PROGRESS_COUNT = 1; - private int PROGRESS_STEP = (90/PROGRESS_COUNT); - - private int onlyFeature = FEATURE_ALL; - - private List teamList; - private List teacherList; - private List subjectList; - private List lessonList; - private List lessonChangeList; - private List gradeCategoryList; - private List gradeList; - private List eventList; - private List eventTypeList; - private List noticeList; - private List attendanceList; - private List announcementList; - private List messageList; - private List messageRecipientList; - private List messageRecipientIgnoreList; - private List metadataList; - private List messageMetadataList; - - private static boolean fakeLogin = false; - private String librusEmail = null; - private String librusPassword = null; - private String synergiaLogin = null; - private String synergiaPassword = null; - private String synergiaLastLogin = null; - private long synergiaLastLoginTime = -1; - private boolean premium = false; - private boolean enableStandardGrades = true; - private boolean enablePointGrades = false; - private boolean enableDescriptiveGrades = false; - private boolean enableTextGrades = false; - private boolean enableBehaviourGrades = true; - private int startPointsSemester1 = 0; - private int startPointsSemester2 = 0; - - private boolean prepare(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - this.activityContext = activityContext; - this.callback = callback; - this.profileId = profileId; - // here we must have a login store: either with a correct ID or -1 - // there may be no profile and that's when onLoginFirst happens - this.profile = profile; - this.loginStore = loginStore; - this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext); - this.today = Date.getToday(); - - DataStore ds = new DataStore(app.db, profileId); - - this.librusEmail = loginStore.getLoginData("email", ""); - this.librusPassword = loginStore.getLoginData("password", ""); - if (profile == null) { - this.synergiaLogin = null; - this.synergiaPassword = null; - } - else { - this.synergiaLogin = profile.getStudentData("accountLogin", null); - this.synergiaPassword = profile.getStudentData("accountPassword", null); - } - if (librusEmail.equals("") || librusPassword.equals("")) { - finishWithError(new AppError(TAG, 214, AppError.CODE_INVALID_LOGIN, "Login field is empty")); - return false; - } - this.premium = profile != null && profile.getStudentData("isPremium", false); - this.failed = 0; - fakeLogin = BuildConfig.DEBUG && librusEmail.toLowerCase().startsWith("fake"); - this.synergiaLastLogin = null; - this.synergiaLastLoginTime = -1; - - this.refreshTokenFailed = false; - - teamList = profileId == -1 ? new ArrayList<>() : app.db.teamDao().getAllNow(profileId); - teacherList = profileId == -1 ? new ArrayList<>() : app.db.teacherDao().getAllNow(profileId); - subjectList = new ArrayList<>(); - lessonList = new ArrayList<>(); - lessonChangeList = new ArrayList<>(); - gradeCategoryList = new ArrayList<>(); - gradeList = new ArrayList<>(); - eventList = new ArrayList<>(); - eventTypeList = new ArrayList<>(); - noticeList = new ArrayList<>(); - attendanceList = new ArrayList<>(); - announcementList = new ArrayList<>(); - messageList = new ArrayList<>(); - messageRecipientList = new ArrayList<>(); - messageRecipientIgnoreList = new ArrayList<>(); - metadataList = new ArrayList<>(); - messageMetadataList = new ArrayList<>(); - - return true; - } - - @Override - public void sync(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - if (!prepare(activityContext, callback, profileId, profile, loginStore)) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - targetEndpoints.add("Me"); - targetEndpoints.add("Schools"); - targetEndpoints.add("Classes"); - targetEndpoints.add("VirtualClasses"); - targetEndpoints.add("Units"); - targetEndpoints.add("Users"); - targetEndpoints.add("Subjects"); - targetEndpoints.add("Classrooms"); - targetEndpoints.add("Timetables"); - targetEndpoints.add("Substitutions"); - targetEndpoints.add("Colors"); - - targetEndpoints.add("SavedGradeCategories"); - targetEndpoints.add("GradesCategories"); - targetEndpoints.add("PointGradesCategories"); - targetEndpoints.add("DescriptiveGradesCategories"); - //targetEndpoints.add("TextGradesCategories"); - targetEndpoints.add("BehaviourGradesCategories"); // TODO: 2019-04-30 - targetEndpoints.add("SaveGradeCategories"); - - targetEndpoints.add("Grades"); - targetEndpoints.add("PointGrades"); - targetEndpoints.add("DescriptiveGrades"); - targetEndpoints.add("BehaviourGrades"); - - targetEndpoints.add("Events"); - targetEndpoints.add("CustomTypes"); - targetEndpoints.add("Homeworks"); - targetEndpoints.add("LuckyNumbers"); - targetEndpoints.add("Notices"); - targetEndpoints.add("AttendancesTypes"); - targetEndpoints.add("Attendances"); - targetEndpoints.add("Announcements"); - targetEndpoints.add("PtMeetings"); - - /*if (isEndpointEnabled(profile, true, "SchoolFreeDays")) - targetEndpoints.add("SchoolFreeDays"); - if (isEndpointEnabled(profile, true, "ClassFreeDays")) - targetEndpoints.add("ClassFreeDays");*/ - targetEndpoints.add("MessagesLogin"); - targetEndpoints.add("MessagesInbox"); - targetEndpoints.add("MessagesOutbox"); - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - @Override - public void syncFeature(@NonNull Context activityContext, @NonNull SyncCallback callback, @NonNull ProfileFull profile, int ... featureList) { - if (featureList == null) { - sync(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile)); - return; - } - if (!prepare(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - if (featureList.length == 1) - onlyFeature = featureList[0]; - targetEndpoints.add("Me"); - targetEndpoints.add("Schools"); - targetEndpoints.add("Classes"); - targetEndpoints.add("VirtualClasses"); - targetEndpoints.add("Units"); - targetEndpoints.add("Users"); - targetEndpoints.add("Subjects"); - targetEndpoints.add("Colors"); - boolean hasMessagesLogin = false; - for (int feature: featureList) { - switch (feature) { - case FEATURE_TIMETABLE: - targetEndpoints.add("Classrooms"); - targetEndpoints.add("Timetables"); - targetEndpoints.add("Substitutions"); - break; - case FEATURE_AGENDA: - targetEndpoints.add("Events"); - targetEndpoints.add("CustomTypes"); - targetEndpoints.add("PtMeetings"); - break; - case FEATURE_GRADES: - targetEndpoints.add("SavedGradeCategories"); - targetEndpoints.add("GradesCategories"); - targetEndpoints.add("PointGradesCategories"); - targetEndpoints.add("DescriptiveGradesCategories"); - //targetEndpoints.add("TextGradesCategories"); - targetEndpoints.add("BehaviourGradesCategories"); // TODO: 2019-04-30 - targetEndpoints.add("SaveGradeCategories"); - - targetEndpoints.add("Grades"); - targetEndpoints.add("PointGrades"); - targetEndpoints.add("DescriptiveGrades"); - targetEndpoints.add("BehaviourGrades"); - break; - case FEATURE_HOMEWORKS: - targetEndpoints.add("Homeworks"); - break; - case FEATURE_NOTICES: - targetEndpoints.add("Notices"); - break; - case FEATURE_ATTENDANCES: - targetEndpoints.add("AttendancesTypes"); - targetEndpoints.add("Attendances"); - break; - case FEATURE_MESSAGES_INBOX: - if (!hasMessagesLogin) { - hasMessagesLogin = true; - targetEndpoints.add("MessagesLogin"); - } - targetEndpoints.add("MessagesInbox"); - break; - case FEATURE_MESSAGES_OUTBOX: - if (!hasMessagesLogin) { - hasMessagesLogin = true; - targetEndpoints.add("MessagesLogin"); - } - targetEndpoints.add("MessagesOutbox"); - break; - case FEATURE_ANNOUNCEMENTS: - targetEndpoints.add("Announcements"); - break; - } - } - targetEndpoints.add("LuckyNumbers"); - - /*if (isEndpointEnabled(profile, true, "SchoolFreeDays")) - targetEndpoints.add("SchoolFreeDays"); - if (isEndpointEnabled(profile, true, "ClassFreeDays")) - targetEndpoints.add("ClassFreeDays");*/ - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - - private void begin() { - if (profile == null) { - finishWithError(new AppError(TAG, 214, AppError.CODE_PROFILE_NOT_FOUND, "Profile == null WTF???")); - return; - } - String accountToken = profile.getStudentData("accountToken", null); - d(TAG, "Beginning account "+ profile.getStudentNameLong() +" sync with token "+accountToken+". Full sync enabled "+fullSync); - synergiaAccessToken = accountToken; - - callback.onProgress(PROGRESS_LOGIN); - - r("get", null); - } - - private void r(String type, String endpoint) { - // endpoint == null when beginning - if (endpoint == null) - endpoint = targetEndpoints.get(0); - int index = -1; - for (String request: targetEndpoints) { - index++; - if (request.equals(endpoint)) { - break; - } - } - if (type.equals("finish")) { - // called when finishing the action - callback.onProgress(PROGRESS_STEP); - index++; - } - d(TAG, "Called r("+type+", "+endpoint+"). Getting "+targetEndpoints.get(index)); - switch (targetEndpoints.get(index)) { - case "Me": - getMe(); - break; - case "Schools": - getSchools(); - break; - case "Classes": - getClasses(); - break; - case "VirtualClasses": - getVirtualClasses(); - break; - case "Units": - getUnits(); - break; - case "Users": - getUsers(); - break; - case "Subjects": - getSubjects(); - break; - case "Classrooms": - getClassrooms(); - break; - case "Timetables": - getTimetables(); - break; - case "Substitutions": - getSubstitutions(); - break; - case "Colors": - getColors(); - break; - case "SavedGradeCategories": - getSavedGradeCategories(); - break; - case "GradesCategories": - getGradesCategories(); - break; - case "PointGradesCategories": - getPointGradesCategories(); - break; - case "DescriptiveGradesCategories": - getDescriptiveGradesSkills(); - break; - case "TextGradesCategories": - getTextGradesCategories(); - break; - case "BehaviourGradesCategories": - getBehaviourGradesCategories(); - break; - case "SaveGradeCategories": - saveGradeCategories(); - break; - case "Grades": - getGrades(); - break; - case "PointGrades": - getPointGrades(); - break; - case "DescriptiveGrades": - getDescriptiveGrades(); - break; - case "BehaviourGrades": - getBehaviourGrades(); - break; - case "Events": - getEvents(); - break; - case "CustomTypes": - getCustomTypes(); - break; - case "Homeworks": - getHomeworks(); - break; - case "LuckyNumbers": - getLuckyNumbers(); - break; - case "Notices": - getNotices(); - break; - case "AttendancesTypes": - getAttendancesTypes(); - break; - case "Attendances": - getAttendances(); - break; - case "Announcements": - getAnnouncements(); - break; - case "PtMeetings": - getPtMeetings(); - break; - case "TeacherFreeDaysTypes": - getTeacherFreeDaysTypes(); - break; - case "TeacherFreeDays": - getTeacherFreeDays(); - break; - case "MessagesLogin": - getMessagesLogin(); - break; - case "MessagesInbox": - getMessagesInbox(); - break; - case "MessagesOutbox": - getMessagesOutbox(); - break; - case "Finish": - finish(); - break; - } - } - private void saveData() { - if (teamList.size() > 0) { - //app.db.teamDao().clear(profileId); - app.db.teamDao().addAll(teamList); - } - if (teacherList.size() > 0 && teacherListChanged) - app.db.teacherDao().addAllIgnore(teacherList); - if (subjectList.size() > 0) - app.db.subjectDao().addAll(subjectList); - if (lessonList.size() > 0) { - app.db.lessonDao().clear(profileId); - app.db.lessonDao().addAll(lessonList); - } - if (lessonChangeList.size() > 0) - app.db.lessonChangeDao().addAll(lessonChangeList); - if (gradeCategoryList.size() > 0 && gradeCategoryListChanged) - app.db.gradeCategoryDao().addAll(gradeCategoryList); - if (gradeList.size() > 0) { - app.db.gradeDao().clear(profileId); - app.db.gradeDao().addAll(gradeList); - } - if (eventList.size() > 0) { - app.db.eventDao().removeFuture(profileId, Date.getToday()); - app.db.eventDao().addAll(eventList); - } - if (eventTypeList.size() > 0) - app.db.eventTypeDao().addAll(eventTypeList); - if (noticeList.size() > 0) { - app.db.noticeDao().clear(profileId); - app.db.noticeDao().addAll(noticeList); - } - if (attendanceList.size() > 0) - app.db.attendanceDao().addAll(attendanceList); - if (announcementList.size() > 0) - app.db.announcementDao().addAll(announcementList); - if (messageList.size() > 0) - app.db.messageDao().addAllIgnore(messageList); - if (messageRecipientList.size() > 0) - app.db.messageRecipientDao().addAll(messageRecipientList); - if (messageRecipientIgnoreList.size() > 0) - app.db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList); - if (metadataList.size() > 0) - app.db.metadataDao().addAllIgnore(metadataList); - if (messageMetadataList.size() > 0) - app.db.metadataDao().setSeen(messageMetadataList); - } - private void finish() { - try { - saveData(); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 480, CODE_OTHER, app.getString(R.string.sync_error_saving_data), null, null, e, null)); - } - if (fullSync) { - profile.setLastFullSync(System.currentTimeMillis()); - fullSync = false; - } - profile.setEmpty(false); - callback.onSuccess(activityContext, new ProfileFull(profile, loginStore)); - } - private void finishWithError(AppError error) { - try { - saveData(); - } - catch (Exception e) { - Crashlytics.logException(e); - } - callback.onError(activityContext, error); - } - - /* _ _ - | | (_) - | | ___ __ _ _ _ __ - | | / _ \ / _` | | '_ \ - | |___| (_) | (_| | | | | | - |______\___/ \__, |_|_| |_| - __/ | - |__*/ - public void login(@NonNull LoginCallback loginCallback) - { - - authorizeCallback = new AuthorizeCallback() { - @Override - public void onCsrfToken(String csrfToken) { - d(TAG, "Found CSRF token: "+csrfToken); - login(csrfToken, librusEmail, librusPassword, librusLoginCallback); - } - - @Override - public void onAuthorizationCode(String code) { - d(TAG, "Found auth code: "+code); - accessToken(code, null, accessTokenCallback); - } - }; - - librusLoginCallback = redirectUrl -> { - fakeAuthorize = "authorize2"; - authorize(AUTHORIZE_URL, authorizeCallback); - }; - - accessTokenCallback = new AccessTokenCallback() { - @Override - public void onSuccess(String tokenType, String accessToken, String refreshToken, int expiresIn) { - d(TAG, "Got tokens: "+tokenType+" "+accessToken); - d(TAG, "Got tokens: "+refreshToken); - loginStore.putLoginData("tokenType", tokenType); - loginStore.putLoginData("accessToken", accessToken); - loginStore.putLoginData("refreshToken", refreshToken); - loginStore.putLoginData("tokenExpiryTime", System.currentTimeMillis()/1000 + expiresIn); - getSynergiaToken(tokenType, accessToken, refreshToken, System.currentTimeMillis()/1000 + expiresIn); - } - - @Override - public void onError() { - d(TAG, "Beginning login (authorize)"); - authorize(AUTHORIZE_URL, authorizeCallback); - } - }; - - synergiaAccountsCallback = data -> { - d(TAG, "Accounts: "+data.toString()); - JsonArray accounts = data.getAsJsonArray("accounts"); - if (accounts.size() == 0) { - finishWithError(new AppError(TAG, 1237, CODE_OTHER, app.getString(R.string.sync_error_register_no_students), data)); - return; - } - long accountDataTime; - List accountIds = new ArrayList<>(); - List accountLogins = new ArrayList<>(); - List accountTokens = new ArrayList<>(); - List accountNamesLong = new ArrayList<>(); - List accountNamesShort = new ArrayList<>(); - accountIds.clear(); - accountLogins.clear(); - accountTokens.clear(); - accountNamesLong.clear(); - accountNamesShort.clear(); - accountDataTime = data.get("lastModification").getAsLong(); - for (JsonElement accountEl: accounts) { - JsonObject account = accountEl.getAsJsonObject(); - - JsonElement state = account.get("state"); - if (state != null && !(state instanceof JsonNull)) { - if (state.getAsString().equals("requiring_an_action")) { - finishWithError(new AppError(TAG, 694, CODE_LIBRUS_DISCONNECTED, data)); - return; - } - if (state.getAsString().equals("need-activation")) { - finishWithError(new AppError(TAG, 701, CODE_SYNERGIA_NOT_ACTIVATED, data)); - return; - } - } - - accountIds.add(account.get("id").getAsInt()); - accountLogins.add(account.get("login").getAsString()); - accountTokens.add(account.get("accessToken").getAsString()); - accountNamesLong.add(account.get("studentName").getAsString()); - String[] nameParts = account.get("studentName").getAsString().split(" "); - accountNamesShort.add(nameParts[0]+" "+nameParts[1].charAt(0)+"."); - } - - List profileList = new ArrayList<>(); - for (int index = 0; index < accountIds.size(); index++) { - Profile newProfile = new Profile(); - newProfile.setStudentNameLong(accountNamesLong.get(index)); - newProfile.setStudentNameShort(accountNamesShort.get(index)); - newProfile.setName(newProfile.getStudentNameLong()); - newProfile.setSubname(librusEmail); - newProfile.setEmpty(true); - newProfile.setLoggedIn(true); - newProfile.putStudentData("accountId", accountIds.get(index)); - newProfile.putStudentData("accountLogin", accountLogins.get(index)); - newProfile.putStudentData("accountToken", accountTokens.get(index)); - newProfile.putStudentData("accountTokenTime", accountDataTime); - profileList.add(newProfile); - } - - callback.onLoginFirst(profileList, loginStore); - }; - - synergiaAccountCallback = data -> { - if (data == null) { - // 410 Gone - app.cookieJar.clearForDomain("portal.librus.pl"); - authorize(AUTHORIZE_URL, authorizeCallback); - return; - } - if (profile == null) { - // this cannot be run on a fresh login - finishWithError(new AppError(TAG, 1290, CODE_PROFILE_NOT_FOUND, "Profile == null", data)); - return; - } - d(TAG, "Account: "+data.toString()); - // synergiaAccount is executed when a synergia token needs a refresh - JsonElement id = data.get("id"); - JsonElement login = data.get("login"); - JsonElement accessToken = data.get("accessToken"); - if (id == null || login == null || accessToken == null) { - finishWithError(new AppError(TAG, 1284, CODE_OTHER, data)); - return; - } - profile.putStudentData("accountId", id.getAsInt()); - profile.putStudentData("accountLogin", login.getAsString()); - profile.putStudentData("accountToken", accessToken.getAsString()); - profile.putStudentData("accountTokenTime", System.currentTimeMillis() / 1000); - profile.setStudentNameLong(data.get("studentName").getAsString()); - String[] nameParts = data.get("studentName").getAsString().split(" "); - profile.setStudentNameShort(nameParts[0] + " " + nameParts[1].charAt(0) + "."); - loginCallback.onSuccess(); - }; - - String tokenType = loginStore.getLoginData("tokenType", "Bearer"); - String accessToken = loginStore.getLoginData("accessToken", null); - String refreshToken = loginStore.getLoginData("refreshToken", null); - long tokenExpiryTime = loginStore.getLoginData("tokenExpiryTime", (long)0); - String accountToken; - - if (profile != null - && (accountToken = profile.getStudentData("accountToken", null)) != null - && !accountToken.equals("") - && (System.currentTimeMillis() / 1000) - profile.getStudentData("accountTokenTime", (long)0) < 3 * 60 * 60) { - c(TAG, "synergia token should be valid"); - loginCallback.onSuccess(); - } - else { - getSynergiaToken(tokenType, accessToken, refreshToken, tokenExpiryTime); - } - } - private String synergiaAccessToken = ""; - private void getSynergiaToken(String tokenType, String accessToken, String refreshToken, long tokenExpiryTime) { - c(TAG, "we have no synergia token or it expired"); - if (!tokenType.equals("") - && refreshToken != null - && accessToken != null - && tokenExpiryTime-30 > System.currentTimeMillis() / 1000) { - c(TAG, "we have a valid librus token, so we can use the API"); - // we have to decide whether we can already proceed getting the synergiaToken - // or a list of students - if (profile != null) { - app.cookieJar.clearForDomain("portal.librus.pl"); - c(TAG, "user is logged in, refreshing synergia token"); - d(TAG, "Librus token: "+accessToken); - synergiaAccount(tokenType, accessToken, profile.getStudentData("accountLogin", null), synergiaAccountCallback); - } - else { - // this *should* be executed only once. ever. - c(TAG, "user is not logged in, getting all the accounts"); - synergiaAccounts(tokenType, accessToken, synergiaAccountsCallback); - } - } else if (refreshToken != null) { - c(TAG, "we don't have a valid token or it expired"); - c(TAG, "but we have a refresh token"); - d(TAG, "Token expired at " + tokenExpiryTime + ", " + (System.currentTimeMillis() / 1000 - tokenExpiryTime) + " seconds ago"); - app.cookieJar.clearForDomain("portal.librus.pl"); - accessToken(null, refreshToken, accessTokenCallback); - } else { - c(TAG, "we don't have any of the needed librus tokens"); - c(TAG, "we need to log in and generate"); - app.cookieJar.clearForDomain("portal.librus.pl"); - authorize(AUTHORIZE_URL, authorizeCallback); - } - } - public boolean loginSynergia(@NonNull LoginCallback loginCallback) - { - if (profile == null) { - return false; - } - if (synergiaLogin == null || synergiaPassword == null || synergiaLogin.equals("") || synergiaPassword.equals("")) { - finishWithError(new AppError(TAG, 1152, CODE_INVALID_LOGIN, "Login field is empty")); - return false; - } - if (System.currentTimeMillis() - synergiaLastLoginTime < 10 * 60 * 1000 && synergiaLogin.equals(synergiaLastLogin)) {// 10 minutes - loginCallback.onSuccess(); - return true; - } - - String escapedPassword = synergiaPassword.replace("&", "&");// TODO: 2019-05-07 check other chars to escape - - String body = - "\n" + - "

\n" + - " \n" + - " "+synergiaLogin+"\n" + - " "+escapedPassword+"\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - synergiaRequest("Login", body, data -> { - if (data == null) { - finishWithError(new AppError(TAG, 1176, AppError.CODE_MAINTENANCE, "data == null (975)")); - return; - } - String error = data.select("response Login status").text(); - if (error.equals("ok")) { - synergiaLastLoginTime = System.currentTimeMillis(); - synergiaLastLogin = synergiaLogin; - loginCallback.onSuccess(); - } - else { - finishWithError(new AppError(TAG, 1186, CODE_INVALID_LOGIN, (Response) null, data.outerHtml())); - } - }); - return true; - } - - /* _ _ _ _ _ _ _ - | | | | | | ___ | | | | | | - | |__| | ___| |_ __ ___ _ __ ___ ( _ ) ___ __ _| | | |__ __ _ ___| | _____ - | __ |/ _ \ | '_ \ / _ \ '__/ __| / _ \/\ / __/ _` | | | '_ \ / _` |/ __| |/ / __| - | | | | __/ | |_) | __/ | \__ \ | (_> < | (_| (_| | | | |_) | (_| | (__| <\__ \ - |_| |_|\___|_| .__/ \___|_| |___/ \___/\/ \___\__,_|_|_|_.__/ \__,_|\___|_|\_\___/ - | | - |*/ - private String fakeAuthorize = "authorize"; - private void authorize(String url, AuthorizeCallback authorizeCallback) { - callback.onActionStarted(R.string.sync_action_authorizing); - Request.builder() - .url(fakeLogin ? "http://szkolny.eu/librus/"+fakeAuthorize+".php" : url) - .userAgent(userAgent) - .withClient(app.httpLazy) - .callback(new TextCallbackHandler() { - @Override - public void onSuccess(String data, Response response) { - //d("headers "+response.headers().toString()); - String location = response.headers().get("Location"); - if (location != null) { - Matcher authMatcher = Pattern.compile(REDIRECT_URL+"\\?code=([A-z0-9]+?)$", Pattern.DOTALL | Pattern.MULTILINE).matcher(location); - if (authMatcher.find()) { - authorizeCallback.onAuthorizationCode(authMatcher.group(1)); - } - else { - //callback.onError(activityContext, Edziennik.CODE_OTHER, "Auth code not found: "+location); - authorize(location, authorizeCallback); - } - } - else { - Matcher csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(data); - if (csrfMatcher.find()) { - authorizeCallback.onCsrfToken(csrfMatcher.group(1)); - } - else { - finishWithError(new AppError(TAG, 463, CODE_OTHER, "CSRF token not found.", response, data)); - } - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 207, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private void login(String csrfToken, String email, String password, LibrusLoginCallback librusLoginCallback) { - callback.onActionStarted(R.string.sync_action_logging_in); - Request.builder() - .url(fakeLogin ? "http://szkolny.eu/librus/login_action.php" : LOGIN_URL) - .userAgent(userAgent) - .addParameter("email", email) - .addParameter("password", password) - .addHeader("X-CSRF-TOKEN", csrfToken) - .contentType(MediaTypeUtils.APPLICATION_JSON) - .post() - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - if (response.parserErrorBody != null && response.parserErrorBody.contains("link aktywacyjny")) { - finishWithError(new AppError(TAG, 487, CODE_LIBRUS_NOT_ACTIVATED, response)); - return; - } - finishWithError(new AppError(TAG, 489, CODE_MAINTENANCE, response)); - return; - } - if (data.get("errors") != null) { - finishWithError(new AppError(TAG, 490, CODE_OTHER, data.get("errors").getAsJsonArray().get(0).getAsString(), response, data)); - return; - } - librusLoginCallback.onLogin(data.get("redirect") != null ? data.get("redirect").getAsString() : ""); - } - - @Override - public void onFailure(Response response, Throwable throwable) { - if (response.code() == 403 - || response.code() == 401) { - finishWithError(new AppError(TAG, 248, AppError.CODE_INVALID_LOGIN, response, throwable)); - return; - } - finishWithError(new AppError(TAG, 251, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private boolean refreshTokenFailed = false; - private void accessToken(String code, String refreshToken, AccessTokenCallback accessTokenCallback) { - callback.onActionStarted(R.string.sync_action_getting_token); - List> params = new ArrayList<>(); - params.add(new Pair<>("client_id", CLIENT_ID)); - if (code != null) { - params.add(new Pair<>("grant_type", "authorization_code")); - params.add(new Pair<>("code", code)); - params.add(new Pair<>("redirect_uri", REDIRECT_URL)); - } - else if (refreshToken != null) { - params.add(new Pair<>("grant_type", "refresh_token")); - params.add(new Pair<>("refresh_token", refreshToken)); - } - Request.builder() - .url(fakeLogin ? "http://szkolny.eu/librus/access_token.php" : TOKEN_URL) - .userAgent(userAgent) - .addParams(params) - .allowErrorCode(HTTP_UNAUTHORIZED) - .post() - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 539, CODE_MAINTENANCE, response)); - return; - } - if (data.get("error") != null) { - JsonElement message = data.get("message"); - JsonElement hint = data.get("hint"); - if (!refreshTokenFailed && refreshToken != null && hint != null && (hint.getAsString().equals("Token has been revoked") || hint.getAsString().equals("Token has expired"))) { - c(TAG, "refreshing the token failed. Trying to log in again."); - refreshTokenFailed = true; - accessTokenCallback.onError(); - return; - } - String errorText = data.get("error").getAsString()+" "+(message == null ? "" : message.getAsString())+" "+(hint == null ? "" : hint.getAsString()); - finishWithError(new AppError(TAG, 552, CODE_OTHER, errorText, response, data)); - return; - } - try { - accessTokenCallback.onSuccess( - data.get("token_type").getAsString(), - data.get("access_token").getAsString(), - data.get("refresh_token").getAsString(), - data.get("expires_in").getAsInt()); - } - catch (NullPointerException e) { - finishWithError(new AppError(TAG, 311, CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 317, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private void synergiaAccounts(String tokenType, String accessToken, SynergiaAccountsCallback synergiaAccountsCallback) { - callback.onActionStarted(R.string.sync_action_getting_accounts); - Request.builder() - .url(fakeLogin ? "http://szkolny.eu/librus/synergia_accounts.php" : ACCOUNTS_URL) - .userAgent(userAgent) - .addHeader("Authorization", tokenType+" "+accessToken) - .get() - .allowErrorCode(HTTP_FORBIDDEN) - .allowErrorCode(HTTP_UNAUTHORIZED) - .allowErrorCode(HTTP_BAD_REQUEST) - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 590, CODE_MAINTENANCE, response)); - return; - } - if (data.get("error") != null) { - JsonElement message = data.get("message"); - JsonElement hint = data.get("hint"); - String errorText = data.get("error").getAsString()+" "+(message == null ? "" : message.getAsString())+" "+(hint == null ? "" : hint.getAsString()); - finishWithError(new AppError(TAG, 597, CODE_OTHER, errorText, response, data)); - return; - } - try { - synergiaAccountsCallback.onSuccess(data); - } - catch (NullPointerException e) { - e.printStackTrace(); - finishWithError(new AppError(TAG, 358, CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 364, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private boolean error410 = false; - - private void synergiaAccount(String tokenType, String accessToken, String accountLogin, SynergiaAccountCallback synergiaAccountCallback) { - callback.onActionStarted(R.string.sync_action_getting_account); - d(TAG, "Requesting "+(fakeLogin ? "http://szkolny.eu/librus/synergia_accounts_fresh.php?login="+accountLogin : ACCOUNT_URL+accountLogin)); - if (accountLogin == null) { // just for safety - synergiaAccounts(tokenType, accessToken, synergiaAccountsCallback); - return; - } - Request.builder() - .url(fakeLogin ? "http://szkolny.eu/librus/synergia_accounts_fresh.php?login="+accountLogin : ACCOUNT_URL+accountLogin) - .userAgent(userAgent) - .addHeader("Authorization", tokenType+" "+accessToken) - .get() - .allowErrorCode(HTTP_NOT_FOUND) - .allowErrorCode(HTTP_FORBIDDEN) - .allowErrorCode(HTTP_UNAUTHORIZED) - .allowErrorCode(HTTP_BAD_REQUEST) - .allowErrorCode(HTTP_GONE) - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 641, CODE_MAINTENANCE, response)); - return; - } - if (response.code() == 410 && !error410) { - JsonElement reason = data.get("reason"); - if (reason != null && !(reason instanceof JsonNull) && reason.getAsString().equals("requires_an_action")) { - finishWithError(new AppError(TAG, 1078, CODE_LIBRUS_DISCONNECTED, response, data)); - return; - } - error410 = true; - synergiaAccountCallback.onSuccess(null); - return; - } - if (data.get("message") != null) { - String message = data.get("message").getAsString(); - if (message.equals("Account not found")) { - finishWithError(new AppError(TAG, 651, CODE_OTHER, app.getString(R.string.sync_error_register_student_not_associated_format, profile.getStudentNameLong(), accountLogin), response, data)); - return; - } - finishWithError(new AppError(TAG, 654, CODE_OTHER, message+"\n\n"+accountLogin, response, data)); - return; - } - if (response.code() == HTTP_OK) { - try { - synergiaAccountCallback.onSuccess(data); - } catch (NullPointerException e) { - e.printStackTrace(); - finishWithError(new AppError(TAG, 662, CODE_OTHER, response, e, data)); - } - } - else { - finishWithError(new AppError(TAG, 425, CODE_OTHER, response, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 432, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private int failed = 0; - - private void apiRequest(String endpoint, ApiRequestCallback apiRequestCallback) { - d(TAG, "Requesting "+API_URL+endpoint); - Request.builder() - .url(fakeLogin ? "http://szkolny.eu/librus/api/"+endpoint : API_URL+endpoint) - .userAgent(userAgent) - .addHeader("Authorization", "Bearer "+synergiaAccessToken) - .get() - .allowErrorCode(HTTP_FORBIDDEN) - .allowErrorCode(HTTP_UNAUTHORIZED) - .allowErrorCode(HTTP_BAD_REQUEST) - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - if (response.parserErrorBody != null && response.parserErrorBody.equals("Nieprawidłowy węzeł.")) { - apiRequestCallback.onSuccess(null); - return; - } - finishWithError(new AppError(TAG, 453, CODE_MAINTENANCE, response)); - return; - } - if (data.get("Status") != null) { - JsonElement message = data.get("Message"); - JsonElement code = data.get("Code"); - d(TAG, "apiRequest Error "+data.get("Status").getAsString()+" "+(message == null ? "" : message.getAsString())+" "+(code == null ? "" : code.getAsString())+"\n\n"+response.request().url().toString()); - if (message != null && !(message instanceof JsonNull) && message.getAsString().equals("Student timetable is not public")) { - try { - apiRequestCallback.onSuccess(null); - } - catch (NullPointerException e) { - e.printStackTrace(); - d(TAG, "apiRequest exception "+e.getMessage()); - finishWithError(new AppError(TAG, 503, CODE_OTHER, response, e, data)); - } - return; - } - if (code != null - && !(code instanceof JsonNull) - && (code.getAsString().equals("LuckyNumberIsNotActive") - || code.getAsString().equals("NotesIsNotActive") - || code.getAsString().equals("AccessDeny")) - ) { - try { - apiRequestCallback.onSuccess(null); - } - catch (NullPointerException e) { - e.printStackTrace(); - d(TAG, "apiRequest exception "+e.getMessage()); - finishWithError(new AppError(TAG, 504, CODE_OTHER, response, e, data)); - } - return; - } - String errorText = data.get("Status").getAsString()+" "+(message == null ? "" : message.getAsString())+" "+(code == null ? "" : code.getAsString()); - if (code != null && !(code instanceof JsonNull) && code.getAsString().equals("TokenIsExpired")) { - failed++; - d(TAG, "Trying to refresh synergia token, api request failed "+failed+" times now"); - if (failed > 1) { - d(TAG, "Giving up, failed "+failed+" times"); - finishWithError(new AppError(TAG, 485, CODE_OTHER, errorText, response, data)); - return; - } - String tokenType = loginStore.getLoginData("tokenType", "Bearer"); - String accessToken = loginStore.getLoginData("accessToken", null); - String refreshToken = loginStore.getLoginData("refreshToken", null); - long tokenExpiryTime = loginStore.getLoginData("tokenExpiryTime", (long)0); - getSynergiaToken(tokenType, accessToken, refreshToken, tokenExpiryTime); - return; - } - finishWithError(new AppError(TAG, 497, CODE_OTHER, errorText, response, data)); - return; - } - try { - apiRequestCallback.onSuccess(data); - } - catch (NullPointerException e) { - e.printStackTrace(); - d(TAG, "apiRequest exception "+e.getMessage()); - finishWithError(new AppError(TAG, 505, CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - if (response.code() == 405) { - // method not allowed - finishWithError(new AppError(TAG, 511, CODE_OTHER, response, throwable)); - return; - } - if (response.code() == 500) { - // TODO: 2019-09-10 dirty hotfix - if ("Classrooms".equals(endpoint)) { - apiRequestCallback.onSuccess(null); - return; - } - finishWithError(new AppError(TAG, 516, CODE_MAINTENANCE, response, throwable)); - return; - } - finishWithError(new AppError(TAG, 520, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private void synergiaRequest(String endpoint, String body, SynergiaRequestCallback synergiaRequestCallback) { - d(TAG, "Requesting "+SYNERGIA_URL+endpoint); - Request.builder() - .url(SYNERGIA_URL+endpoint) - .userAgent(synergiaUserAgent) - .setTextBody(body, MediaTypeUtils.APPLICATION_XML) - .callback(new TextCallbackHandler() { - @Override - public void onSuccess(String data, Response response) { - if ((data.contains("error") || data.contains("")) && !data.contains("Niepoprawny")) { - finishWithError(new AppError(TAG, 541, AppError.CODE_MAINTENANCE, response, data)); - return; - } - synergiaRequestCallback.onSuccess(Jsoup.parse(data, "", Parser.xmlParser())); - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 556, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - // CALLBACKS & INTERFACES - private interface AuthorizeCallback { - void onCsrfToken(String csrfToken); - void onAuthorizationCode(String code); - } - private interface LibrusLoginCallback { - void onLogin(String redirectUrl); - } - private interface AccessTokenCallback { - void onSuccess(String tokenType, String accessToken, String refreshToken, int expiresIn); - void onError(); - } - private interface SynergiaAccountsCallback { - void onSuccess(JsonObject data); - } - private interface SynergiaAccountCallback { - void onSuccess(JsonObject data); - } - private interface ApiRequestCallback { - void onSuccess(JsonObject data); - } - private interface SynergiaRequestCallback { - void onSuccess(Document data); - } - - private AuthorizeCallback authorizeCallback; - private LibrusLoginCallback librusLoginCallback; - private AccessTokenCallback accessTokenCallback; - private SynergiaAccountsCallback synergiaAccountsCallback; - private SynergiaAccountCallback synergiaAccountCallback; - - /* _____ _ _____ _ - | __ \ | | | __ \ | | - | | | | __ _| |_ __ _ | |__) |___ __ _ _ _ ___ ___| |_ ___ - | | | |/ _` | __/ _` | | _ // _ \/ _` | | | |/ _ \/ __| __/ __| - | |__| | (_| | || (_| | | | \ \ __/ (_| | |_| | __/\__ \ |_\__ \ - |_____/ \__,_|\__\__,_| |_| \_\___|\__, |\__,_|\___||___/\__|___/ - | | - |*/ - private void getMe() { - if (!fullSync) { - r("finish", "Me"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_account_info); - apiRequest("Me", data -> { - JsonObject me = data.get("Me").getAsJsonObject(); - me = me.get("Account").getAsJsonObject(); - //d("Got School: "+school.toString()); - try { - boolean premium = me.get("IsPremium").getAsBoolean(); - boolean premiumDemo = me.get("IsPremiumDemo").getAsBoolean(); - this.premium = premium || premiumDemo; - profile.putStudentData("isPremium", premium || premiumDemo); - r("finish", "Me"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1316, CODE_OTHER, e, data)); - } - }); - } - - private Map> lessonRanges = new HashMap<>(); - private String schoolName = ""; - private void getSchools() { - if (!fullSync) { - try { - lessonRanges = app.gson.fromJson(profile.getStudentData("lessonRanges", "{}"), new TypeToken>>() { - }.getType()); - if (lessonRanges != null && lessonRanges.size() > 0) { - r("finish", "Schools"); - return; - } - } - catch (Exception e) { - e.printStackTrace(); - } - } - lessonRanges = new HashMap<>(); - callback.onActionStarted(R.string.sync_action_syncing_school_info); - apiRequest("Schools", data -> { - //d("Got School: "+school.toString()); - try { - JsonObject school = data.get("School").getAsJsonObject(); - int schoolId = school.get("Id").getAsInt(); - String schoolNameLong = school.get("Name").getAsString(); - StringBuilder schoolNameShort = new StringBuilder(); - for (String schoolNamePart: schoolNameLong.split(" ")) { - if (schoolNamePart.isEmpty()) - continue; - schoolNameShort.append(Character.toLowerCase(schoolNamePart.charAt(0))); - } - String schoolTown = school.get("Town").getAsString(); - schoolName = schoolId+schoolNameShort.toString()+"_"+schoolTown.toLowerCase(); - profile.putStudentData("schoolName", schoolName); - - lessonRanges.clear(); - int index = 0; - for (JsonElement lessonRangeEl: school.get("LessonsRange").getAsJsonArray()) { - JsonObject lr = lessonRangeEl.getAsJsonObject(); - JsonElement from = lr.get("From"); - JsonElement to = lr.get("To"); - if (from != null && to != null && !(from instanceof JsonNull) && !(to instanceof JsonNull)) { - lessonRanges.put(index, new Pair<>(Time.fromH_m(from.getAsString()), Time.fromH_m(to.getAsString()))); - } - index++; - } - profile.putStudentData("lessonRanges", app.gson.toJson(lessonRanges)); - r("finish", "Schools"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1364, CODE_OTHER, e, data)); - } - }); - } - - private long teamClassId = -1; - private void getClasses() { - if (!fullSync) { - r("finish", "Classes"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_class); - apiRequest("Classes", data -> { - //d("Got Class: "+myClass.toString()); - try { - JsonObject myClass = data.get("Class").getAsJsonObject(); - String teamName = myClass.get("Number").getAsString() - + myClass.get("Symbol").getAsString(); - teamClassId = myClass.get("Id").getAsLong(); - teamList.add(new Team( - profileId, - teamClassId, - teamName, - 1, - schoolName+":"+teamName, - myClass.get("ClassTutor").getAsJsonObject().get("Id").getAsLong())); - JsonElement semester1Begin = myClass.get("BeginSchoolYear"); - JsonElement semester2Begin = myClass.get("EndFirstSemester"); - JsonElement yearEnd = myClass.get("EndSchoolYear"); - if (semester1Begin != null - && semester2Begin != null - && yearEnd != null - && !(semester1Begin instanceof JsonNull) - && !(semester2Begin instanceof JsonNull) - && !(yearEnd instanceof JsonNull)) { - profile.setDateSemester1Start(Date.fromY_m_d(semester1Begin.getAsString())); - profile.setDateSemester2Start(Date.fromY_m_d(semester2Begin.getAsString())); - profile.setDateYearEnd(Date.fromY_m_d(yearEnd.getAsString())); - } - JsonElement unit = myClass.get("Unit"); - if (unit != null && !(unit instanceof JsonNull)) { - profile.putStudentData("unitId", unit.getAsJsonObject().get("Id").getAsLong()); - } - r("finish", "Classes"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1411, CODE_OTHER, e, data)); - } - }); - } - - private void getVirtualClasses() { - if (!fullSync) { - r("finish", "VirtualClasses"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_teams); - apiRequest("VirtualClasses", data -> { - if (data == null) { - r("finish", "VirtualClasses"); - return; - } - try { - JsonArray classes = data.get("VirtualClasses").getAsJsonArray(); - for (JsonElement myClassEl: classes) { - JsonObject myClass = myClassEl.getAsJsonObject(); - String teamName = myClass.get("Name").getAsString(); - long teamId = myClass.get("Id").getAsLong(); - long teacherId = -1; - JsonElement el; - if ((el = myClass.get("Teacher")) != null) { - teacherId = el.getAsJsonObject().get("Id").getAsLong(); - } - teamList.add(new Team( - profileId, - teamId, - teamName, - 2, - schoolName + ":" + teamName, - teacherId)); - } - r("finish", "VirtualClasses"); - } catch (Exception e) { - finishWithError(new AppError(TAG, 1449, CODE_OTHER, e, data)); - } - }); - } - - private void getUnits() { - if (!fullSync) { - enableStandardGrades = profile.getStudentData("enableStandardGrades", true); - enablePointGrades = profile.getStudentData("enablePointGrades", false); - enableDescriptiveGrades = profile.getStudentData("enableDescriptiveGrades", false); - enableTextGrades = profile.getStudentData("enableTextGrades", false); - enableBehaviourGrades = profile.getStudentData("enableBehaviourGrades", true); - startPointsSemester1 = profile.getStudentData("startPointsSemester1", 0); - startPointsSemester2 = profile.getStudentData("startPointsSemester2", 0); - r("finish", "Units"); - return; - } - d(TAG, "Grades settings: "+enableStandardGrades+", "+enablePointGrades+", "+enableDescriptiveGrades); - callback.onActionStarted(R.string.sync_action_syncing_school_info); - apiRequest("Units", data -> { - if (data == null) { - r("finish", "Units"); - return; - } - JsonArray units = data.getAsJsonArray("Units"); - try { - long unitId = profile.getStudentData("unitId", (long)-1); - enableStandardGrades = true; // once a week or two (during a full sync) force getting the standard grade list. If there aren't any, disable it again later. - enableBehaviourGrades = true; - enableTextGrades = true; // TODO: 2019-05-13 if "DescriptiveGradesEnabled" are also TextGrades - profile.putStudentData("enableStandardGrades", true); - profile.putStudentData("enableBehaviourGrades", true); - profile.putStudentData("enableTextGrades", true); - for (JsonElement unitEl: units) { - JsonObject unit = unitEl.getAsJsonObject(); - - if (unit.get("Id").getAsLong() == unitId) { - JsonObject gradesSettings = unit.getAsJsonObject("GradesSettings"); - enablePointGrades = gradesSettings.get("PointGradesEnabled").getAsBoolean(); - enableDescriptiveGrades = gradesSettings.get("DescriptiveGradesEnabled").getAsBoolean(); - - JsonObject behaviourGradesSettings = unit.getAsJsonObject("BehaviourGradesSettings"); - JsonObject startPoints = behaviourGradesSettings.getAsJsonObject("StartPoints"); - - startPointsSemester1 = startPoints.get("Semester1").getAsInt(); - JsonElement startPointsSemester2El; - if ((startPointsSemester2El = startPoints.get("Semester2")) != null) { - startPointsSemester2 = startPointsSemester2El.getAsInt(); - } - else { - startPointsSemester2 = startPointsSemester1; - } - - profile.putStudentData("enablePointGrades", enablePointGrades); - profile.putStudentData("enableDescriptiveGrades", enableDescriptiveGrades); - profile.putStudentData("startPointsSemester1", startPointsSemester1); - profile.putStudentData("startPointsSemester2", startPointsSemester2); - break; - } - } - d(TAG, "Grades settings: "+enableStandardGrades+", "+enablePointGrades+", "+enableDescriptiveGrades); - r("finish", "Units"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1506, CODE_OTHER, e, data)); - } - }); - } - - private boolean teacherListChanged = false; - private void getUsers() { - if (!fullSync) { - r("finish", "Users"); - teacherList = app.db.teacherDao().getAllNow(profileId); - teacherListChanged = false; - return; - } - callback.onActionStarted(R.string.sync_action_syncing_users); - apiRequest("Users", data -> { - if (data == null) { - r("finish", "Users"); - return; - } - JsonArray users = data.get("Users").getAsJsonArray(); - //d("Got Users: "+users.toString()); - try { - teacherListChanged = true; - for (JsonElement userEl : users) { - JsonObject user = userEl.getAsJsonObject(); - JsonElement firstName = user.get("FirstName"); - JsonElement lastName = user.get("LastName"); - teacherList.add(new Teacher( - profileId, - user.get("Id").getAsLong(), - firstName instanceof JsonNull ? "" : firstName.getAsString(), - lastName instanceof JsonNull ? "" : lastName.getAsString() - )); - } - r("finish", "Users"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1544, CODE_OTHER, e, data)); - } - }); - } - - private void getSubjects() { - if (!fullSync) { - r("finish", "Subjects"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_subjects); - apiRequest("Subjects", data -> { - if (data == null) { - r("finish", "Subjects"); - return; - } - JsonArray subjects = data.get("Subjects").getAsJsonArray(); - //d("Got Subjects: "+subjects.toString()); - try { - for (JsonElement subjectEl : subjects) { - JsonObject subject = subjectEl.getAsJsonObject(); - JsonElement longName = subject.get("Name"); - JsonElement shortName = subject.get("Short"); - subjectList.add(new Subject( - profileId, - subject.get("Id").getAsLong(), - longName instanceof JsonNull ? "" : longName.getAsString(), - shortName instanceof JsonNull ? "" : shortName.getAsString() - )); - } - subjectList.add(new Subject( - profileId, - 1, - "Zachowanie", - "zach" - )); - r("finish", "Subjects"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1588, CODE_OTHER, e, data)); - } - }); - } - - private SparseArray classrooms = new SparseArray<>(); - private void getClassrooms() { - //if (!fullSync) - // r("finish", "Classrooms"); - callback.onActionStarted(R.string.sync_action_syncing_classrooms); - apiRequest("Classrooms", data -> { - if (data == null) { - r("finish", "Classrooms"); - return; - } - JsonArray jClassrooms = data.get("Classrooms").getAsJsonArray(); - //d("Got Classrooms: "+jClassrooms.toString()); - classrooms.clear(); - try { - for (JsonElement classroomEl : jClassrooms) { - JsonObject classroom = classroomEl.getAsJsonObject(); - classrooms.put(classroom.get("Id").getAsInt(), classroom.get("Name").getAsString()); - } - r("finish", "Classrooms"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1617, CODE_OTHER, e, data)); - } - }); - } - - private void getTimetables() { - callback.onActionStarted(R.string.sync_action_syncing_timetable); - Date weekStart = Week.getWeekStart(); - if (Date.getToday().getWeekDay() > 4) { - weekStart.stepForward(0, 0, 7); - } - apiRequest("Timetables?weekStart="+weekStart.getStringY_m_d(), data -> { - if (data == null) { - r("finish", "Timetables"); - return; - } - JsonObject timetables = data.get("Timetable").getAsJsonObject(); - try { - for (Map.Entry dayEl: timetables.entrySet()) { - JsonArray day = dayEl.getValue().getAsJsonArray(); - for (JsonElement lessonGroupEl: day) { - if ((lessonGroupEl instanceof JsonArray && ((JsonArray) lessonGroupEl).size() == 0) || lessonGroupEl instanceof JsonNull || lessonGroupEl == null) { - continue; - } - JsonArray lessonGroup = lessonGroupEl.getAsJsonArray(); - for (JsonElement lessonEl: lessonGroup) { - if ((lessonEl instanceof JsonArray && ((JsonArray) lessonEl).size() == 0) || lessonEl instanceof JsonNull || lessonEl == null) { - continue; - } - JsonObject lesson = lessonEl.getAsJsonObject(); - - boolean substitution = false; - boolean cancelled = false; - JsonElement isSubstitutionClass; - if ((isSubstitutionClass = lesson.get("IsSubstitutionClass")) != null) { - substitution = isSubstitutionClass.getAsBoolean(); - } - JsonElement isCanceled; - if ((isCanceled = lesson.get("IsCanceled")) != null) { - cancelled = isCanceled.getAsBoolean(); - } - - if (substitution && cancelled) { - // the lesson is probably shifted. Skip this one - continue; - } - - Lesson lessonObject = new Lesson( - profileId, - lesson.get("DayNo").getAsInt() - 1, - Time.fromH_m(lesson.get(substitution && !cancelled ? "OrgHourFrom" : "HourFrom").getAsString()), - Time.fromH_m(lesson.get(substitution && !cancelled ? "OrgHourTo" : "HourTo").getAsString()) - ); - - JsonElement subject; - if ((subject = lesson.get(substitution && !cancelled ? "OrgSubject" : "Subject")) != null) { - lessonObject.subjectId = subject.getAsJsonObject().get("Id").getAsLong(); - } - - JsonElement teacher; - if ((teacher = lesson.get(substitution && !cancelled ? "OrgTeacher" : "Teacher")) != null) { - lessonObject.teacherId = teacher.getAsJsonObject().get("Id").getAsLong(); - } - - JsonElement myClass; - if ((myClass = lesson.get("Class")) != null) { - lessonObject.teamId = myClass.getAsJsonObject().get("Id").getAsLong(); - } - if (myClass == null && (myClass = lesson.get("VirtualClass")) != null) { - lessonObject.teamId = myClass.getAsJsonObject().get("Id").getAsLong(); - } - - JsonElement classroom; - if ((classroom = lesson.get(substitution && !cancelled ? "OrgClassroom" : "Classroom")) != null) { - lessonObject.classroomName = classrooms.get(classroom.getAsJsonObject().get("Id").getAsInt()); - } - - lessonList.add(lessonObject); - } - } - } - r("finish", "Timetables"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1704, CODE_OTHER, e, data)); - } - }); - } - - private void getSubstitutions() { - callback.onActionStarted(R.string.sync_action_syncing_timetable_changes); - apiRequest("Calendars/Substitutions", data -> { - if (data == null) { - r("finish", "Substitutions"); - return; - } - - JsonArray substitutions = data.get("Substitutions").getAsJsonArray(); - try { - List ignoreList = new ArrayList<>(); - for (JsonElement substitutionEl : substitutions) { - JsonObject substitution = substitutionEl.getAsJsonObject(); - - String str_date = substitution.get("OrgDate").getAsString(); - Date lessonDate = Date.fromY_m_d(str_date); - - Time startTime = Time.getNow(); - JsonElement lessonNo; - if (!((lessonNo = substitution.get("OrgLessonNo")) instanceof JsonNull)) { - Pair timePair = lessonRanges.get(lessonNo.getAsInt()); - if (timePair != null) - startTime = timePair.first; - } - - JsonElement isShifted; - JsonElement isCancelled; - if ((isShifted = substitution.get("IsShifted")) != null && isShifted.getAsBoolean()) { - // a lesson is shifted - // add a TYPE_CANCELLED for the source lesson and a TYPE_CHANGE for the destination lesson - - // source lesson: cancel - LessonChange lessonCancelled = new LessonChange(profileId, lessonDate, startTime, startTime.clone().stepForward(0, 45, 0)); - lessonCancelled.type = TYPE_CANCELLED; - lessonChangeList.add(lessonCancelled); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonCancelled.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - - // target lesson: change - startTime = Time.getNow(); - if (!((lessonNo = substitution.get("LessonNo")) instanceof JsonNull)) { - Pair timePair = lessonRanges.get(lessonNo.getAsInt()); - if (timePair != null) - startTime = timePair.first; - } - - LessonChange lessonChanged = new LessonChange(profileId, lessonDate, startTime, startTime.clone().stepForward(0, 45, 0)); - lessonChanged.type = TYPE_CHANGE; - JsonElement subject; - if ((subject = substitution.get("Subject")) != null) { - lessonChanged.subjectId = subject.getAsJsonObject().get("Id").getAsLong(); - } - JsonElement teacher; - if ((teacher = substitution.get("Teacher")) != null) { - lessonChanged.teacherId = teacher.getAsJsonObject().get("Id").getAsLong(); - } - JsonElement myClass; - if ((myClass = substitution.get("Class")) != null) { - lessonChanged.teamId = myClass.getAsJsonObject().get("Id").getAsLong(); - } - if (myClass == null && (myClass = substitution.get("VirtualClass")) != null) { - lessonChanged.teamId = myClass.getAsJsonObject().get("Id").getAsLong(); - } - lessonChangeList.add(lessonChanged); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonChanged.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - - // ignore the target lesson in further array elements - it's already changed - ignoreList.add(lessonChanged.lessonDate.combineWith(lessonChanged.startTime)); - } - else if ((isCancelled = substitution.get("IsCancelled")) != null && isCancelled.getAsBoolean()) { - LessonChange lessonChange = new LessonChange(profileId, lessonDate, startTime, startTime.clone().stepForward(0, 45, 0)); - // if it's actually a lesson shift - ignore the target lesson cancellation - if (ignoreList.size() > 0 && ignoreList.contains(lessonChange.lessonDate.combineWith(lessonChange.startTime))) - continue; - lessonChange.type = TYPE_CANCELLED; - lessonChangeList.add(lessonChange); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonChange.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - else { - LessonChange lessonChange = new LessonChange(profileId, lessonDate, startTime, startTime.clone().stepForward(0, 45, 0)); - - lessonChange.type = TYPE_CHANGE; - - JsonElement subject; - if ((subject = substitution.get("Subject")) != null) { - lessonChange.subjectId = subject.getAsJsonObject().get("Id").getAsLong(); - } - JsonElement teacher; - if ((teacher = substitution.get("Teacher")) != null) { - lessonChange.teacherId = teacher.getAsJsonObject().get("Id").getAsLong(); - } - - JsonElement myClass; - if ((myClass = substitution.get("Class")) != null) { - lessonChange.teamId = myClass.getAsJsonObject().get("Id").getAsLong(); - } - if (myClass == null && (myClass = substitution.get("VirtualClass")) != null) { - lessonChange.teamId = myClass.getAsJsonObject().get("Id").getAsLong(); - } - - lessonChangeList.add(lessonChange); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonChange.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - - } - r("finish", "Substitutions"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1822, CODE_OTHER, e, data)); - } - }); - } - - private SparseIntArray colors = new SparseIntArray(); - private void getColors() { - colors.put( 1, 0xFFF0E68C); - colors.put( 2, 0xFF87CEFA); - colors.put( 3, 0xFFB0C4DE); - colors.put( 4, 0xFFF0F8FF); - colors.put( 5, 0xFFF0FFFF); - colors.put( 6, 0xFFF5F5DC); - colors.put( 7, 0xFFFFEBCD); - colors.put( 8, 0xFFFFF8DC); - colors.put( 9, 0xFFA9A9A9); - colors.put(10, 0xFFBDB76B); - colors.put(11, 0xFF8FBC8F); - colors.put(12, 0xFFDCDCDC); - colors.put(13, 0xFFDAA520); - colors.put(14, 0xFFE6E6FA); - colors.put(15, 0xFFFFA07A); - colors.put(16, 0xFF32CD32); - colors.put(17, 0xFF66CDAA); - colors.put(18, 0xFF66CDAA); - colors.put(19, 0xFFC0C0C0); - colors.put(20, 0xFFD2B48C); - colors.put(21, 0xFF3333FF); - colors.put(22, 0xFF7B68EE); - colors.put(23, 0xFFBA55D3); - colors.put(24, 0xFFFFB6C1); - colors.put(25, 0xFFFF1493); - colors.put(26, 0xFFDC143C); - colors.put(27, 0xFFFF0000); - colors.put(28, 0xFFFF8C00); - colors.put(29, 0xFFFFD700); - colors.put(30, 0xFFADFF2F); - colors.put(31, 0xFF7CFC00); - r("finish", "Colors"); - /* - apiRequest("Colors", data -> { - JsonArray jColors = data.get("Colors").getAsJsonArray(); - d("Got Colors: "+jColors.toString()); - colors.clear(); - try { - for (JsonElement colorEl : jColors) { - JsonObject color = colorEl.getAsJsonObject(); - colors.put(color.get("Id").getAsInt(), Color.parseColor("#"+color.get("RGB").getAsString())); - } - } - catch (Exception e) { - e.printStackTrace(); - } - getGrades(); - });*/ - } - - private boolean gradeCategoryListChanged = false; - private void getSavedGradeCategories() { - gradeCategoryList = app.db.gradeCategoryDao().getAllNow(profileId); - gradeCategoryListChanged = false; - r("finish", "SavedGradeCategories"); - } - - private void saveGradeCategories() { - r("finish", "SaveGradeCategories"); - } - - private void getGradesCategories() { - if (!fullSync) { - // cancel every not-full sync; no need to download categories again - // every full sync it'll be enabled to make sure there are no grades - by getUnits - r("finish", "GradesCategories"); - return; - } - // not a full sync. Will get all grade categories. Clear the current list. - gradeCategoryList.clear(); - - callback.onActionStarted(R.string.sync_action_syncing_grade_categories); - apiRequest("Grades/Categories", data -> { - if (data == null) { - r("finish", "GradesCategories"); - return; - } - JsonArray categories = data.get("Categories").getAsJsonArray(); - enableStandardGrades = categories.size() > 0; - profile.putStudentData("enableStandardGrades", enableStandardGrades); - if (!enableStandardGrades) { - r("finish", "GradesCategories"); - return; - } - gradeCategoryListChanged = true; - //d("Got Grades/Categories: "+categories.toString()); - try { - for (JsonElement categoryEl : categories) { - JsonObject category = categoryEl.getAsJsonObject(); - JsonElement name = category.get("Name"); - JsonElement weight = category.get("Weight"); - JsonElement color = category.get("Color"); - JsonElement countToTheAverage = category.get("CountToTheAverage"); - int colorInt = Color.BLUE; - if (!(color instanceof JsonNull) && color != null) { - colorInt = colors.get(color.getAsJsonObject().get("Id").getAsInt()); - } - boolean countToTheAverageBool = !(countToTheAverage instanceof JsonNull) && countToTheAverage != null && countToTheAverage.getAsBoolean(); - int weightInt = weight instanceof JsonNull || weight == null || !countToTheAverageBool ? 0 : weight.getAsInt(); - int categoryId = category.get("Id").getAsInt(); - gradeCategoryList.add(new GradeCategory( - profileId, - categoryId, - weightInt, - colorInt, - name instanceof JsonNull || name == null ? "" : name.getAsString() - )); - } - r("finish", "GradesCategories"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1954, CODE_OTHER, e, data)); - } - }); - } - - private void getPointGradesCategories() { - if (!fullSync || !enablePointGrades) { - // cancel every not-full sync; no need to download categories again - // or - // if it's a full sync, point grades may have already been disabled in getUnits - r("finish", "PointGradesCategories"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_point_grade_categories); - apiRequest("PointGrades/Categories", data -> { - if (data == null) { - r("finish", "PointGradesCategories"); - return; - } - JsonArray categories = data.get("Categories").getAsJsonArray(); - enablePointGrades = categories.size() > 0; - profile.putStudentData("enablePointGrades", enablePointGrades); - if (!enablePointGrades) { - r("finish", "PointGradesCategories"); - return; - } - gradeCategoryListChanged = true; - //d("Got Grades/Categories: "+categories.toString()); - for (JsonElement categoryEl : categories) { - JsonObject category = categoryEl.getAsJsonObject(); - JsonElement name = category.get("Name"); - JsonElement weight = category.get("Weight"); - JsonElement color = category.get("Color"); - JsonElement countToTheAverage = category.get("CountToTheAverage"); - JsonElement valueFrom = category.get("ValueFrom"); - JsonElement valueTo = category.get("ValueTo"); - int colorInt = Color.BLUE; - if (!(color instanceof JsonNull) && color != null) { - colorInt = colors.get(color.getAsJsonObject().get("Id").getAsInt()); - } - boolean countToTheAverageBool = !(countToTheAverage instanceof JsonNull) && countToTheAverage != null && countToTheAverage.getAsBoolean(); - int weightInt = weight instanceof JsonNull || weight == null || !countToTheAverageBool ? 0 : weight.getAsInt(); - int categoryId = category.get("Id").getAsInt(); - float valueFromFloat = valueFrom.getAsFloat(); - float valueToFloat = valueTo.getAsFloat(); - gradeCategoryList.add( - new GradeCategory( - profileId, - categoryId, - weightInt, - colorInt, - name instanceof JsonNull || name == null ? "" : name.getAsString() - ).setValueRange(valueFromFloat, valueToFloat) - ); - } - r("finish", "PointGradesCategories"); - }); - } - - private void getDescriptiveGradesSkills() { - if (!fullSync || !enableDescriptiveGrades) { - // cancel every not-full sync; no need to download categories again - // or - // if it's a full sync, descriptive grades may have already been disabled in getUnits - r("finish", "DescriptiveGradesCategories"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_descriptive_grade_categories); - apiRequest("DescriptiveTextGrades/Skills", data -> { - if (data == null) { - r("finish", "DescriptiveGradesCategories"); - return; - } - JsonArray categories = data.get("Skills").getAsJsonArray(); - enableDescriptiveGrades = categories.size() > 0; - profile.putStudentData("enableDescriptiveGrades", enableDescriptiveGrades); - if (!enableDescriptiveGrades) { - r("finish", "DescriptiveGradesCategories"); - return; - } - gradeCategoryListChanged = true; - //d("Got Grades/Categories: "+categories.toString()); - for (JsonElement categoryEl : categories) { - JsonObject category = categoryEl.getAsJsonObject(); - JsonElement name = category.get("Name"); - JsonElement color = category.get("Color"); - int colorInt = Color.BLUE; - if (!(color instanceof JsonNull) && color != null) { - colorInt = colors.get(color.getAsJsonObject().get("Id").getAsInt()); - } - int weightInt = -1; - int categoryId = category.get("Id").getAsInt(); - gradeCategoryList.add(new GradeCategory( - profileId, - categoryId, - weightInt, - colorInt, - name instanceof JsonNull || name == null ? "" : name.getAsString() - )); - } - r("finish", "DescriptiveGradesCategories"); - }); - } - - private void getTextGradesCategories() { - if (!fullSync || !enableTextGrades) { - // cancel every not-full sync; no need to download categories again - // or - // if it's a full sync, text grades may have already been disabled in getUnits - r("finish", "TextGradesCategories"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_descriptive_grade_categories); - apiRequest("TextGrades/Categories", data -> { - if (data == null) { - r("finish", "TextGradesCategories"); - return; - } - JsonArray categories = data.get("Categories").getAsJsonArray(); - enableTextGrades = categories.size() > 0; - profile.putStudentData("enableTextGrades", enableTextGrades); - if (!enableTextGrades) { - r("finish", "TextGradesCategories"); - return; - } - gradeCategoryListChanged = true; - //d("Got Grades/Categories: "+categories.toString()); - for (JsonElement categoryEl : categories) { - JsonObject category = categoryEl.getAsJsonObject(); - JsonElement name = category.get("Name"); - JsonElement color = category.get("Color"); - int colorInt = Color.BLUE; - if (!(color instanceof JsonNull) && color != null) { - colorInt = colors.get(color.getAsJsonObject().get("Id").getAsInt()); - } - int weightInt = -1; - int categoryId = category.get("Id").getAsInt(); - gradeCategoryList.add(new GradeCategory( - profileId, - categoryId, - weightInt, - colorInt, - name instanceof JsonNull || name == null ? "" : name.getAsString() - )); - } - r("finish", "TextGradesCategories"); - }); - } - - private void getBehaviourGradesCategories() { - if (!fullSync || !enableBehaviourGrades) { - // cancel every not-full sync; no need to download categories again - // or - // if it's a full sync, descriptive grades may have already been disabled in getUnits - r("finish", "BehaviourGradesCategories"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_behaviour_grade_categories); - apiRequest("BehaviourGrades/Points/Categories", data -> { - if (data == null) { - r("finish", "BehaviourGradesCategories"); - return; - } - JsonArray categories = data.get("Categories").getAsJsonArray(); - enableBehaviourGrades = categories.size() > 0; - profile.putStudentData("enableBehaviourGrades", enableBehaviourGrades); - if (!enableBehaviourGrades) { - r("finish", "BehaviourGradesCategories"); - return; - } - gradeCategoryListChanged = true; - //d("Got Grades/Categories: "+categories.toString()); - for (JsonElement categoryEl : categories) { - JsonObject category = categoryEl.getAsJsonObject(); - JsonElement name = category.get("Name"); - JsonElement valueFrom = category.get("ValueFrom"); - JsonElement valueTo = category.get("ValueTo"); - int colorInt = Color.BLUE; - int categoryId = category.get("Id").getAsInt(); - float valueFromFloat = valueFrom.getAsFloat(); - float valueToFloat = valueTo.getAsFloat(); - gradeCategoryList.add( - new GradeCategory( - profileId, - categoryId, - -1, - colorInt, - name instanceof JsonNull || name == null ? "" : name.getAsString() - ).setValueRange(valueFromFloat, valueToFloat) - ); - } - r("finish", "BehaviourGradesCategories"); - }); - } - - private void getGrades() { - d(TAG, "Grades settings: "+enableStandardGrades+", "+enablePointGrades+", "+enableDescriptiveGrades); - if (!enableStandardGrades && false) { - // cancel only if grades have been disabled before - // TODO do not cancel. this does not show any grades until a full sync happens. wtf - // in KOTLIN api, maybe this will be forced when user synchronises this feature exclusively - r("finish", "Grades"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_grades); - apiRequest("Grades", data -> { - if (data == null) { - r("finish", "Grades"); - return; - } - JsonArray grades = data.get("Grades").getAsJsonArray(); - enableStandardGrades = grades.size() > 0; - profile.putStudentData("enableStandardGrades", enableStandardGrades); - if (!enableStandardGrades) { - r("finish", "Grades"); - return; - } - //d("Got Grades: "+grades.toString()); - for (JsonElement gradeEl : grades) { - JsonObject grade = gradeEl.getAsJsonObject(); - long id = grade.get("Id").getAsLong(); - long teacherId = grade.get("AddedBy").getAsJsonObject().get("Id").getAsLong(); - int semester = grade.get("Semester").getAsInt(); - long categoryId = grade.get("Category").getAsJsonObject().get("Id").getAsLong(); - long subjectId = grade.get("Subject").getAsJsonObject().get("Id").getAsLong(); - String name = grade.get("Grade").getAsString(); - float value = getGradeValue(name); - - String str_date = grade.get("AddDate").getAsString(); - long addedDate = Date.fromIso(str_date); - - float weight = 0.0f; - String category = ""; - int color = -1; - GradeCategory gradeCategory = GradeCategory.search(gradeCategoryList, categoryId); - if (gradeCategory != null) { - weight = gradeCategory.weight; - category = gradeCategory.text; - color = gradeCategory.color; - } - - if (name.equals("-") || name.equals("+") || name.equalsIgnoreCase("np") || name.equalsIgnoreCase("bz")) { - // fix for + and - grades that lower the average - weight = 0; - } - - Grade gradeObject = new Grade( - profileId, - id, - category, - color, - "", - name, - value, - weight, - semester, - teacherId, - subjectId - ); - - if (grade.get("IsConstituent").getAsBoolean()) { - // normal grade - gradeObject.type = TYPE_NORMAL; - } - if (grade.get("IsSemester").getAsBoolean()) { - // semester final - gradeObject.type = (gradeObject.semester == 1 ? TYPE_SEMESTER1_FINAL : TYPE_SEMESTER2_FINAL); - } - else if (grade.get("IsSemesterProposition").getAsBoolean()) { - // semester proposed - gradeObject.type = (gradeObject.semester == 1 ? TYPE_SEMESTER1_PROPOSED : TYPE_SEMESTER2_PROPOSED); - } - else if (grade.get("IsFinal").getAsBoolean()) { - // year final - gradeObject.type = TYPE_YEAR_FINAL; - } - else if (grade.get("IsFinalProposition").getAsBoolean()) { - // year final - gradeObject.type = TYPE_YEAR_PROPOSED; - } - - JsonElement historyEl = grade.get("Improvement"); - if (historyEl != null) { - JsonObject history = historyEl.getAsJsonObject(); - long historicalId = history.get("Id").getAsLong(); - for (Grade historicalGrade: gradeList) { - if (historicalGrade.id != historicalId) - continue; - // historicalGrade - historicalGrade.parentId = gradeObject.id; - if (historicalGrade.name.equals("nb")) { - historicalGrade.weight = 0; - } - break; - } - gradeObject.isImprovement = true; - } - - /*if (RegisterGradeCategory.getById(app.register, registerGrade.categoryId, -1) == null) { - getGradesCategories = true; - }*/ - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "Grades"); - }); - } - - private void getPointGrades() { - d(TAG, "Grades settings: "+enableStandardGrades+", "+enablePointGrades+", "+enableDescriptiveGrades); - if (!enablePointGrades) { - // cancel only if grades have been disabled before - r("finish", "PointGrades"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_point_grades); - apiRequest("PointGrades", data -> { - if (data == null) { - r("finish", "PointGrades"); - return; - } - JsonArray grades = data.get("Grades").getAsJsonArray(); - enablePointGrades = grades.size() > 0; - profile.putStudentData("enablePointGrades", enablePointGrades); - if (!enablePointGrades) { - r("finish", "PointGrades"); - return; - } - //d("Got Grades: "+grades.toString()); - for (JsonElement gradeEl : grades) { - JsonObject grade = gradeEl.getAsJsonObject(); - long id = grade.get("Id").getAsLong(); - long teacherId = grade.get("AddedBy").getAsJsonObject().get("Id").getAsLong(); - int semester = grade.get("Semester").getAsInt(); - long categoryId = grade.get("Category").getAsJsonObject().get("Id").getAsLong(); - long subjectId = grade.get("Subject").getAsJsonObject().get("Id").getAsLong(); - String name = grade.get("Grade").getAsString(); - float originalValue = grade.get("GradeValue").getAsFloat(); - - String str_date = grade.get("AddDate").getAsString(); - long addedDate = Date.fromIso(str_date); - - float weight = 0.0f; - String category = ""; - int color = -1; - float value = 0.0f; - float maxPoints = 0.0f; - GradeCategory gradeCategory = GradeCategory.search(gradeCategoryList, categoryId); - if (gradeCategory != null) { - weight = gradeCategory.weight; - category = gradeCategory.text; - color = gradeCategory.color; - maxPoints = gradeCategory.valueTo; - value = originalValue; - } - - Grade gradeObject = new Grade( - profileId, - id, - category, - color, - "", - name, - value, - weight, - semester, - teacherId, - subjectId - ); - gradeObject.type = Grade.TYPE_POINT; - gradeObject.valueMax = maxPoints; - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "PointGrades"); - }); - } - - private void getDescriptiveGrades() { - d(TAG, "Grades settings: "+enableStandardGrades+", "+enablePointGrades+", "+enableDescriptiveGrades); - if (!enableDescriptiveGrades && !enableTextGrades) { - // cancel only if grades have been disabled before - r("finish", "DescriptiveGrades"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_descriptive_grades); - apiRequest("BaseTextGrades", data -> { - if (data == null) { - r("finish", "DescriptiveGrades"); - return; - } - JsonArray grades = data.get("Grades").getAsJsonArray(); - int descriptiveGradesCount = 0; - int textGradesCount = 0; - //d("Got Grades: "+grades.toString()); - for (JsonElement gradeEl : grades) { - JsonObject grade = gradeEl.getAsJsonObject(); - long id = grade.get("Id").getAsLong(); - long teacherId = grade.get("AddedBy").getAsJsonObject().get("Id").getAsLong(); - int semester = grade.get("Semester").getAsInt(); - long subjectId = grade.get("Subject").getAsJsonObject().get("Id").getAsLong(); - String description = grade.get("Grade").getAsString(); - - long categoryId = -1; - JsonElement categoryEl = grade.get("Category"); - JsonElement skillEl = grade.get("Skill"); - if (categoryEl != null) { - categoryId = categoryEl.getAsJsonObject().get("Id").getAsLong(); - textGradesCount++; - } - if (skillEl != null) { - categoryId = skillEl.getAsJsonObject().get("Id").getAsLong(); - descriptiveGradesCount++; - } - - String str_date = grade.get("AddDate").getAsString(); - long addedDate = Date.fromIso(str_date); - - String category = ""; - int color = -1; - GradeCategory gradeCategory = GradeCategory.search(gradeCategoryList, categoryId); - if (gradeCategory != null) { - category = gradeCategory.text; - color = gradeCategory.color; - } - - Grade gradeObject = new Grade( - profileId, - id, - category, - color, - description, - " ", - 0.0f, - 0, - semester, - teacherId, - subjectId - ); - gradeObject.type = Grade.TYPE_DESCRIPTIVE; - if (categoryEl != null) { - gradeObject.type = Grade.TYPE_TEXT; - } - if (skillEl != null) { - gradeObject.type = Grade.TYPE_DESCRIPTIVE; - } - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - enableDescriptiveGrades = descriptiveGradesCount > 0; - enableTextGrades = textGradesCount > 0; - profile.putStudentData("enableDescriptiveGrades", enableDescriptiveGrades); - profile.putStudentData("enableTextGrades", enableTextGrades); - r("finish", "DescriptiveGrades"); - }); - } - - private void getBehaviourGrades() { - d(TAG, "Grades settings: "+enableStandardGrades+", "+enablePointGrades+", "+enableDescriptiveGrades); - if (!enableBehaviourGrades) { - // cancel only if grades have been disabled before - r("finish", "BehaviourGrades"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_behaviour_grades); - apiRequest("BehaviourGrades/Points", data -> { - if (data == null) { - r("finish", "BehaviourGrades"); - return; - } - JsonArray grades = data.get("Grades").getAsJsonArray(); - enableBehaviourGrades = grades.size() > 0; - profile.putStudentData("enableBehaviourGrades", enableBehaviourGrades); - if (!enableBehaviourGrades) { - r("finish", "BehaviourGrades"); - return; - } - //d("Got Grades: "+grades.toString()); - DecimalFormat nameFormat = new DecimalFormat("#.##"); - - Grade gradeStartSemester1 = new Grade( - profileId, - -1, - app.getString(R.string.grade_start_points), - 0xffbdbdbd, - app.getString(R.string.grade_start_points_format, 1), - nameFormat.format(startPointsSemester1), - startPointsSemester1, - -1, - 1, - -1, - 1 - ); - gradeStartSemester1.type = Grade.TYPE_BEHAVIOUR; - Grade gradeStartSemester2 = new Grade( - profileId, - -2, - app.getString(R.string.grade_start_points), - 0xffbdbdbd, - app.getString(R.string.grade_start_points_format, 2), - nameFormat.format(startPointsSemester2), - startPointsSemester2, - -1, - 2, - -1, - 1 - ); - gradeStartSemester2.type = Grade.TYPE_BEHAVIOUR; - - gradeList.add(gradeStartSemester1); - gradeList.add(gradeStartSemester2); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, -1, true, true, profile.getSemesterStart(1).getInMillis())); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, -2, true, true, profile.getSemesterStart(2).getInMillis())); - - for (JsonElement gradeEl : grades) { - JsonObject grade = gradeEl.getAsJsonObject(); - long id = grade.get("Id").getAsLong(); - long teacherId = grade.get("AddedBy").getAsJsonObject().get("Id").getAsLong(); - int semester = grade.get("Semester").getAsInt(); - long categoryId = grade.get("Category").getAsJsonObject().get("Id").getAsLong(); - long subjectId = 1; - - float value = 0.0f; - String name = "?"; - JsonElement nameValue; - if ((nameValue = grade.get("Value")) != null) { - value = nameValue.getAsFloat(); - name = value < 0 ? nameFormat.format(value) : "+"+nameFormat.format(value); - } - else if ((nameValue = grade.get("ShortName")) != null) { - name = nameValue.getAsString(); - } - - String str_date = grade.get("AddDate").getAsString(); - long addedDate = Date.fromIso(str_date); - - int color = value > 0 ? 16 : value < 0 ? 26 : 12; - color = colors.get(color); - - String category = ""; - float maxPoints = 0.0f; - GradeCategory gradeCategory = GradeCategory.search(gradeCategoryList, categoryId); - if (gradeCategory != null) { - category = gradeCategory.text; - maxPoints = gradeCategory.valueTo; - } - - Grade gradeObject = new Grade( - profileId, - id, - category, - color, - "", - name, - value, - -1, - semester, - teacherId, - subjectId - ); - gradeObject.type = Grade.TYPE_BEHAVIOUR; - gradeObject.valueMax = maxPoints; - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "BehaviourGrades"); - }); - } - - //private boolean eventTypeListChanged = false; - private void getEvents() { - //eventTypeList = app.db.eventTypeDao().getAllNow(profileId); - // eventTypeListChanged = false; - callback.onActionStarted(R.string.sync_action_syncing_events); - apiRequest("HomeWorks", data -> { - if (data == null) { - r("finish", "Events"); - return; - } - JsonArray events = data.get("HomeWorks").getAsJsonArray(); - //d("Got Grades: "+events.toString()); - boolean getCustomTypes = false; - try { - for (JsonElement eventEl : events) { - JsonObject event = eventEl.getAsJsonObject(); - - JsonElement el; - JsonObject obj; - - long id = event.get("Id").getAsLong(); - long teacherId = -1; - long subjectId = -1; - int type = -1; - - if ((el = event.get("CreatedBy")) != null - && (obj = el.getAsJsonObject()) != null - && (el = obj.get("Id")) != null) { - teacherId = el.getAsLong(); - } - if ((el = event.get("Subject")) != null - && (obj = el.getAsJsonObject()) != null - && (el = obj.get("Id")) != null) { - subjectId = el.getAsLong(); - } - String topic = event.get("Content").getAsString(); - - if ((el = event.get("Category")) != null - && (obj = el.getAsJsonObject()) != null - && (el = obj.get("Id")) != null) { - type = el.getAsInt(); - } - /*EventType typeObject = app.db.eventTypeDao().getByIdNow(profileId, type); - if (typeObject == null) { - getCustomTypes = true; - }*/ - - JsonElement myClass = event.get("Class"); - long teamId = myClass == null ? -1 : myClass.getAsJsonObject().get("Id").getAsLong(); - - String str_date = event.get("AddDate").getAsString(); - long addedDate = Date.fromIso(str_date); - - str_date = event.get("Date").getAsString(); - Date eventDate = Date.fromY_m_d(str_date); - - - Time startTime = null; - JsonElement lessonNo; - JsonElement timeFrom; - if (!((lessonNo = event.get("LessonNo")) instanceof JsonNull)) { - Pair timePair = lessonRanges.get(lessonNo.getAsInt()); - if(timePair != null) - startTime = timePair.first; - } - if (startTime == null && !((timeFrom = event.get("TimeFrom")) instanceof JsonNull)) { - startTime = Time.fromH_m(timeFrom.getAsString()); - } - - Event eventObject = new Event( - profileId, - id, - eventDate, - startTime, - topic, - -1, - type, - false, - teacherId, - subjectId, - teamId - ); - - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "Events"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2541, CODE_OTHER, e, data)); - } - }); - } - - private void getCustomTypes() { - if (!fullSync) { - r("finish", "CustomTypes"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_event_categories); - apiRequest("HomeWorks/Categories", data -> { - if (data == null) { - r("finish", "CustomTypes"); - return; - } - JsonArray jCategories = data.get("Categories").getAsJsonArray(); - //d("Got Classrooms: "+jClassrooms.toString()); - try { - for (JsonElement categoryEl : jCategories) { - JsonObject category = categoryEl.getAsJsonObject(); - eventTypeList.add(new EventType(profileId, category.get("Id").getAsInt(), category.get("Name").getAsString(), colors.get(category.get("Color").getAsJsonObject().get("Id").getAsInt()))); - } - r("finish", "CustomTypes"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2573, CODE_OTHER, e, data)); - } - }); - } - - private void getHomeworks() { - if (!premium) { - r("finish", "Homeworks"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_homework); - apiRequest("HomeWorkAssignments", data -> { - if (data == null) { - r("finish", "Homeworks"); - return; - } - JsonArray homeworks = data.get("HomeWorkAssignments").getAsJsonArray(); - //d("Got Grades: "+events.toString()); - try { - for (JsonElement homeworkEl : homeworks) { - JsonObject homework = homeworkEl.getAsJsonObject(); - - JsonElement el; - JsonObject obj; - - long id = homework.get("Id").getAsLong(); - long teacherId = -1; - long subjectId = -1; - - if ((el = homework.get("Teacher")) != null - && (obj = el.getAsJsonObject()) != null - && (el = obj.get("Id")) != null) { - teacherId = el.getAsLong(); - } - - String topic = ""; - try { - topic = homework.get("Topic").getAsString() + "\n"; - topic += homework.get("Text").getAsString(); - } - catch (Exception e) { - e.printStackTrace(); - } - - String str_date = homework.get("Date").getAsString(); - Date addedDate = Date.fromY_m_d(str_date); - - str_date = homework.get("DueDate").getAsString(); - Date eventDate = Date.fromY_m_d(str_date); - - - Time startTime = null; - - Event eventObject = new Event( - profileId, - id, - eventDate, - startTime, - topic, - -1, - -1, - false, - teacherId, - subjectId, - -1 - ); - - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), addedDate.getInMillis())); - } - r("finish", "Homeworks"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2648, CODE_OTHER, e, data)); - } - }); - } - - private void getLuckyNumbers() { - if (!profile.getLuckyNumberEnabled() || (profile.getLuckyNumberDate() != null && profile.getLuckyNumberDate().getValue() == Date.getToday().getValue())) { - r("finish", "LuckyNumbers"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_lucky_number); - apiRequest("LuckyNumbers", data -> { - if (data == null) { - profile.setLuckyNumberEnabled(false); - } - else { - profile.setLuckyNumber(-1); - profile.setLuckyNumberDate(Date.getToday()); - try { - JsonElement luckyNumberEl = data.get("LuckyNumber"); - if (luckyNumberEl != null) { - JsonObject luckyNumber = luckyNumberEl.getAsJsonObject(); - profile.setLuckyNumber(luckyNumber.get("LuckyNumber").getAsInt()); - profile.setLuckyNumberDate(Date.fromY_m_d(luckyNumber.get("LuckyNumberDay").getAsString())); - } - } catch (Exception e) { - finishWithError(new AppError(TAG, 2678, CODE_OTHER, e, data)); - } - finally { - app.db.luckyNumberDao().add(new LuckyNumber(profileId, profile.getLuckyNumberDate(), profile.getLuckyNumber())); - } - } - r("finish", "LuckyNumbers"); - }); - } - - private void getNotices() { - callback.onActionStarted(R.string.sync_action_syncing_notices); - apiRequest("Notes", data -> { - if (data == null) { - r("finish", "Notices"); - return; - } - try { - JsonArray jNotices = data.get("Notes").getAsJsonArray(); - - for (JsonElement noticeEl : jNotices) { - JsonObject notice = noticeEl.getAsJsonObject(); - - int type = notice.get("Positive").getAsInt(); - switch (type) { - case 0: - type = TYPE_NEGATIVE; - break; - case 1: - type = TYPE_POSITIVE; - break; - case 2: - type = TYPE_NEUTRAL; - break; - } - - long id = notice.get("Id").getAsLong(); - - Date addedDate = Date.fromY_m_d(notice.get("Date").getAsString()); - - int semester = profile.dateToSemester(addedDate); - - JsonElement el; - JsonObject obj; - long teacherId = -1; - if ((el = notice.get("Teacher")) != null - && (obj = el.getAsJsonObject()) != null - && (el = obj.get("Id")) != null) { - teacherId = el.getAsLong(); - } - - Notice noticeObject = new Notice( - profileId, - id, - notice.get("Text").getAsString(), - semester, - type, - teacherId - ); - - noticeList.add(noticeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate.getInMillis())); - } - r("finish", "Notices"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2750, CODE_OTHER, e, data)); - } - }); - } - - private SparseArray> attendanceTypes = new SparseArray<>(); - private void getAttendancesTypes() { - callback.onActionStarted(R.string.sync_action_syncing_attendance_types); - apiRequest("Attendances/Types", data -> { - if (data == null) { - r("finish", "AttendancesTypes"); - return; - } - try { - JsonArray jTypes = data.get("Types").getAsJsonArray(); - for (JsonElement typeEl : jTypes) { - JsonObject type = typeEl.getAsJsonObject(); - int id = type.get("Id").getAsInt(); - attendanceTypes.put(id, - new Pair<>( - type.get("Standard").getAsBoolean() ? id : type.getAsJsonObject("StandardType").get("Id").getAsInt(), - type.get("Name").getAsString() - ) - ); - } - r("finish", "AttendancesTypes"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2782, CODE_OTHER, e, data)); - } - }); - } - - private void getAttendances() { - callback.onActionStarted(R.string.sync_action_syncing_attendances); - apiRequest("Attendances"+(fullSync ? "" : "?dateFrom="+ Date.getToday().stepForward(0, -1, 0).getStringY_m_d()), data -> { - if (data == null) { - r("finish", "Attendances"); - return; - } - - try { - JsonArray jAttendances = data.get("Attendances").getAsJsonArray(); - - for (JsonElement attendanceEl : jAttendances) { - JsonObject attendance = attendanceEl.getAsJsonObject(); - - int type = attendance.getAsJsonObject("Type").get("Id").getAsInt(); - Pair attendanceType; - if ((attendanceType = attendanceTypes.get(type)) != null) { - type = attendanceType.first; - } - switch (type) { - case 1: - type = TYPE_ABSENT; - break; - case 2: - type = TYPE_BELATED; - break; - case 3: - type = TYPE_ABSENT_EXCUSED; - break; - case 4: - type = TYPE_RELEASED; - break; - default: - case 100: - type = TYPE_PRESENT; - break; - } - - String idStr = attendance.get("Id").getAsString(); - int id = strToInt(idStr.replaceAll("[^\\d.]", "")); - - long addedDate = Date.fromIso(attendance.get("AddDate").getAsString()); - - Time startTime = Time.getNow(); - Pair timePair = lessonRanges.get(attendance.get("LessonNo").getAsInt()); - if (timePair != null) - startTime = timePair.first; - Date lessonDate = Date.fromY_m_d(attendance.get("Date").getAsString()); - int lessonWeekDay = lessonDate.getWeekDay(); - long subjectId = -1; - String topic = ""; - if (attendanceType != null) { - topic = attendanceType.second; - } - - for (Lesson lesson: lessonList) { - if (lesson.weekDay == lessonWeekDay && lesson.startTime.getValue() == startTime.getValue()) { - subjectId = lesson.subjectId; - } - } - - Attendance attendanceObject = new Attendance( - profileId, - id, - attendance.getAsJsonObject("AddedBy").get("Id").getAsLong(), - subjectId, - attendance.get("Semester").getAsInt(), - topic, - lessonDate, - startTime, - type); - - attendanceList.add(attendanceObject); - if (attendanceObject.type != TYPE_PRESENT) { - metadataList.add(new Metadata(profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - } - r("finish", "Attendances"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2872, CODE_OTHER, e, data)); - } - }); - } - - private void getAnnouncements() { - callback.onActionStarted(R.string.sync_action_syncing_announcements); - apiRequest("SchoolNotices", data -> { - if (data == null) { - r("finish", "Announcements"); - return; - } - try { - JsonArray jAnnouncements = data.get("SchoolNotices").getAsJsonArray(); - - for (JsonElement announcementEl : jAnnouncements) { - JsonObject announcement = announcementEl.getAsJsonObject(); - - String idStr = announcement.get("Id").getAsString(); - long id = crc16(idStr.getBytes()); - - long addedDate = Date.fromIso(announcement.get("CreationDate").getAsString()); - - boolean read = announcement.get("WasRead").getAsBoolean(); - - String subject = ""; - String text = ""; - Date startDate = null; - Date endDate = null; - try { - subject = announcement.get("Subject").getAsString(); - text = announcement.get("Content").getAsString(); - startDate = Date.fromY_m_d(announcement.get("StartDate").getAsString()); - endDate = Date.fromY_m_d(announcement.get("EndDate").getAsString()); - } - catch (Exception e) { - e.printStackTrace(); - } - - JsonElement el; - JsonObject obj; - long teacherId = -1; - if ((el = announcement.get("AddedBy")) != null - && (obj = el.getAsJsonObject()) != null - && (el = obj.get("Id")) != null) { - teacherId = el.getAsLong(); - } - - Announcement announcementObject = new Announcement( - profileId, - id, - subject, - text, - startDate, - endDate, - teacherId - ); - - announcementList.add(announcementObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_ANNOUNCEMENT, announcementObject.id, read, read, addedDate)); - } - r("finish", "Announcements"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2944, CODE_OTHER, e, data)); - } - }); - } - - private void getPtMeetings() { - if (!fullSync) { - r("finish", "PtMeetings"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_pt_meetings); - apiRequest("ParentTeacherConferences", data -> { - if (data == null) { - r("finish", "PtMeetings"); - return; - } - try { - JsonArray jMeetings = data.get("ParentTeacherConferences").getAsJsonArray(); - for (JsonElement meetingEl: jMeetings) { - JsonObject meeting = meetingEl.getAsJsonObject(); - - long id = meeting.get("Id").getAsLong(); - Event eventObject = new Event( - profileId, - id, - Date.fromY_m_d(meeting.get("Date").getAsString()), - Time.fromH_m(meeting.get("Time").getAsString()), - meeting.get("Topic").getAsString(), - -1, - TYPE_PT_MEETING, - false, - meeting.getAsJsonObject("Teacher").get("Id").getAsLong(), - -1, - teamClassId - ); - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - r("finish", "PtMeetings"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 2996, CODE_OTHER, e, data)); - } - }); - } - - private SparseArray teacherFreeDaysTypes = new SparseArray<>(); - private void getTeacherFreeDaysTypes() { - callback.onActionStarted(R.string.sync_action_syncing_teacher_free_days_types); - apiRequest("TeacherFreeDays/Types", data -> { - if (data == null) { - r("finish", "TeacherFreeDays"); - return; - } - try { - JsonArray jTypes = data.get("Types").getAsJsonArray(); - for (JsonElement typeEl : jTypes) { - JsonObject type = typeEl.getAsJsonObject(); - int id = type.get("Id").getAsInt(); - teacherFreeDaysTypes.put(id, type.get("Name").getAsString()); - } - r("finish", "TeacherFreeDaysTypes"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 3019, CODE_OTHER, e, data)); - } - }); - } - - private void getTeacherFreeDays() { - callback.onActionStarted(R.string.sync_action_syncing_teacher_free_days); - apiRequest("TeacherFreeDays", data -> { - if (data == null) { - r("finish", "TeacherFreeDays"); - return; - } - try { - JsonArray jFreeDays = data.get("TeacherFreeDays").getAsJsonArray(); - for (JsonElement freeDayEl: jFreeDays) { - JsonObject freeDay = freeDayEl.getAsJsonObject(); - - long id = freeDay.get("Id").getAsLong(); - - Date dateFrom = Date.fromY_m_d(freeDay.get("DateFrom").getAsString()); - Date dateTo = Date.fromY_m_d(freeDay.get("DateTo").getAsString()); - - int type = freeDay.getAsJsonObject("Type").get("Id").getAsInt(); - String topic = teacherFreeDaysTypes.get(type)+"\n"+(dateFrom.getValue() != dateTo.getValue() ? dateFrom.getFormattedString()+" - "+dateTo.getFormattedString() : ""); - Event eventObject = new Event( - profileId, - id, - dateFrom, - null, - topic, - -1, - TYPE_TEACHER_ABSENCE, - false, - freeDay.getAsJsonObject("Teacher").get("Id").getAsLong(), - -1, - -1 - ); - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - r("finish", "TeacherFreeDays"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 3069, CODE_OTHER, e, data)); - } - }); - } - - private void getMessagesLogin() { - if (synergiaPassword == null) { - // skip messages - r("finish", "MessagesOutbox"); - return; - } - loginSynergia(() -> { - r("finish", "MessagesLogin"); - }); - } - - private void getMessagesInbox() { - String body = - "\n" + - "
\n" + - " \n" + - " 0\n" + - " \n" + - ""; - synergiaRequest("Inbox/action/GetList", body, data -> { - try { - long startTime = System.currentTimeMillis(); - - for (Element e: data.select("response GetList data ArrayItem")) { - - long id = Long.parseLong(e.select("messageId").text()); - - String subject = e.select("topic").text(); - - String senderFirstName = e.select("senderFirstName").text(); - String senderLastName = e.select("senderLastName").text(); - - long senderId = -1; - - for (Teacher teacher: teacherList) { - if (teacher.name.equalsIgnoreCase(senderFirstName) && teacher.surname.equalsIgnoreCase(senderLastName)) { - senderId = teacher.id; - break; - } - } - if (senderId == -1) { - Teacher teacher = new Teacher(profileId, -1 * Utils.crc16((senderFirstName+" "+senderLastName).getBytes()), senderFirstName, senderLastName); - senderId = teacher.id; - teacherList.add(teacher); - teacherListChanged = true; - } - - long readDate = 0; - long sentDate; - - String readDateStr = e.select("readDate").text(); - String sentDateStr = e.select("sendDate").text(); - - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); - if (!readDateStr.isEmpty()) { - readDate = formatter.parse(readDateStr).getTime(); - } - sentDate = formatter.parse(sentDateStr).getTime(); - - Message message = new Message( - profileId, - id, - subject, - null, - TYPE_RECEIVED, - senderId, - -1 - ); - - MessageRecipient messageRecipient = new MessageRecipient( - profileId, - -1 /* me */, - -1, - readDate, - /*messageId*/ id - ); - - if (!e.select("isAnyFileAttached").text().equals("0")) - message.setHasAttachments(); - - messageList.add(message); - messageRecipientList.add(messageRecipient); - messageMetadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, readDate > 0, readDate > 0 || profile.getEmpty(), sentDate)); - } - - } catch (Exception e3) { - finishWithError(new AppError(TAG, 3164, CODE_OTHER, e3, data.outerHtml())); - return; - } - - r("finish", "MessagesInbox"); - }); - } - - private void getMessagesOutbox() { - if (!fullSync && onlyFeature != FEATURE_MESSAGES_OUTBOX && !profile.getEmpty()) { - // a quick sync and the profile is already synced at least once - r("finish", "MessagesOutbox"); - return; - } - String body = - "\n" + - "
\n" + - " \n" + - " 0\n" + - " \n" + - ""; - synergiaRequest("Outbox/action/GetList", body, data -> { - try { - long startTime = System.currentTimeMillis(); - - for (Element e: data.select("response GetList data ArrayItem")) { - - long id = Long.parseLong(e.select("messageId").text()); - - String subject = e.select("topic").text(); - - String receiverFirstName = e.select("receiverFirstName").text(); - String receiverLastName = e.select("receiverLastName").text(); - - long receiverId = -1; - - for (Teacher teacher: teacherList) { - if (teacher.name.equalsIgnoreCase(receiverFirstName) && teacher.surname.equalsIgnoreCase(receiverLastName)) { - receiverId = teacher.id; - break; - } - } - if (receiverId == -1) { - Teacher teacher = new Teacher(profileId, -1 * Utils.crc16((receiverFirstName+" "+receiverLastName).getBytes()), receiverFirstName, receiverLastName); - receiverId = teacher.id; - teacherList.add(teacher); - teacherListChanged = true; - } - - long sentDate; - - String sentDateStr = e.select("sendDate").text(); - - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); - sentDate = formatter.parse(sentDateStr).getTime(); - - Message message = new Message( - profileId, - id, - subject, - null, - TYPE_SENT, - -1, - -1 - ); - - MessageRecipient messageRecipient = new MessageRecipient( - profileId, - receiverId, - -1, - -1, - /*messageId*/ id - ); - - if (!e.select("isAnyFileAttached").text().equals("0")) - message.setHasAttachments(); - - messageList.add(message); - messageRecipientIgnoreList.add(messageRecipient); - metadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)); - } - - } catch (Exception e3) { - finishWithError(new AppError(TAG, 3270, CODE_OTHER, e3, data.outerHtml())); - return; - } - - r("finish", "MessagesOutbox"); - }); - } - - @Override - public Map getConfigurableEndpoints(Profile profile) { - Map configurableEndpoints = new LinkedHashMap<>(); - configurableEndpoints.put("Classrooms", new Endpoint("Classrooms",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Timetables", new Endpoint("Timetables",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Substitutions", new Endpoint("Substitutions",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Grades", new Endpoint("Grades",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("PointGrades", new Endpoint("PointGrades",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Events", new Endpoint("Events",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Homeworks", new Endpoint("Homeworks",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("LuckyNumbers", new Endpoint("LuckyNumbers",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Notices", new Endpoint("Notices",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Attendances", new Endpoint("Attendances",true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("Announcements", new Endpoint("Announcements",true, true, profile.getChangedEndpoints())); - configurableEndpoints.put("PtMeetings", new Endpoint("PtMeetings",true, true, profile.getChangedEndpoints())); - configurableEndpoints.put("TeacherFreeDays", new Endpoint("TeacherFreeDays",false, false, profile.getChangedEndpoints())); - //configurableEndpoints.put("SchoolFreeDays", new Endpoint("SchoolFreeDays",true, true, profile.changedEndpoints)); - //configurableEndpoints.put("ClassFreeDays", new Endpoint("ClassFreeDays",true, true, profile.changedEndpoints)); - configurableEndpoints.put("MessagesInbox", new Endpoint("MessagesInbox", true, false, profile.getChangedEndpoints())); - configurableEndpoints.put("MessagesOutbox", new Endpoint("MessagesOutbox", true, true, profile.getChangedEndpoints())); - return configurableEndpoints; - } - - @Override - public boolean isEndpointEnabled(Profile profile, boolean defaultActive, String name) { - return defaultActive ^ contains(profile.getChangedEndpoints(), name); - } - - @Override - public void syncMessages(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile) - { - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - Librus.this.profile.setEmpty(true); - targetEndpoints = new ArrayList<>(); - targetEndpoints.add("Users"); - targetEndpoints.add("MessagesLogin"); - targetEndpoints.add("MessagesInbox"); - targetEndpoints.add("MessagesOutbox"); - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - - /* __ __ - | \/ | - | \ / | ___ ___ ___ __ _ __ _ ___ ___ - | |\/| |/ _ \/ __/ __|/ _` |/ _` |/ _ \/ __| - | | | | __/\__ \__ \ (_| | (_| | __/\__ \ - |_| |_|\___||___/___/\__,_|\__, |\___||___/ - __/ | - |__*/ - @Override - public void getMessage(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, @NonNull MessageGetCallback messageCallback) - { - if (message.body != null) { - boolean readByAll = true; - // load this message's recipient(s) data - message.recipients = app.db.messageRecipientDao().getAllByMessageId(profile.getId(), message.id); - for (MessageRecipientFull recipient: message.recipients) { - if (recipient.id == -1) - recipient.fullName = profile.getStudentNameLong(); - if (message.type == TYPE_SENT && recipient.readDate < 1) - readByAll = false; - } - if (!message.seen) { - app.db.metadataDao().setSeen(profile.getId(), message, true); - } - if (readByAll) { - // if a sent msg is not read by everyone, download it again to check the read status - new Handler(activityContext.getMainLooper()).post(() -> { - messageCallback.onSuccess(message); - }); - return; - } - } - - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - loginSynergia(() -> { - String requestBody = - "\n" + - "
\n" + - " \n" + - " "+message.id+"\n" + - " 0\n" + - " \n" + - ""; - synergiaRequest("GetMessage", requestBody, data -> { - - List messageRecipientList = new ArrayList<>(); - - try { - Element e = data.select("response GetMessage data").first(); - - String body = e.select("Message").text(); - body = new String(Base64.decode(body, Base64.DEFAULT)); - body = body.replaceAll("\n", "
"); - body = body.replaceAll("", ""); - - message.clearAttachments(); - Elements attachments = e.select("attachments ArrayItem"); - if (attachments != null) { - for (Element attachment: attachments) { - message.addAttachment(Long.parseLong(attachment.select("id").text()), attachment.select("filename").text(), -1); - } - } - - message.body = body; - - if (message.type == TYPE_RECEIVED) { - app.db.teacherDao().updateLoginId(profileId, message.senderId, e.select("senderId").text()); - - MessageRecipientFull recipient = new MessageRecipientFull(profileId, -1, message.id); - - long readDate = 0; - String readDateStr = e.select("readDate").text(); - if (!readDateStr.isEmpty()) { - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); - readDate = formatter.parse(readDateStr).getTime(); - } - recipient.readDate = readDate; - - recipient.fullName = profile.getStudentNameLong(); - messageRecipientList.add(recipient); - - } - else if (message.type == TYPE_SENT) { - List teacherList = app.db.teacherDao().getAllNow(profileId); - for (Element receiver: e.select("receivers ArrayItem")) { - String receiverFirstName = e.select("firstName").text(); - String receiverLastName = e.select("lastName").text(); - - long receiverId = -1; - - for (Teacher teacher: teacherList) { - if (teacher.name.equalsIgnoreCase(receiverFirstName) && teacher.surname.equalsIgnoreCase(receiverLastName)) { - receiverId = teacher.id; - break; - } - } - - app.db.teacherDao().updateLoginId(profileId, receiverId, receiver.select("receiverId").text()); - - MessageRecipientFull recipient = new MessageRecipientFull(profileId, receiverId, message.id); - - long readDate = 0; - String readDateStr = e.select("readed").text(); - if (!readDateStr.isEmpty()) { - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); - readDate = formatter.parse(readDateStr).getTime(); - } - recipient.readDate = readDate; - - recipient.fullName = receiverFirstName+" "+receiverLastName; - messageRecipientList.add(recipient); - } - } - - } - catch (Exception e) { - finishWithError(new AppError(TAG, 795, CODE_OTHER, e, data.outerHtml())); - return; - } - - if (!message.seen) { - app.db.metadataDao().setSeen(profileId, message, true); - } - app.db.messageDao().add(message); - app.db.messageRecipientDao().addAll((List)(List) messageRecipientList); // not addAllIgnore - - message.recipients = messageRecipientList; - - new Handler(activityContext.getMainLooper()).post(() -> { - messageCallback.onSuccess(message); - }); - }); - }); - } - - @Override - public void getAttachment(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, long attachmentId, @NonNull AttachmentGetCallback attachmentCallback) { - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - loginSynergia(() -> { - String requestBody = - "\n" + - "
\n" + - " \n" + - " "+attachmentId+"\n" + - " "+message.id+"\n" + - " 0\n" + - " \n" + - ""; - synergiaRequest("GetFileDownloadLink", requestBody, data -> { - String downloadLink = data.select("response GetFileDownloadLink downloadLink").text(); - Matcher keyMatcher = Pattern.compile("singleUseKey=([0-9A-f_]+)").matcher(downloadLink); - if (keyMatcher.find()) { - getAttachmentCheckKeyTries = 0; - getAttachmentCheckKey(keyMatcher.group(1), attachmentCallback); - } - else { - finishWithError(new AppError(TAG, 629, CODE_OTHER, "Błąd pobierania tokenu. Skontaktuj się z twórcą aplikacji.", data.outerHtml())); - } - }); - }); - } - private int getAttachmentCheckKeyTries = 0; - private void getAttachmentCheckKey(String attachmentKey, AttachmentGetCallback attachmentCallback) { - Request.builder() - .url(SYNERGIA_SANDBOX_URL+"CSCheckKey") - .userAgent(synergiaUserAgent) - .addParameter("singleUseKey", attachmentKey) - .post() - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 645, AppError.CODE_MAINTENANCE, response)); - return; - } - try { - String status = data.get("status").getAsString(); - if (status.equals("not_downloaded_yet")) { - if (getAttachmentCheckKeyTries++ > 5) { - finishWithError(new AppError(TAG, 658, CODE_OTHER, "Załącznik niedostępny. Przekroczono czas oczekiwania.", response, data)); - return; - } - new Handler(activityContext.getMainLooper()).postDelayed(() -> { - getAttachmentCheckKey(attachmentKey, attachmentCallback); - }, 2000); - } - else if (status.equals("ready")) { - Request.Builder builder = Request.builder() - .url(SYNERGIA_SANDBOX_URL+"CSDownload&singleUseKey="+attachmentKey); - new Handler(activityContext.getMainLooper()).post(() -> { - attachmentCallback.onSuccess(builder); - }); - } - else { - finishWithError(new AppError(TAG, 667, AppError.CODE_ATTACHMENT_NOT_AVAILABLE, response, data)); - } - } - catch (Exception e) { - finishWithError(new AppError(TAG, 671, AppError.CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 677, CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - @Override - public void getRecipientList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull RecipientListGetCallback recipientListGetCallback) { - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - if (System.currentTimeMillis() - profile.getLastReceiversSync() < 24 * 60 * 60 * 1000) { - AsyncTask.execute(() -> { - List teacherList = app.db.teacherDao().getAllNow(profileId); - new Handler(activityContext.getMainLooper()).post(() -> recipientListGetCallback.onSuccess(teacherList)); - }); - return; - } - - loginSynergia(() -> { - String requestBody = - "\n" + - "
\n" + - " \n" + - " 1\n" + - " \n" + - ""; - synergiaRequest("Receivers/action/GetTypes", requestBody, data -> { - - teacherList = app.db.teacherDao().getAllNow(profileId); - - for (Teacher teacher: teacherList) { - teacher.typeDescription = null; // TODO: 2019-06-13 it better - } - - Elements categories = data.select("response GetTypes data list ArrayItem"); - for (Element category: categories) { - String categoryId = category.select("id").text(); - String categoryName = category.select("name").text(); - Elements categoryList = getRecipientCategory(categoryId); - if (categoryList == null) - return; // the error callback is already executed - for (Element item: categoryList) { - if (item.select("list").size() == 1) { - String className = item.select("label").text(); - Elements list = item.select("list ArrayItem"); - for (Element teacher: list) { - updateTeacher(categoryId, Long.parseLong(teacher.select("id").text()), teacher.select("label").text(), categoryName, className); - } - } - else { - updateTeacher(categoryId, Long.parseLong(item.select("id").text()), item.select("label").text(), categoryName, null); - } - } - } - - app.db.teacherDao().addAll(teacherList); - - profile.setLastReceiversSync(System.currentTimeMillis()); - app.db.profileDao().add(profile); - - new Handler(activityContext.getMainLooper()).post(() -> recipientListGetCallback.onSuccess(new ArrayList<>(teacherList))); - }); - }); - } - private Elements getRecipientCategory(String categoryId) { - Response response = null; - try { - String endpoint = "Receivers/action/GetListForType"; - d(TAG, "Requesting "+SYNERGIA_URL+endpoint); - String body = - "\n" + - "
\n" + - " \n" + - " "+categoryId+"\n" + - " \n" + - ""; - response = Request.builder() - .url(SYNERGIA_URL+endpoint) - .userAgent(synergiaUserAgent) - .setTextBody(body, MediaTypeUtils.APPLICATION_XML) - .build().execute(); - if (response.code() != 200) { - finishWithError(new AppError(TAG, 3569, CODE_OTHER, response)); - return null; - } - String data = new TextCallbackHandler().backgroundParser(response); - if (data.contains("error") || data.contains("")) { - finishWithError(new AppError(TAG, 3556, AppError.CODE_MAINTENANCE, response, data)); - return null; - } - Document doc = Jsoup.parse(data, "", Parser.xmlParser()); - return doc.select("response GetListForType data ArrayItem"); - } catch (Exception e) { - finishWithError(new AppError(TAG, 3562, CODE_OTHER, response, e)); - return null; - } - } - private void updateTeacher(String category, long loginId, String nameLastFirst, String typeDescription, String className) { - nameLastFirst = nameLastFirst.replaceAll("\\s+", " "); - int type = TYPE_OTHER; - String position; - switch (category) { - case "tutors": - type = TYPE_EDUCATOR; - break; - case "teachers": - type = TYPE_TEACHER; - break; - case "pedagogue": - type = TYPE_PEDAGOGUE; - break; - case "librarian": - type = TYPE_LIBRARIAN; - break; - case "admin": - type = TYPE_SCHOOL_ADMIN; - break; - case "secretary": - type = TYPE_SECRETARIAT; - break; - case "sadmin": - type = TYPE_SUPER_ADMIN; - break; - case "parentsCouncil": - type = TYPE_PARENTS_COUNCIL; - int index = nameLastFirst.indexOf(" - "); - position = index == -1 ? "" : nameLastFirst.substring(index+3); - nameLastFirst = index == -1 ? nameLastFirst : nameLastFirst.substring(0, index); - typeDescription = bs(className)+bs(": ", position); - break; - case "schoolParentsCouncil": - type = TYPE_SCHOOL_PARENTS_COUNCIL; - index = nameLastFirst.indexOf(" - "); - position = index == -1 ? "" : nameLastFirst.substring(index+3); - nameLastFirst = index == -1 ? nameLastFirst : nameLastFirst.substring(0, index); - typeDescription = bs(position); - break; - case "contactsGroups": - return; - } - Teacher teacher = Teacher.getByFullNameLastFirst(teacherList, nameLastFirst); - if (teacher == null) { - String[] nameParts = nameLastFirst.split(" ", Integer.MAX_VALUE); - teacher = new Teacher(profileId, -1 * Utils.crc16((nameParts.length > 1 ? nameParts[1]+" "+nameParts[0] : nameParts[0]).getBytes()), nameParts.length > 1 ? nameParts[1] : "", nameParts[0]); - teacherList.add(teacher); - } - teacher.loginId = String.valueOf(loginId); - teacher.type = 0; - teacher.setType(type); - if (type == TYPE_OTHER) { - teacher.typeDescription = typeDescription+bs(" ", teacher.typeDescription); - } - else { - teacher.typeDescription = typeDescription; - } - } - - @Override - public MessagesComposeInfo getComposeInfo(@NonNull ProfileFull profile) { - return new MessagesComposeInfo(0, 0, 150, 20000); - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/Mobidziennik.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/Mobidziennik.java deleted file mode 100644 index 71c200b8..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/Mobidziennik.java +++ /dev/null @@ -1,2426 +0,0 @@ -package pl.szczodrzynski.edziennik.api; - -import android.content.Context; -import android.graphics.Color; -import android.os.AsyncTask; -import android.os.Handler; -import android.text.Html; -import android.util.Pair; -import android.util.SparseArray; -import android.util.SparseIntArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.crashlytics.android.Crashlytics; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import im.wangchao.mhttp.Request; -import im.wangchao.mhttp.Response; -import im.wangchao.mhttp.callback.TextCallbackHandler; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.BuildConfig; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; -import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; -import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; -import pl.szczodrzynski.edziennik.datamodels.Attendance; -import pl.szczodrzynski.edziennik.datamodels.Event; -import pl.szczodrzynski.edziennik.datamodels.Grade; -import pl.szczodrzynski.edziennik.datamodels.GradeCategory; -import pl.szczodrzynski.edziennik.datamodels.Lesson; -import pl.szczodrzynski.edziennik.datamodels.LessonChange; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.LuckyNumber; -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.MessageFull; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipient; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipientFull; -import pl.szczodrzynski.edziennik.datamodels.Metadata; -import pl.szczodrzynski.edziennik.datamodels.Notice; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.datamodels.Subject; -import pl.szczodrzynski.edziennik.datamodels.Teacher; -import pl.szczodrzynski.edziennik.datamodels.Team; -import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Endpoint; -import pl.szczodrzynski.edziennik.models.Time; -import pl.szczodrzynski.edziennik.models.Week; - -import static pl.szczodrzynski.edziennik.api.AppError.CODE_INVALID_LOGIN; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_MAINTENANCE; -import static pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_CUSTOM; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_RELEASED; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_DEFAULT; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_EXAM; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_SHORT_QUIZ; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_YEAR_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_MOBIDZIENNIK; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_DELETED; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_RECEIVED; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_SENT; -import static pl.szczodrzynski.edziennik.utils.Utils.bs; -import static pl.szczodrzynski.edziennik.utils.Utils.crc16; -import static pl.szczodrzynski.edziennik.utils.Utils.d; -import static pl.szczodrzynski.edziennik.utils.Utils.monthFromName; -import static pl.szczodrzynski.edziennik.utils.Utils.strToInt; - -public class Mobidziennik implements EdziennikInterface { - public Mobidziennik(App app) { - this.app = app; - } - - private static final String TAG = "api.Mobidziennik"; - - private App app; - private Context activityContext = null; - private SyncCallback callback = null; - private int profileId = -1; - private Profile profile = null; - private LoginStore loginStore = null; - private boolean fullSync = true; - private Date today = Date.getToday(); - private List targetEndpoints = new ArrayList<>(); - - // PROGRESS - private static final int PROGRESS_LOGIN = 10; - private int PROGRESS_COUNT = 1; - private int PROGRESS_STEP = (90/PROGRESS_COUNT); - - private int onlyFeature = FEATURE_ALL; - - private List teamList; - private List teacherList; - private List subjectList; - private List lessonList; - private List lessonChangeList; - private SparseArray gradeAddedDates; - private SparseArray gradeAverages; - private SparseIntArray gradeColors; - private List gradeList; - private List eventList; - private List noticeList; - private List attendanceList; - private List messageList; - private List messageRecipientList; - private List messageRecipientIgnoreList; - private List metadataList; - private List messageMetadataList; - - private static boolean fakeLogin = false && !(BuildConfig.BUILD_TYPE.equals("release")); - private String lastLogin = null; - private long lastLoginTime = 0; - private String lastResponse = null; - private String loginServerName = null; - private String loginUsername = null; - private String loginPassword = null; - private int studentId = -1; - - private long attendancesLastSync = 0; - - private boolean prepare(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - this.activityContext = activityContext; - this.callback = callback; - this.profileId = profileId; - // here we must have a login store: either with a correct ID or -1 - // there may be no profile and that's when onLoginFirst happens - this.profile = profile; - this.loginStore = loginStore; - this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext); - this.today = Date.getToday(); - - this.loginServerName = loginStore.getLoginData("serverName", ""); - this.loginUsername = loginStore.getLoginData("username", ""); - this.loginPassword = loginStore.getLoginData("password", ""); - if (loginServerName.equals("") || loginUsername.equals("") || loginPassword.equals("")) { - finishWithError(new AppError(TAG, 157, AppError.CODE_INVALID_LOGIN, "Login field is empty")); - return false; - } - this.studentId = profile == null ? -1 : profile.getStudentData("studentId", -1); - this.attendancesLastSync = profile == null ? 0 : profile.getStudentData("attendancesLastSync", (long)0); - fakeLogin = BuildConfig.DEBUG && loginUsername.toLowerCase().startsWith("fake"); - - teamList = profileId == -1 ? new ArrayList<>() : app.db.teamDao().getAllNow(profileId); - teacherList = profileId == -1 ? new ArrayList<>() : app.db.teacherDao().getAllNow(profileId); - subjectList = new ArrayList<>(); - lessonList = new ArrayList<>(); - lessonChangeList = new ArrayList<>(); - gradeAddedDates = new SparseArray<>(); - gradeAverages = new SparseArray<>(); - gradeColors = new SparseIntArray(); - gradeList = new ArrayList<>(); - eventList = new ArrayList<>(); - noticeList = new ArrayList<>(); - attendanceList = new ArrayList<>(); - messageList = new ArrayList<>(); - messageRecipientList = new ArrayList<>(); - messageRecipientIgnoreList = new ArrayList<>(); - metadataList = new ArrayList<>(); - messageMetadataList = new ArrayList<>(); - - return true; - } - - @Override - public void sync(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - if (!prepare(activityContext, callback, profileId, profile, loginStore)) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - targetEndpoints.add("GetData"); - targetEndpoints.add("ProcessData"); - targetEndpoints.add("Attendances"); - targetEndpoints.add("ClassCalendar"); - targetEndpoints.add("GradeDetails"); - targetEndpoints.add("NoticeDetails"); - targetEndpoints.add("Messages"); - targetEndpoints.add("MessagesInbox"); - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - @Override - public void syncFeature(@NonNull Context activityContext, @NonNull SyncCallback callback, @NonNull ProfileFull profile, @Nullable int ... featureList) { - if (featureList == null) { - sync(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile)); - return; - } - if (!prepare(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - if (featureList.length == 1) - onlyFeature = featureList[0]; - if (featureList.length == 1 && (featureList[0] == FEATURE_MESSAGES_INBOX || featureList[0] == FEATURE_MESSAGES_OUTBOX)) { - teacherList = app.db.teacherDao().getAllNow(profileId); - for (Teacher teacher: teacherList) { - teachersMap.put((int) teacher.id, teacher.getFullNameLastFirst()); - } - if (featureList[0] == FEATURE_MESSAGES_INBOX) { - targetEndpoints.add("MessagesInbox"); - } - else { - targetEndpoints.add("Messages"); - } - } - else { - // this is needed for all features except messages - targetEndpoints.add("GetData"); - targetEndpoints.add("ProcessData"); - for (int feature: featureList) { - switch (feature) { - case FEATURE_AGENDA: - targetEndpoints.add("ClassCalendar"); - break; - case FEATURE_GRADES: - targetEndpoints.add("GradeDetails"); - break; - case FEATURE_NOTICES: - targetEndpoints.add("NoticeDetails"); - break; - case FEATURE_ATTENDANCES: - targetEndpoints.add("Attendances"); - break; - case FEATURE_MESSAGES_INBOX: - targetEndpoints.add("MessagesInbox"); - break; - case FEATURE_MESSAGES_OUTBOX: - targetEndpoints.add("Messages"); - break; - } - } - } - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - - private void begin() { - callback.onProgress(PROGRESS_LOGIN); - - r("get", null); - } - - private void r(String type, String endpoint) { - // endpoint == null when beginning - if (endpoint == null) - endpoint = targetEndpoints.get(0); - int index = -1; - for (String request: targetEndpoints) { - index++; - if (request.equals(endpoint)) { - break; - } - } - if (type.equals("finish")) { - // called when finishing the action - callback.onProgress(PROGRESS_STEP); - index++; - } - d(TAG, "Called r("+type+", "+endpoint+"). Getting "+targetEndpoints.get(index)); - switch (targetEndpoints.get(index)) { - case "GetData": - getData(); - break; - case "ProcessData": - processData(); - break; - case "ClassCalendar": - getClassCalendar(); - break; - case "GradeDetails": - getGradeDetails(); - break; - case "NoticeDetails": - getNoticeDetails(); - break; - case "Attendances": - getAttendances(); - break; - case "Messages": - getAllMessages(); - break; - case "MessagesInbox": - getMessagesInbox(); - break; - case "Finish": - finish(); - break; - } - } - private void saveData() { - if (teamList.size() > 0) { - app.db.teamDao().clear(profileId); - app.db.teamDao().addAll(teamList); - } - if (teacherList.size() > 0) - app.db.teacherDao().addAllIgnore(teacherList); - if (subjectList.size() > 0) - app.db.subjectDao().addAll(subjectList); - if (lessonList.size() > 0) { - app.db.lessonDao().clear(profileId); - app.db.lessonDao().addAll(lessonList); - } - if (lessonChangeList.size() > 0) - app.db.lessonChangeDao().addAll(lessonChangeList); - if (gradeList.size() > 0) { - app.db.gradeDao().clearForSemester(profileId, profile.getCurrentSemester()); - app.db.gradeDao().addAll(gradeList); - app.db.gradeDao().updateDetails(profileId, gradeAverages, gradeAddedDates, gradeColors); - } - if (eventList.size() > 0) { - app.db.eventDao().removeFuture(profileId, today); - app.db.eventDao().addAll(eventList); - } - if (noticeList.size() > 0) { - app.db.noticeDao().clear(profileId); - app.db.noticeDao().addAll(noticeList); - } - if (attendanceList.size() > 0) { - // clear only last two weeks - //app.db.attendanceDao().clearAfterDate(profileId, Date.getToday().stepForward(0, 0, -14)); - app.db.attendanceDao().addAll(attendanceList); - } - if (messageList.size() > 0) - app.db.messageDao().addAllIgnore(messageList); - if (messageRecipientList.size() > 0) - app.db.messageRecipientDao().addAll(messageRecipientList); - if (messageRecipientIgnoreList.size() > 0) - app.db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList); - if (metadataList.size() > 0) - app.db.metadataDao().addAllIgnore(metadataList); - if (messageMetadataList.size() > 0) - app.db.metadataDao().setSeen(messageMetadataList); - } - private void finish() { - try { - saveData(); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 303, CODE_OTHER, app.getString(R.string.sync_error_saving_data), null, null, e, null)); - } - if (fullSync) { - profile.setLastFullSync(System.currentTimeMillis()); - fullSync = false; - } - profile.setEmpty(false); - callback.onSuccess(activityContext, new ProfileFull(profile, loginStore)); - } - private void finishWithError(AppError error) { - try { - saveData(); - } - catch (Exception e) { - Crashlytics.logException(e); - } - callback.onError(activityContext, error); - } - - /* _ _ - | | (_) - | | ___ __ _ _ _ __ - | | / _ \ / _` | | '_ \ - | |___| (_) | (_| | | | | | - |______\___/ \__, |_|_| |_| - __/ | - |__*/ - private void login(@NonNull LoginCallback loginCallback) { - if (System.currentTimeMillis() - lastLoginTime < 10*60*1000 - && lastLogin.equals(loginServerName+":"+loginUsername)) { - loginCallback.onSuccess(); - return; - } - app.cookieJar.clearForDomain(loginServerName+".mobidziennik.pl"); - - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_log.php" : "https://"+loginServerName+".mobidziennik.pl/api/") - .userAgent(System.getProperty( "http.agent" )) - .contentType("application/x-www-form-urlencoded; charset=UTF-8") - .addParameter("wersja", "20") - .addParameter("ip", app.deviceId) - .addParameter("login", loginUsername) - .addParameter("haslo", loginPassword) - .addParameter("token", app.appConfig.fcmTokens.get(LOGIN_TYPE_MOBIDZIENNIK).first) - .post() - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 333, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - - //app.profile.loggedIn = (data != null && data.equals("ok")); - if (data == null || data.equals("")) { // for safety - finishWithError(new AppError(TAG, 339, CODE_MAINTENANCE, response)); - } - else if (data.equals("Nie jestes zalogowany")) { - finishWithError(new AppError(TAG, 343, AppError.CODE_INVALID_LOGIN, response, data)); - } - else if (data.equals("ifun")) { - finishWithError(new AppError(TAG, 346, AppError.CODE_INVALID_DEVICE, response, data)); - } - else if (data.equals("stare haslo")) { - finishWithError(new AppError(TAG, 349, AppError.CODE_OLD_PASSWORD, response, data)); - } - else if (data.equals("Archiwum")) { - finishWithError(new AppError(TAG, 352, AppError.CODE_ARCHIVED, response, data)); - } - else if (data.equals("Trwają prace techniczne lub pojawił się jakiś problem")) { - finishWithError(new AppError(TAG, 355, CODE_MAINTENANCE, data, response, data)); - } - else if (data.equals("ok")) - { - // a successful login - if (profile != null && studentId != -1) { - lastLogin = loginServerName+":"+loginUsername; - lastLoginTime = System.currentTimeMillis(); - loginCallback.onSuccess(); - return; - } - getData((data1 -> { - String[] tables = data1.split("T@B#LA"); - - List studentIds = new ArrayList<>(); - List studentNamesLong = new ArrayList<>(); - List studentNamesShort = new ArrayList<>(); - String[] student = tables[8].split("\n"); - for (String aStudent : student) { - if (aStudent.isEmpty()) { - continue; - } - String[] student1 = aStudent.split("\\|", Integer.MAX_VALUE); - if (student1.length == 2) - continue; - studentIds.add(strToInt(student1[0])); - studentNamesLong.add(student1[2] + " " + student1[4]); - studentNamesShort.add(student1[2] + " " + student1[4].charAt(0) + "."); - } - - List profileList = new ArrayList<>(); - - for (int index = 0; index < studentIds.size(); index++) { - Profile profile = new Profile(); - profile.setStudentNameLong(studentNamesLong.get(index)); - profile.setStudentNameShort(studentNamesShort.get(index)); - profile.setName(profile.getStudentNameLong()); - profile.setSubname(loginUsername); - profile.setEmpty(true); - profile.setLoggedIn(true); - profile.putStudentData("studentId", studentIds.get(index)); - profileList.add(profile); - } - - callback.onLoginFirst(profileList, loginStore); - })); - } - else { - if (data.contains("Uuuups... nieprawidłowy adres")) { - finishWithError(new AppError(TAG, 404, AppError.CODE_INVALID_SERVER_ADDRESS, response, data)); - return; - } - finishWithError(new AppError(TAG, 407, AppError.CODE_MAINTENANCE, response, data)); - } - } - }) - .build() - .enqueue(); - } - - /* _ _ _ _ _ _ _ - | | | | | | ___ | | | | | | - | |__| | ___| |_ __ ___ _ __ ___ ( _ ) ___ __ _| | | |__ __ _ ___| | _____ - | __ |/ _ \ | '_ \ / _ \ '__/ __| / _ \/\ / __/ _` | | | '_ \ / _` |/ __| |/ / __| - | | | | __/ | |_) | __/ | \__ \ | (_> < | (_| (_| | | | |_) | (_| | (__| <\__ \ - |_| |_|\___|_| .__/ \___|_| |___/ \___/\/ \___\__,_|_|_|_.__/ \__,_|\___|_|\_\___/ - | | - |*/ - private interface GetDataCallback { - void onSuccess(String data); - } - private void getData(GetDataCallback getDataCallback) { - callback.onActionStarted(R.string.sync_action_syncing); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod.php?"+System.currentTimeMillis() : "https://"+ loginServerName +".mobidziennik.pl/api/zrzutbazy") - .userAgent(System.getProperty( "http.agent" )) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 427, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - if (data == null || data.equals("")) { - finishWithError(new AppError(TAG, 432, CODE_MAINTENANCE, response)); - return; - } - if (data.equals("Nie jestes zalogowany")) { - finishWithError(new AppError(TAG, 437, CODE_INVALID_LOGIN, response, data)); - lastLoginTime = 0; - return; - } - - lastResponse = data; - - getDataCallback.onSuccess(data); - } - }) - .build() - .enqueue(); - } - private void getData() { - getData((data -> r("finish", "GetData"))); - } - - /* _____ _ _____ _ - | __ \ | | | __ \ | | - | | | | __ _| |_ __ _ | |__) |___ __ _ _ _ ___ ___| |_ ___ - | | | |/ _` | __/ _` | | _ // _ \/ _` | | | |/ _ \/ __| __/ __| - | |__| | (_| | || (_| | | | \ \ __/ (_| | |_| | __/\__ \ |_\__ \ - |_____/ \__,_|\__\__,_| |_| \_\___|\__, |\__,_|\___||___/\__|___/ - | | - |*/ - private void processData() { - callback.onActionStarted(R.string.sync_action_processing_data); - String[] tables = lastResponse.split("T@B#LA"); - - if (studentId == -1) { - return; - } - try { - int i = 0; - for (String table : tables) { - if (i == 0) { - processUsers(table); - } - if (i == 3) { - processDates(table); - } - if (i == 4) { - processSubjects(table); - } - if (i == 7) { - processTeams(table, null); - } - if (i == 8) { - processStudent(table, loginUsername); - } - if (i == 9) { - processTeams(null, table); - } - if (i == 14) { - processGradeCategories(table); - } - if (i == 15) { - processLessons(table); - } - if (i == 16) { - processAttendances(table); - } - if (i == 17) { - processNotices(table); - } - if (i == 18) { - processGrades(table); - } - if (i == 21) { - processEvents(table); - } - if (i == 23) { - processHomeworks(table); - } - if (i == 24) { - processTimetable(table); - } - i++; - } - r("finish", "ProcessData"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 511, CODE_OTHER, e, lastResponse)); - } - } - - private void getClassCalendar() { - callback.onActionStarted(R.string.sync_action_syncing_calendar); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_kalendarzklasowy.php" : "https://" + loginServerName + ".mobidziennik.pl/dziennik/kalendarzklasowy") - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 523, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - // just skip any failures here - if (data == null || data.equals("")) { - r("finish", "ClassCalendar"); - return; - } - if (data.contains("nie-pamietam-hasla")) { - r("finish", "ClassCalendar"); - return; - } - - if (profile.getLuckyNumberEnabled() - && (profile.getLuckyNumberDate() == null || profile.getLuckyNumberDate().getValue() != Date.getToday().getValue() || profile.getLuckyNumber() == -1)) { - // set the current date as we checked the lucky number today - profile.setLuckyNumber(-1); - profile.setLuckyNumberDate(Date.getToday()); - - Matcher matcher = Pattern.compile("class=\"szczesliwy_numerek\".*>0*([0-9]+)(?:/0*[0-9]+)*", Pattern.DOTALL).matcher(data); - if (matcher.find()) { - try { - profile.setLuckyNumber(Integer.parseInt(matcher.group(1))); - } catch (Exception e3) { - e3.printStackTrace(); - } - } - app.db.luckyNumberDao().add(new LuckyNumber(profileId, profile.getLuckyNumberDate(), profile.getLuckyNumber())); - } - - Matcher matcher = Pattern.compile("events: (.+),$", Pattern.MULTILINE).matcher(data); - if (matcher.find()) { - try { - //d(TAG, matcher.group(1)); - JsonArray events = new JsonParser().parse(matcher.group(1)).getAsJsonArray(); - //d(TAG, "Events size "+events.size()); - for (JsonElement eventEl: events) { - JsonObject event = eventEl.getAsJsonObject(); - - //d(TAG, "Event "+event.toString()); - - JsonElement idEl = event.get("id"); - String idStr; - if (idEl != null && (idStr = idEl.getAsString()).startsWith("kalendarz;")) { - String[] idParts = idStr.split(";", Integer.MAX_VALUE); - if (idParts.length > 2) { - long id = idParts[2].isEmpty() ? -1 : strToInt(idParts[2]); - long targetStudentId = strToInt(idParts[1]); - if (targetStudentId != studentId) - continue; - - // TODO null-safe getAs..() - Date eventDate = Date.fromY_m_d(event.get("start").getAsString()); - //if (eventDate.getValue() < today.getValue()) - // continue; - - String eventColor = event.get("color").getAsString(); - - int eventType = Event.TYPE_INFORMATION; - - switch (eventColor) { - case "#C54449": - case "#c54449": - eventType = Event.TYPE_SHORT_QUIZ; - break; - case "#AB0001": - case "#ab0001": - eventType = Event.TYPE_EXAM; - break; - case "#008928": - eventType = Event.TYPE_CLASS_EVENT; - break; - case "#b66000": - case "#B66000": - eventType = Event.TYPE_EXCURSION; - break; - } - - String title = event.get("title").getAsString(); - String comment = event.get("comment").getAsString(); - - String topic = title; - if (!title.equals(comment)) { - topic += "\n"+comment; - } - - if (id == -1) { - id = crc16(topic.getBytes()); - } - - Event eventObject = new Event( - profileId, - id, - eventDate, - null, - topic, - -1, - eventType, - false, - -1, - -1, - teamClass != null ? teamClass.id : -1 - ); - - - //d(TAG, "Event "+eventObject); - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis() /* no addedDate here though */)); - } - } - } - r("finish", "ClassCalendar"); - } catch (Exception e3) { - finishWithError(new AppError(TAG, 638, CODE_OTHER, response, e3, data)); - } - } - else { - r("finish", "ClassCalendar"); - } - } - }) - .build() - .enqueue(); - } - - private void getGradeDetails() { - callback.onActionStarted(R.string.sync_action_syncing_grade_details); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_oceny.php" : "https://" + loginServerName + ".mobidziennik.pl/dziennik/oceny?semestr="+ profile.getCurrentSemester()) - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 658, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - // just skip any failures here - if (data == null || data.equals("")) { - r("finish", "GradeDetails"); - return; - } - if (data.contains("nie-pamietam-hasla")) { - r("finish", "GradeDetails"); - return; - } - - try { - Document doc = Jsoup.parse(data); - - Elements grades = doc.select("table.spis a, table.spis span, table.spis div"); - - String gradeCategory = ""; - int gradeColor = -1; - String subjectName = ""; - - for (Element e: grades) { - switch (e.tagName()) { - case "div": { - //d(TAG, "Outer HTML "+e.outerHtml()); - Matcher matcher = Pattern.compile("\\n*\\s*(.+?)\\n*(?:<.*?)??", Pattern.DOTALL).matcher(e.outerHtml()); - if (matcher.find()) { - subjectName = matcher.group(1); - } - break; - } - case "span": { - String css = e.attr("style"); - Matcher matcher = Pattern.compile("background-color:([#A-Fa-f0-9]+);", Pattern.DOTALL).matcher(css); - if (matcher.find()) { - gradeColor = Color.parseColor(matcher.group(1)); - } - matcher = Pattern.compile("> (.+?):", Pattern.DOTALL).matcher(e.outerHtml()); - if (matcher.find()) { - gradeCategory = matcher.group(1); - } - break; - } - case "a": { - int gradeId = strToInt(e.attr("rel")); - float gradeClassAverage = -1; - Date gradeAddedDate = null; - long gradeAddedDateMillis = -1; - - String html = e.html(); - Matcher matcher = Pattern.compile("Średnia ocen:.*([0-9]*\\.?[0-9]*)", Pattern.DOTALL).matcher(html); - if (matcher.find()) { - gradeClassAverage = Float.parseFloat(matcher.group(1)); - } - - matcher = Pattern.compile("Wpisano:.*.+?,\\s([0-9]+)\\s(.+?)\\s([0-9]{4}),\\sgodzina\\s([0-9:]+)", Pattern.DOTALL).matcher(html); - int month = 1; - if (matcher.find()) { - switch (matcher.group(2)) { - case "stycznia": - month = 1; - break; - case "lutego": - month = 2; - break; - case "marca": - month = 3; - break; - case "kwietnia": - month = 4; - break; - case "maja": - month = 5; - break; - case "czerwca": - month = 6; - break; - case "lipca": - month = 7; - break; - case "sierpnia": - month = 8; - break; - case "września": - month = 9; - break; - case "października": - month = 10; - break; - case "listopada": - month = 11; - break; - case "grudnia": - month = 12; - break; - } - gradeAddedDate = new Date( - strToInt(matcher.group(3)), - month, - strToInt(matcher.group(1)) - ); - Time time = Time.fromH_m_s( - matcher.group(4) - ); - gradeAddedDateMillis = gradeAddedDate.combineWith(time); - } - - matcher = Pattern.compile("Liczona do średniej:.*?nie
", Pattern.DOTALL).matcher(html); - if (matcher.find()) { - matcher = Pattern.compile("(.+?).*?.+?.*?\\((.+?)\\).*?.*?Wartość oceny:.*?([0-9.]+).*?Wpisał\\(a\\):.*?(.+?)", Pattern.DOTALL).matcher(html); - if (matcher.find()) { - String gradeName = matcher.group(1); - String gradeDescription = matcher.group(2); - float gradeValue = Float.parseFloat(matcher.group(3)); - String teacherName = matcher.group(4); - - long teacherId = -1; - long subjectId = -1; - - //d(TAG, "Grade "+gradeName+" "+gradeCategory+" subject "+subjectName+" teacher "+teacherName); - - if (!subjectName.equals("")) { - int subjectIndex = -1; - for (int i = 0; i < subjectsMap.size(); i++) { - if (subjectsMap.valueAt(i).equals(subjectName)) { - subjectIndex = i; - } - } - //d(TAG, "subjectIndex "+subjectIndex); - if (subjectIndex >= 0 && subjectIndex < subjectsMap.size()) { - subjectId = subjectsMap.keyAt(subjectIndex); - } - } - - if (!teacherName.equals("")) { - //d(TAG, "teacherName "+teacherName); - int teacherIndex = -1; - for (int i = 0; i < teachersMap.size(); i++) { - if (teachersMap.valueAt(i).equals(teacherName)) { - teacherIndex = i; - } - } - //d(TAG, "teacherIndex "+teacherIndex); - if (teacherIndex >= 0 && teacherIndex < teachersMap.size()) { - teacherId = teachersMap.keyAt(teacherIndex); - } - } - - int semester = profile.dateToSemester(gradeAddedDate); - - Grade gradeObject = new Grade( - profileId, - gradeId, - gradeCategory, - gradeColor, - "NLDŚR, "+gradeDescription, - gradeName, - gradeValue, - 0, - semester, - teacherId, - subjectId - ); - - gradeObject.classAverage = gradeClassAverage; - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), gradeAddedDateMillis)); - } - } else { - gradeAverages.put(gradeId, gradeClassAverage); - gradeAddedDates.put(gradeId, gradeAddedDateMillis); - gradeColors.put(gradeId, gradeColor); - } - break; - } - } - } - - r("finish", "GradeDetails"); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 852, CODE_OTHER, response, e, data)); - } - } - }) - .build() - .enqueue(); - } - - // TODO: 2019-06-04 punkty z zachowania znikają po synchronizacji - private void getNoticeDetails() { - if (noticeList.size() == 0) { - r("finish", "NoticeDetails"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_notice_details); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_zachowanie.php" : "https://" + loginServerName + ".mobidziennik.pl/mobile/zachowanie") - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 873, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - // just skip any failures here - if (data == null || data.equals("")) { - r("finish", "NoticeDetails"); - return; - } - if (data.contains("nie-pamietam-hasla")) { - r("finish", "NoticeDetails"); - return; - } - - Matcher matcher = Pattern.compile("(?:([0-9-.,]+).+?)?
.+?Treść:.+?(?:Kategoria: (.+?).+?)?Czas", Pattern.DOTALL).matcher(data); - while (matcher.find()) { - try { - String pointsStr = matcher.group(1); - String idStr = matcher.group(2); - String categoryStr = matcher.groupCount() == 3 ? matcher.group(3) : null; - float points = pointsStr == null ? 0 : Float.parseFloat(pointsStr); - long id = Long.parseLong(idStr); - for (Notice notice: noticeList) { - if (notice.id != id) - continue; - notice.points = points; - notice.category = categoryStr; - } - } catch (Exception e) { - Crashlytics.logException(e); - e.printStackTrace(); - if (onlyFeature == FEATURE_NOTICES) - finishWithError(new AppError(TAG, 880, CODE_OTHER, response, e, data)); - else - r("finish", "NoticeDetails"); - return; - } - } - r("finish", "NoticeDetails"); - } - }) - .build() - .enqueue(); - } - - public static class AttendanceLessonRange { - int weekDay; - int lessonNumber; - Time startTime; - Time endTime; - List lessons; - - public AttendanceLessonRange(int section, int number, int lessonNumber, Time startTime, Time endTime) { - this.weekDay = section == 1 ? number + 4 : number; - this.lessonNumber = lessonNumber; - this.startTime = startTime; - this.endTime = endTime; - this.lessons = new ArrayList<>(); - } - public void addLesson(AttendanceLesson lesson) { - lessons.add(lesson); - } - } - public static class AttendanceLesson { - String subjectName; - String topic; - String teamName; - String teacherName; - long lessonId; - - public AttendanceLesson(String subjectName, String topic, String teamName, String teacherName, long lessonId) { - this.subjectName = subjectName; - this.topic = topic; - this.teamName = teamName; - this.teacherName = teacherName; - this.lessonId = lessonId; - } - } - private Date attendancesCheckDate = Week.getWeekStart(); - private void getAttendances() { - r("finish", "Attendances"); - // TODO: 2019-09-10 please download attendances from /dziennik/frekwencja. /mobile does not work above v13.0 - if (true) { - return; - } - callback.onActionStarted(R.string.sync_action_syncing_attendances); - d(TAG, "Get attendances for week "+attendancesCheckDate.getStringY_m_d()); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_frekwencja.php" : "https://" + loginServerName + ".mobidziennik.pl/mobile/frekwencja") - .userAgent(System.getProperty("http.agent")) - .addParameter("uczen", studentId) - .addParameter("data_poniedzialek", attendancesCheckDate.getStringY_m_d()) - .post() - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 944, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - // just skip any failures here - if (data == null || data.equals("")) { - r("finish", "Attendances"); - return; - } - if (data.contains("nie-pamietam-hasla")) { - r("finish", "Attendances"); - return; - } - - List ranges = new ArrayList<>(); - - try { - Matcher matcher = Pattern.compile("
.*?

([0-9]{2}):([0-9]{2}) - ([0-9]{2}):([0-9]{2}) (.+?)\\s*?

.*?

", Pattern.DOTALL).matcher(data); - while (matcher.find()) { - int section = strToInt(matcher.group(1)); - int number = strToInt(matcher.group(2)); - int lessonNumber = strToInt(matcher.group(3)); - int startHour = strToInt(matcher.group(4)); - int startMinute = strToInt(matcher.group(5)); - int endHour = strToInt(matcher.group(6)); - int endMinute = strToInt(matcher.group(7)); - String lessonsString = matcher.group(8); - AttendanceLessonRange range = new AttendanceLessonRange(section, number, lessonNumber, new Time(startHour, startMinute, 0), new Time(endHour, endMinute, 0)); - Matcher matcher2 = Pattern.compile("(.+?) - (.+?)??.+?.+?\\(([0-9]{1,2}) (.+?),\\s+(.+?), id lekcji: ([0-9]+?)\\).+?", Pattern.DOTALL).matcher(lessonsString); - while (matcher2.find()) { - String topic = matcher2.group(2); - topic = topic == null ? "" : Html.fromHtml(topic).toString(); - if (topic.startsWith("Lekcja odwołana:")) - continue; - String teamName = matcher2.group(3)+matcher2.group(4); - boolean notFound = true; - for (Team team: teamList) { - if (teamName.equals(team.name)) { - notFound = false; - break; - } - } - if (notFound) - continue; - range.addLesson( - new AttendanceLesson( - matcher2.group(1), - topic, - teamName, - matcher2.group(5), - strToInt(matcher2.group(6)) - ) - ); - } - ranges.add(range); - } - matcher = Pattern.compile("([.|sz+]+)?", Pattern.DOTALL).matcher(data); - int index = 0; - while (matcher.find()) { - int currentIndex = index++; - String markers = matcher.groupCount() == 1 ? matcher.group(1) : null; - if (markers == null) - continue; - AttendanceLessonRange range = ranges.get(currentIndex); - - Date date = attendancesCheckDate.clone().stepForward(0, 0, range.weekDay); - long addedDate = date.combineWith(range.startTime); - - int markerIndex = 0; - for (char marker: markers.toCharArray()) { - int type = TYPE_CUSTOM; - switch (marker) { - case '.': - type = TYPE_PRESENT; - break; - case '|': - type = TYPE_ABSENT; - break; - case 's': - type = TYPE_BELATED; - break; - case 'z': - type = TYPE_RELEASED; - break; - case '+': - type = TYPE_ABSENT_EXCUSED; - } - AttendanceLesson lesson; - if (markerIndex >= range.lessons.size()) { - lesson = new AttendanceLesson("", "Nieznana lekcja", "???", "", addedDate); - } - else { - lesson = range.lessons.get(markerIndex); - } - - long teacherId = -1; - long subjectId = -1; - - int teacherIndex = -1; - for (int i = 0; i < teachersMap.size(); i++) { - if (teachersMap.valueAt(i).equals(lesson.teacherName)) { - teacherIndex = i; - } - } - if (teacherIndex >= 0 && teacherIndex < teachersMap.size()) { - teacherId = teachersMap.keyAt(teacherIndex); - } - - int subjectIndex = -1; - for (int i = 0; i < subjectsMap.size(); i++) { - if (subjectsMap.valueAt(i).equals(lesson.subjectName)) { - subjectIndex = i; - } - } - if (subjectIndex >= 0 && subjectIndex < subjectsMap.size()) { - subjectId = subjectsMap.keyAt(subjectIndex); - } - - Attendance attendanceObject = new Attendance( - profileId, - lesson.lessonId, - teacherId, - subjectId, - profile.dateToSemester(date), - lesson.topic, - date, - range.startTime, - type); - markerIndex++; - attendanceList.add(attendanceObject); - if (attendanceObject.type != TYPE_PRESENT) { - boolean markAsRead = onlyFeature == FEATURE_ATTENDANCES && attendancesLastSync == 0; - metadataList.add(new Metadata(profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile.getEmpty() || markAsRead, profile.getEmpty() || markAsRead, addedDate)); - } - } - } - } catch (Exception e) { - Crashlytics.logException(e); - e.printStackTrace(); - if (onlyFeature == FEATURE_ATTENDANCES) - finishWithError(new AppError(TAG, 955, CODE_OTHER, response, e, data)); - else - r("finish", "Attendances"); - return; - } - - if (onlyFeature == FEATURE_ATTENDANCES) { - // syncing attendances exclusively - if (attendancesLastSync == 0) { - // first sync - get attendances until it's start of the school year - attendancesLastSync = profile.getSemesterStart(1).getInMillis(); - } - Date lastSyncDate = Date.fromMillis(attendancesLastSync); - lastSyncDate.stepForward(0, 0, -7); - if (lastSyncDate.getValue() < attendancesCheckDate.getValue()) { - attendancesCheckDate.stepForward(0, 0, -7); - r("get", "Attendances"); - } - else { - profile.putStudentData("attendancesLastSync", System.currentTimeMillis()); - r("finish", "Attendances"); - } - } - else { - if (attendancesLastSync != 0) { - // not a first sync - Date lastSyncDate = Date.fromMillis(attendancesLastSync); - lastSyncDate.stepForward(0, 0, 2); - if (lastSyncDate.getValue() >= attendancesCheckDate.getValue()) { - profile.putStudentData("attendancesLastSync", System.currentTimeMillis()); - } - } - r("finish", "Attendances"); - } - } - }) - .build() - .enqueue(); - } - - private void getAllMessages() { - if (!fullSync && onlyFeature != FEATURE_MESSAGES_OUTBOX) { - r("finish", "Messages"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_messages); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_wiadomosci_szukaj.php" : "https://" + loginServerName + ".mobidziennik.pl/dziennik/wyszukiwarkawiadomosci?q=+") - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 1012, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - // just skip any failures here - if (data == null || data.equals("")) { - r("finish", "GradeDetails"); - return; - } - if (data.contains("nie-pamietam-hasla")) { - r("finish", "GradeDetails"); - return; - } - - try { - long startTime = System.currentTimeMillis(); - Document doc = Jsoup.parse(data); - - Element listElement = doc.getElementsByClass("spis").first(); - if (listElement == null) { - r("finish", "Messages"); - return; - } - Elements list = listElement.getElementsByClass("podswietl"); - for (Element item: list) { - long id = Long.parseLong(item.attr("rel").replaceAll("[^\\d]", "" )); - - Element subjectEl = item.select("td:eq(0) div").first(); - String subject = subjectEl.text(); - - Element addedDateEl = item.select("td:eq(1)").first(); - String addedDateStr = addedDateEl.text(); - long addedDate = Date.fromIsoHm(addedDateStr); - - Element typeEl = item.select("td:eq(2) img").first(); - int type = TYPE_RECEIVED; - if (typeEl.outerHtml().contains("mail_send.png")) - type = TYPE_SENT; - - Element senderEl = item.select("td:eq(3) div").first(); - long senderId = -1; - if (type == TYPE_RECEIVED) { - String senderName = senderEl.text(); - for (int i = 0; i < teachersMap.size(); i++) { - if (senderName.equals(teachersMap.valueAt(i))) { - senderId = teachersMap.keyAt(i); - break; - } - } - messageRecipientList.add(new MessageRecipient(profileId, -1, id)); - } - else { - // TYPE_SENT, so multiple recipients possible - String[] recipientNames = senderEl.text().split(", "); - for (String recipientName: recipientNames) { - long recipientId = -1; - for (int i = 0; i < teachersMap.size(); i++) { - if (recipientName.equals(teachersMap.valueAt(i))) { - recipientId = teachersMap.keyAt(i); - break; - } - } - messageRecipientIgnoreList.add(new MessageRecipient(profileId, recipientId, id)); - } - } - - Message message = new Message( - profileId, - id, - subject, - null, - type, - senderId, - -1 - ); - - messageList.add(message); - metadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, addedDate)); - } - - } catch (Exception e3) { - finishWithError(new AppError(TAG, 949, CODE_OTHER, response, e3, data)); - return; - } - - r("finish", "Messages"); - } - }) - .build() - .enqueue(); - } - - private void getMessagesInbox() { - callback.onActionStarted(R.string.sync_action_syncing_messages); - Request.builder() - .url(fakeLogin ? "https://szkolny.eu/mobimobi/mobi_mod_wiadomosci.php" : "https://" + loginServerName + ".mobidziennik.pl/dziennik/wiadomosci") - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 972, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - // just skip any failures here - if (data == null || data.equals("")) { - r("finish", "GradeDetails"); - return; - } - if (data.contains("nie-pamietam-hasla")) { - r("finish", "GradeDetails"); - return; - } - - try { - if (data.contains("Brak wiadomości odebranych.")) { - r("finish", "MessagesInbox"); - return; - } - Document doc = Jsoup.parse(data); - - Elements list = doc.getElementsByClass("spis").first().getElementsByClass("podswietl"); - for (Element item: list) { - long id = Long.parseLong(item.attr("rel")); - - Element subjectEl = item.select("td:eq(0)").first(); - //d(TAG, "subjectEl "+subjectEl.outerHtml()); - boolean hasAttachments = false; - if (subjectEl.getElementsByTag("a").size() != 0) { - hasAttachments = true; - } - String subject = subjectEl.ownText(); - - Element addedDateEl = item.select("td:eq(1) small").first(); - //d(TAG, "addedDateEl "+addedDateEl.outerHtml()); - String addedDateStr = addedDateEl.text(); - long addedDate = Date.fromIsoHm(addedDateStr); - - Element senderEl = item.select("td:eq(2)").first(); - //d(TAG, "senderEl "+senderEl.outerHtml()); - String senderName = senderEl.ownText(); - long senderId = -1; - for (int i = 0; i < teachersMap.size(); i++) { - if (senderName.equals(teachersMap.valueAt(i))) { - senderId = teachersMap.keyAt(i); - break; - } - } - messageRecipientIgnoreList.add(new MessageRecipient(profileId, -1, id)); - - boolean isRead = item.select("td:eq(3) span").first().hasClass("wiadomosc_przeczytana"); - - Message message = new Message( - profileId, - id, - subject, - null, - TYPE_RECEIVED, - senderId, - -1 - ); - - if (hasAttachments) - message.setHasAttachments(); - - - messageList.add(message); - messageMetadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, isRead, isRead || profile.getEmpty(), addedDate)); - } - r("finish", "MessagesInbox"); - } catch (Exception e3) { - finishWithError(new AppError(TAG, 1044, CODE_OTHER, response, e3, data)); - } - } - }) - .build() - .enqueue(); - } - - private SparseArray teachersMap = new SparseArray<>(); - private void processUsers(String table) - { - String[] users = table.split("\n"); - - //app.db.teacherDao().clear(profileId); - for (String userStr: users) - { - if (userStr.isEmpty()) { - continue; - } - String[] user = userStr.split("\\|", Integer.MAX_VALUE); - - teachersMap.put(strToInt(user[0]), user[5]+" "+user[4]); - teacherList.add(new Teacher(profileId, strToInt(user[0]), user[4], user[5])); - } - } - - private void processDates(String table) - { - String[] dates = table.split("\n"); - for (String dateStr: dates) - { - if (dateStr.isEmpty()) { - continue; - } - String[] date = dateStr.split("\\|", Integer.MAX_VALUE); - switch (date[1]) { - case "semestr1_poczatek": - profile.setDateSemester1Start(Date.fromYmd(date[3])); - break; - case "semestr2_poczatek": - profile.setDateSemester2Start(Date.fromYmd(date[3])); - break; - case "koniec_roku_szkolnego": - profile.setDateYearEnd(Date.fromYmd(date[3])); - break; - } - } - } - - private SparseArray subjectsMap = new SparseArray<>(); - private void processSubjects(String table) - { - String[] subjects = table.split("\n"); - - // because a student may no longer have a subject - // which he had before - // therefore, this subject's grades do not get deleted - // so we should keep the subject for correct naming - //app.db.subjectDao().clear(profileId); - for (String subjectStr: subjects) - { - if (subjectStr.isEmpty()) { - continue; - } - String[] subject = subjectStr.split("\\|", Integer.MAX_VALUE); - - subjectsMap.put(strToInt(subject[0]), subject[1]); - subjectList.add(new Subject(profileId, strToInt(subject[0]), subject[1], subject[2])); - } - } - - private Team teamClass = null; - private SparseArray teamsMap = new SparseArray<>(); - private List allTeams = new ArrayList<>(); - private void processTeams(String tableTeams, String tableRelations) - { - if (tableTeams != null) - { - allTeams.clear(); - String[] teams = tableTeams.split("\n"); - for (String teamStr: teams) { - if (teamStr.isEmpty()) { - continue; - } - String[] team = teamStr.split("\\|", Integer.MAX_VALUE); - Team teamObject = new Team( - profileId, - strToInt(team[0]), - team[1]+team[2], - strToInt(team[3]), - loginServerName+":"+team[1]+team[2],// TODO zrobiłem to na szybko - strToInt(team[4], -1)); - allTeams.add(teamObject); - } - } - if (tableRelations != null) - { - app.db.teamDao().clear(profileId); - String[] teams = tableRelations.split("\n"); - for (String teamStr: teams) { - if (teamStr.isEmpty()) { - continue; - } - String[] team = teamStr.split("\\|", Integer.MAX_VALUE); - if (strToInt(team[1]) != studentId) - continue; - Team teamObject = Team.getById(allTeams, strToInt(team[2])); - if (teamObject != null) { - if (teamObject.type == 1) { - profile.setStudentNumber(strToInt(team[4])); - teamClass = teamObject; - } - teamsMap.put((int)teamObject.id, teamObject.name); - teamList.add(teamObject); - } - } - } - } - - private SparseArray> students = new SparseArray<>(); - private boolean processStudent(String table, String loginUsername) - { - students.clear(); - String[] student = table.split("\n"); - for (int i = 0; i < student.length; i++) { - if (student[i].isEmpty()) { - continue; - } - String[] student1 = student[i].split("\\|", Integer.MAX_VALUE); - String[] student2 = student[++i].split("\\|", Integer.MAX_VALUE); - students.put(strToInt(student1[0]), new Pair<>(student1, student2)); - } - Pair studentData = students.get(studentId); - try { - profile.setAttendancePercentage(Float.parseFloat(studentData.second[1])); - } - catch (Exception e) { - e.printStackTrace(); - } - return true; - } - - private SparseArray gradeCategoryList = new SparseArray<>(); - private void processGradeCategories(String table) - { - String[] gradeCategories = table.split("\n"); - - for (String gradeCategoryStr: gradeCategories) - { - if (gradeCategoryStr.isEmpty()) { - continue; - } - String[] gradeCategory = gradeCategoryStr.split("\\|", Integer.MAX_VALUE); - List columns = new ArrayList<>(); - Collections.addAll(columns, gradeCategory[7].split(";")); - - if (teamsMap.indexOfKey(strToInt(gradeCategory[1])) >= 0) { - gradeCategoryList.put( - Integer.parseInt(gradeCategory[0]), - new GradeCategory( - profileId, - Integer.parseInt(gradeCategory[0]), - Float.parseFloat(gradeCategory[3]), - Color.parseColor("#"+gradeCategory[6]), - gradeCategory[4] - ).addColumns(columns) - ); - } - } - } - - private class MobiLesson { - long id; - int subjectId; - int teacherId; - int teamId; - String topic; - Date date; - Time startTime; - Time endTime; - int presentCount; - int absentCount; - int lessonNumber; - String signed; - - MobiLesson(long id, int subjectId, int teacherId, int teamId, String topic, Date date, Time startTime, Time endTime, int presentCount, int absentCount, int lessonNumber, String signed) { - this.id = id; - this.subjectId = subjectId; - this.teacherId = teacherId; - this.teamId = teamId; - this.topic = topic; - this.date = date; - this.startTime = startTime; - this.endTime = endTime; - this.presentCount = presentCount; - this.absentCount = absentCount; - this.lessonNumber = lessonNumber; - this.signed = signed; - } - } - - private List mobiLessons = new ArrayList<>(); - - private MobiLesson getLesson(long id) { - for (MobiLesson mobiLesson : mobiLessons) { - if (mobiLesson.id == id) - return mobiLesson; - } - return null; - } - - private void processLessons(String table) - { - mobiLessons.clear(); - - String[] lessons2 = table.split("\n"); - for (String lessonStr: lessons2) - { - if (lessonStr.isEmpty()) { - continue; - } - String[] lesson = lessonStr.split("\\|", Integer.MAX_VALUE); - - MobiLesson newMobiLesson = new MobiLesson( - Long.parseLong(lesson[0]), - strToInt(lesson[1]), - strToInt(lesson[2]), - strToInt(lesson[3]), - lesson[4], - Date.fromYmd(lesson[5]), - Time.fromYmdHm(lesson[6]), - Time.fromYmdHm(lesson[7]), - strToInt(lesson[8]), - strToInt(lesson[9]), - strToInt(lesson[10]), - lesson[11]); - mobiLessons.add(newMobiLesson); - } - } - - private void processAttendances(String table) - { - if (true) - return; - String[] attendances = table.split("\n"); - for (String attendanceStr: attendances) - { - if (attendanceStr.isEmpty()) { - continue; - } - String[] attendance = attendanceStr.split("\\|", Integer.MAX_VALUE); - - if (strToInt(attendance[2]) != studentId) - continue; - //RegisterAttendance oldAttendance = RegisterAttendance.getById(oldAttendances, Long.parseLong(attendance[0])); - - MobiLesson mobiLesson = getLesson(Long.parseLong(attendance[1])); - if (mobiLesson != null) { - int type = TYPE_PRESENT; - switch (attendance[4]) { - case "2": - type = TYPE_ABSENT; - break; - case "5": - type = TYPE_ABSENT_EXCUSED; - break; - case "4": - type = TYPE_RELEASED; - break; - } - - int semester = profile.dateToSemester(mobiLesson.date); - - Attendance attendanceObject = new Attendance( - profileId, - strToInt(attendance[0]), - mobiLesson.teacherId, - mobiLesson.subjectId, - semester, - mobiLesson.topic, - mobiLesson.date, - mobiLesson.startTime, - type); - attendanceList.add(attendanceObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - } - - } - - private void processNotices(String table) - { - String[] notices = table.split("\n"); - - for (String noticeStr: notices) - { - if (noticeStr.isEmpty()) { - continue; - } - String[] notice = noticeStr.split("\\|", Integer.MAX_VALUE); - - if (strToInt(notice[2]) != studentId) - continue; - - Notice noticeObject = new Notice( - profileId, - strToInt(notice[0]), - notice[4], - strToInt(notice[6]), - (notice[3] != null ? (notice[3].equals("1") ? Notice.TYPE_POSITIVE : Notice.TYPE_NEGATIVE) : Notice.TYPE_NEUTRAL), - strToInt(notice[5]) - ); - - Metadata metadata = new Metadata(profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile.getEmpty(), profile.getEmpty(), new Date().parseFromYmd(notice[7]).getInMillis()); - - noticeList.add(noticeObject); - metadataList.add(metadata); - } - } - - private void processGrades(String table) - { - app.db.gradeDao().getDetails(profileId, gradeAddedDates, gradeAverages, gradeColors); - - String[] grades = table.split("\n"); - - long addedDate = Date.getNowInMillis(); - for (String gradeStr: grades) - { - if (gradeStr.isEmpty()) { - continue; - } - String[] grade = gradeStr.split("\\|", Integer.MAX_VALUE); - - if (strToInt(grade[1]) != studentId) - continue; - - if (grade[6].equals("")) { - grade[6] = "-1"; - } - if (grade[10].equals("")) { - grade[10] = "1"; - } - - float weight = 0.0f; - String category = ""; - String description = ""; - int color = -1; - int categoryId = strToInt(grade[6]); - GradeCategory gradeCategory = gradeCategoryList.get(categoryId); - if (gradeCategory != null) { - weight = gradeCategory.weight; - category = gradeCategory.text; - description = gradeCategory.columns.get(strToInt(grade[10]) - 1); - color = gradeCategory.color; - } - - Grade gradeObject = new Grade( - profileId, - strToInt(grade[0]), - category, - color, - description, - grade[7], - Float.parseFloat(grade[11]), - weight, - strToInt(grade[5]), - strToInt(grade[2]), - strToInt(grade[3]) - ); - - - // fix for "0" value grades, so they're not counted in the average - if (gradeObject.value == 0.0f) { - gradeObject.weight = 0; - } - - switch (grade[8]) { - case "3": - // semester proposed - gradeObject.type = (gradeObject.semester == 1 ? TYPE_SEMESTER1_PROPOSED : TYPE_SEMESTER2_PROPOSED); - break; - case "1": - // semester final - gradeObject.type = (gradeObject.semester == 1 ? TYPE_SEMESTER1_FINAL : TYPE_SEMESTER2_FINAL); - break; - case "4": - // year proposed - gradeObject.type = TYPE_YEAR_PROPOSED; - break; - case "2": - // year final - gradeObject.type = TYPE_YEAR_FINAL; - break; - } - - Metadata metadata = new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate); - gradeList.add(gradeObject); - metadataList.add(metadata); - addedDate++; // increase the added date to sort grades as they are in the school profile - } - } - - private void processEvents(String table) - { - String[] events = table.split("\n"); - Date today = Date.getToday(); - for (String eventStr: events) - { - if (eventStr.isEmpty()) { - continue; - } - String[] event = eventStr.split("\\|", Integer.MAX_VALUE); - - Date eventDate = new Date().parseFromYmd(event[4]); - if (eventDate.getValue() < today.getValue()) - continue; - - String examType = ""; - Pattern pattern = Pattern.compile("\\(([0-9A-ząęóżźńśłć]*?)\\)$"); - Matcher matcher = pattern.matcher(event[5]); - if (matcher.find()) { - examType = matcher.group(1); - } - - long addedDate = 0; - - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); - try { - addedDate = simpleDateFormat.parse(event[7]).getTime(); - } catch (ParseException e) { - e.printStackTrace(); - } - - int eventType = examType.equals("sprawdzian") ? TYPE_EXAM : examType.equals("kartkówka") ? TYPE_SHORT_QUIZ : TYPE_DEFAULT; - Event eventObject = new Event( - profileId, - strToInt(event[0]), - eventDate, - new Time().parseFromYmdHm(event[6]), - event[5].replace("("+examType+")", ""), - -1, - eventType, - false, - strToInt(event[1]), - strToInt(event[3]), - strToInt(event[2]) - ); - - if (teamsMap.indexOfKey((int)eventObject.teamId) >= 0) { - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - } - } - - private void processHomeworks(String table) - { - String[] homeworks = table.split("\n"); - Date today = Date.getToday(); - for (String homeworkStr: homeworks) - { - if (homeworkStr.isEmpty()) { - continue; - } - String[] homework = homeworkStr.split("\\|", Integer.MAX_VALUE); - - Date eventDate = new Date().parseFromYmd(homework[2]); - if (eventDate.getValue() < today.getValue()) - continue; - - Event eventObject = new Event( - profileId, - strToInt(homework[0]), - new Date().parseFromYmd(homework[2]), - new Time().parseFromYmdHm(homework[3]), - homework[1], - -1, - Event.TYPE_HOMEWORK, - false, - strToInt(homework[7]), - strToInt(homework[6]), - strToInt(homework[5]) - ); - - if (teamsMap.indexOfKey((int)eventObject.teamId) >= 0) { - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_HOMEWORK, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - } - } - - private void processTimetable(String table) - { - String[] lessons = table.split("\n"); - List list = Arrays.asList(lessons); - //Collections.reverse(list); - - // searching for all planned lessons - for (String lessonStr: (String[])list.toArray()) - { - //Log.d(TAG, lessonStr); - if (!lessonStr.isEmpty()) - { - String[] lesson = lessonStr.split("\\|", Integer.MAX_VALUE); - - if (strToInt(lesson[0]) != studentId) - continue; - - if (lesson[1].equals("plan_lekcji") || lesson[1].equals("lekcja")) - { - Lesson lessonObject = new Lesson(profileId, lesson[2], lesson[3], lesson[4]); - - for(int i = 0; i < subjectsMap.size(); i++) { - int key = subjectsMap.keyAt(i); - String str = subjectsMap.valueAt(i); - if (lesson[5].equalsIgnoreCase(str)) { - lessonObject.subjectId = key; - } - } - for(int i = 0; i < teachersMap.size(); i++) { - int key = teachersMap.keyAt(i); - String str = teachersMap.valueAt(i); - if ((lesson[7] + " " + lesson[6]).equalsIgnoreCase(str)) { - lessonObject.teacherId = key; - } - } - for(int i = 0; i < teamsMap.size(); i++) { - int key = teamsMap.keyAt(i); - String str = teamsMap.valueAt(i); - if ((lesson[8] + lesson[9]).equalsIgnoreCase(str)) { - lessonObject.teamId = key; - } - } - lessonObject.classroomName = lesson[11]; - lessonList.add(lessonObject); - } - } - } - - // searching for all changes - for (String lessonStr: (String[])list.toArray()) - { - if (!lessonStr.isEmpty()) - { - String[] lesson = lessonStr.split("\\|", Integer.MAX_VALUE); - - if (strToInt(lesson[0]) != studentId) - continue; - - if (lesson[1].equals("zastepstwo") || lesson[1].equals("lekcja_odwolana")) - { - LessonChange lessonChange = new LessonChange(profileId, lesson[2], lesson[3], lesson[4]); - - //Log.d(TAG, "Lekcja "+lessonStr); - - for(int i = 0; i < subjectsMap.size(); i++) { - int key = subjectsMap.keyAt(i); - String str = subjectsMap.valueAt(i); - if (lesson[5].equalsIgnoreCase(str)) { - lessonChange.subjectId = key; - } - } - for(int i = 0; i < teachersMap.size(); i++) { - int key = teachersMap.keyAt(i); - String str = teachersMap.valueAt(i); - if ((lesson[7] + " " + lesson[6]).equalsIgnoreCase(str)) { - lessonChange.teacherId = key; - } - } - for(int i = 0; i < teamsMap.size(); i++) { - int key = teamsMap.keyAt(i); - String str = teamsMap.valueAt(i); - if ((lesson[8] + lesson[9]).equalsIgnoreCase(str)) { - lessonChange.teamId = key; - } - } - - if (lesson[1].equals("zastepstwo")) { - lessonChange.type = LessonChange.TYPE_CHANGE; - } - if (lesson[1].equals("lekcja_odwolana")) { - lessonChange.type = LessonChange.TYPE_CANCELLED; - } - if (lesson[1].equals("lekcja")) { - lessonChange.type = LessonChange.TYPE_ADDED; - } - lessonChange.classroomName = lesson[11]; - - Lesson originalLesson = lessonChange.getOriginalLesson(lessonList); - - if (lessonChange.type == LessonChange.TYPE_ADDED) { - if (originalLesson == null) { - // original lesson doesn't exist, save a new addition - // TODO - /*if (!RegisterLessonChange.existsAddition(app.profile, registerLessonChange)) { - app.profile.timetable.addLessonAddition(registerLessonChange); - }*/ - } - else { - // original lesson exists, so we need to compare them - if (!lessonChange.matches(originalLesson)) { - // the lessons are different, so it's probably a lesson change - // ahhh this damn API - lessonChange.type = LessonChange.TYPE_CHANGE; - } - } - - } - if (lessonChange.type != LessonChange.TYPE_ADDED) { - // it's not a lesson addition - lessonChangeList.add(lessonChange); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonChange.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - if (originalLesson == null) { - // there is no original lesson, so we have to add one in order to change it - lessonList.add(Lesson.fromLessonChange(lessonChange)); - } - } - } - } - } - } - - @Override - public Map getConfigurableEndpoints(Profile profile) { - return null; - } - - @Override - public boolean isEndpointEnabled(Profile profile, boolean defaultActive, String name) { - return defaultActive; - } - - @Override - public void syncMessages(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile) { - - } - - /* __ __ - | \/ | - | \ / | ___ ___ ___ __ _ __ _ ___ ___ - | |\/| |/ _ \/ __/ __|/ _` |/ _` |/ _ \/ __| - | | | | __/\__ \__ \ (_| | (_| | __/\__ \ - |_| |_|\___||___/___/\__,_|\__, |\___||___/ - __/ | - |__*/ - @Override - public void getMessage(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, @NonNull MessageGetCallback messageCallback) { - if (message.body != null) { - boolean readByAll = true; - message.recipients = app.db.messageRecipientDao().getAllByMessageId(profile.getId(), message.id); - for (MessageRecipientFull recipient: message.recipients) { - if (recipient.id == -1) - recipient.fullName = profile.getStudentNameLong(); - if (message.type == TYPE_SENT && recipient.readDate < 1) - readByAll = false; - } - if (!message.seen) { - app.db.metadataDao().setSeen(profile.getId(), message, true); - } - if (readByAll) { - // if a sent msg is not read by everyone, download it again to check the read status - new Handler(activityContext.getMainLooper()).post(() -> { - messageCallback.onSuccess(message); - }); - return; - } - } - - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> Request.builder() - .url("https://" + loginServerName + ".mobidziennik.pl/dziennik/"+(message.type == TYPE_RECEIVED || message.type == TYPE_DELETED ? "wiadodebrana" : "wiadwyslana")+"/?id="+message.id) - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 1720, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - if (data == null || data.equals("")) { - finishWithError(new AppError(TAG, 1727, CODE_MAINTENANCE, response)); - return; - } - if (data.contains("nie-pamietam-hasla")) { - lastLoginTime = 0; - finishWithError(new AppError(TAG, 1732, CODE_INVALID_LOGIN, response, data)); - return; - } - - List messageRecipientList = new ArrayList<>(); - - try { - Document doc = Jsoup.parse(data); - - Element content = doc.select("#content").first(); - - Element body = content.select(".wiadomosc_tresc").first(); - - if (message.type == TYPE_RECEIVED) { - MessageRecipientFull recipient = new MessageRecipientFull(profileId, -1, message.id); - long readDate = System.currentTimeMillis(); - Matcher matcher = Pattern.compile("czas przeczytania:.+?,\\s([0-9]+)\\s(.+?)\\s([0-9]{4}),\\sgodzina\\s([0-9:]+)", Pattern.DOTALL).matcher(body.html()); - if (matcher.find()) { - Date date = new Date( - strToInt(matcher.group(3)), - monthFromName(matcher.group(2)), - strToInt(matcher.group(1)) - ); - Time time = Time.fromH_m_s( - matcher.group(4) - ); - readDate = date.combineWith(time); - } - recipient.readDate = readDate; - recipient.fullName = profile.getStudentNameLong(); - messageRecipientList.add(recipient); - } - else { - message.senderId = -1; - message.senderReplyId = -1; - - List teacherList = app.db.teacherDao().getAllNow(profileId); - - Elements recipients = content.select("table.spis tr:has(td)"); - for (Element recipientEl: recipients) { - - Element senderEl = recipientEl.select("td:eq(0)").first(); - Teacher teacher = null; - String senderName = senderEl.text(); - for (Teacher aTeacher: teacherList) { - if (aTeacher.getFullNameLastFirst().equals(senderName)) { - teacher = aTeacher; - break; - } - } - - long readDate = 0; - Element isReadEl = recipientEl.select("td:eq(2)").first(); - if (!isReadEl.ownText().equals("NIE")) { - Element readDateEl = recipientEl.select("td:eq(3) small").first(); - Matcher matcher = Pattern.compile(".+?,\\s([0-9]+)\\s(.+?)\\s([0-9]{4}),\\sgodzina\\s([0-9:]+)", Pattern.DOTALL).matcher(readDateEl.ownText()); - if (matcher.find()) { - Date date = new Date( - strToInt(matcher.group(3)), - monthFromName(matcher.group(2)), - strToInt(matcher.group(1)) - ); - Time time = Time.fromH_m_s( - matcher.group(4) - ); - readDate = date.combineWith(time); - } - } - - MessageRecipientFull recipient = new MessageRecipientFull(profileId, teacher == null ? -1 : teacher.id, message.id); - recipient.readDate = readDate; - recipient.fullName = teacher == null ? "?" : teacher.getFullName(); - messageRecipientList.add(recipient); - } - } - - body.select("div").remove(); - - message.body = body.html(); - - message.clearAttachments(); - Elements attachments = content.select("ul li"); - if (attachments != null) { - //d(TAG, "attachments "+attachments.outerHtml()); - for (Element attachment: attachments) { - attachment = attachment.select("a").first(); - //d(TAG, "attachment "+attachment.outerHtml()); - String attachmentName = attachment.ownText(); - long attachmentId = -1; - Matcher matcher = Pattern.compile("href=\"https://.+?\\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)\"(?:.+?)(List) messageRecipientList); - - message.recipients = messageRecipientList; - - new Handler(activityContext.getMainLooper()).post(() -> { - messageCallback.onSuccess(message); - }); - } - }) - .build() - .enqueue()); - } - - @Override - public void getAttachment(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, long attachmentId, @NonNull AttachmentGetCallback attachmentCallback) { - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - Request.Builder builder = Request.builder() - .url("https://"+loginServerName+".mobidziennik.pl/dziennik/"+(message.type == TYPE_SENT ? "wiadwyslana" : "wiadodebrana")+"/?id="+message.id+"&zalacznik="+attachmentId); - new Handler(activityContext.getMainLooper()).post(() -> { - attachmentCallback.onSuccess(builder); - }); - }); - } - - @Override - public void getRecipientList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull RecipientListGetCallback recipientListGetCallback) { - if (!prepare(activityContext, errorCallback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - if (System.currentTimeMillis() - profile.getLastReceiversSync() < 24 * 60 * 60 * 1000) { - AsyncTask.execute(() -> { - List teacherList = app.db.teacherDao().getAllNow(profileId); - new Handler(activityContext.getMainLooper()).post(() -> recipientListGetCallback.onSuccess(teacherList)); - }); - return; - } - - login(() -> { - Request.builder() - .url("https://" + loginServerName + ".mobidziennik.pl/mobile/dodajwiadomosc") - .userAgent(System.getProperty("http.agent")) - .callback(new TextCallbackHandler() { - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 2289, CODE_OTHER, response, throwable)); - } - - @Override - public void onSuccess(String data, Response response) { - if (data == null || data.equals("")) { - finishWithError(new AppError(TAG, 2295, CODE_MAINTENANCE, response)); - return; - } - if (data.contains("nie-pamietam-hasla")) { - lastLoginTime = 0; - finishWithError(new AppError(TAG, 2300, CODE_INVALID_LOGIN, response, data)); - return; - } - - teacherList = app.db.teacherDao().getAllNow(profileId); - - for (Teacher teacher: teacherList) { - teacher.typeDescription = null; // TODO: 2019-06-13 it better - } - - Matcher categoryMatcher = Pattern.compile("").matcher(data); - while (categoryMatcher.find()) { - String categoryId = categoryMatcher.group(1); - String categoryName = categoryMatcher.group(2); - String categoryHtml = getRecipientCategory(categoryId); - if (categoryHtml == null) - return; // the error callback is already executed - Matcher teacherMatcher = Pattern.compile("(.+?)|").matcher(categoryHtml); - while (teacherMatcher.find()) { - if (teacherMatcher.group(1) != null) { - String className = teacherMatcher.group(1); - String listHtml = teacherMatcher.group(2); - Matcher listMatcher = Pattern.compile("").matcher(listHtml); - while (listMatcher.find()) { - updateTeacher(categoryId, Long.parseLong(listMatcher.group(1)), listMatcher.group(2), categoryName, className); - } - } - else { - updateTeacher(categoryId, Long.parseLong(teacherMatcher.group(3)), teacherMatcher.group(4), categoryName, null); - } - } - } - app.db.teacherDao().addAll(teacherList); - - profile.setLastReceiversSync(System.currentTimeMillis()); - app.db.profileDao().add(profile); - - new Handler(activityContext.getMainLooper()).post(() -> recipientListGetCallback.onSuccess(new ArrayList<>(teacherList))); - } - }) - .build() - .enqueue(); - }); - } - private String getRecipientCategory(String categoryId) { - Response response = null; - try { - response = Request.builder() - .url("https://" + loginServerName + ".mobidziennik.pl/mobile/odbiorcyWiadomosci") - .userAgent(System.getProperty("http.agent")) - .addParameter("typ", categoryId) - .post() - .build().execute(); - if (response.code() != 200) { - finishWithError(new AppError(TAG, 2349, CODE_OTHER, response)); - return null; - } - String data = new TextCallbackHandler().backgroundParser(response); - if (data == null || data.equals("")) { - return ""; - } - if (data.contains("nie-pamietam-hasla")) { - lastLoginTime = 0; - finishWithError(new AppError(TAG, 2300, CODE_INVALID_LOGIN, response, data)); - return null; - } - return data; - } catch (Exception e) { - finishWithError(new AppError(TAG, 2355, CODE_OTHER, response, e)); - return null; - } - } - private void updateTeacher(String category, long id, String nameLastFirst, String typeDescription, String className) { - Teacher teacher = Teacher.getById(teacherList, id); - if (teacher == null) { - String[] nameParts = nameLastFirst.split(" ", Integer.MAX_VALUE); - teacher = new Teacher(profileId, id, nameParts[1], nameParts[0]); - teacherList.add(teacher); - } - teacher.loginId = String.valueOf(id); - teacher.type = 0; - if (className != null) { - teacher.typeDescription = bs("", teacher.typeDescription, ", ")+className; - } - switch (category) { - case "1": - teacher.setType(Teacher.TYPE_PRINCIPAL); - break; - case "2": - teacher.setType(Teacher.TYPE_TEACHER); - break; - case "8": - teacher.setType(Teacher.TYPE_EDUCATOR); - break; - case "9": - teacher.setType(Teacher.TYPE_PEDAGOGUE); - break; - case "10": - teacher.setType(Teacher.TYPE_OTHER); - teacher.typeDescription = "Specjaliści"; - break; - case "Administracja WizjaNet": - teacher.setType(Teacher.TYPE_SUPER_ADMIN); - break; - case "Administratorzy": - teacher.setType(Teacher.TYPE_SCHOOL_ADMIN); - break; - case "Sekretarka": - teacher.setType(Teacher.TYPE_SECRETARIAT); - break; - default: - teacher.setType(Teacher.TYPE_OTHER); - teacher.typeDescription = typeDescription+bs(" ", teacher.typeDescription); - break; - } - } - - @Override - public MessagesComposeInfo getComposeInfo(@NonNull ProfileFull profile) { - return new MessagesComposeInfo(-1, profile.getStudentData("attachmentSizeLimit", 6291456L), 100, -1); - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/Vulcan.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/Vulcan.java deleted file mode 100644 index 32705060..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/Vulcan.java +++ /dev/null @@ -1,1676 +0,0 @@ -package pl.szczodrzynski.edziennik.api; - -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.util.Base64; -import android.util.Pair; -import android.util.SparseArray; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.crashlytics.android.Crashlytics; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import im.wangchao.mhttp.Request; -import im.wangchao.mhttp.Response; -import im.wangchao.mhttp.callback.JsonCallbackHandler; -import okhttp3.OkHttpClient; -import okio.Buffer; -import pl.szczodrzynski.edziennik.App; -import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.api.interfaces.AttachmentGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.EdziennikInterface; -import pl.szczodrzynski.edziennik.api.interfaces.LoginCallback; -import pl.szczodrzynski.edziennik.api.interfaces.MessageGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.RecipientListGetCallback; -import pl.szczodrzynski.edziennik.api.interfaces.SyncCallback; -import pl.szczodrzynski.edziennik.datamodels.Attendance; -import pl.szczodrzynski.edziennik.datamodels.Event; -import pl.szczodrzynski.edziennik.datamodels.Grade; -import pl.szczodrzynski.edziennik.datamodels.GradeCategory; -import pl.szczodrzynski.edziennik.datamodels.Lesson; -import pl.szczodrzynski.edziennik.datamodels.LessonChange; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.MessageFull; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipient; -import pl.szczodrzynski.edziennik.datamodels.MessageRecipientFull; -import pl.szczodrzynski.edziennik.datamodels.Metadata; -import pl.szczodrzynski.edziennik.datamodels.Notice; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.datamodels.Subject; -import pl.szczodrzynski.edziennik.datamodels.Teacher; -import pl.szczodrzynski.edziennik.datamodels.Team; -import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo; -import pl.szczodrzynski.edziennik.models.Date; -import pl.szczodrzynski.edziennik.models.Endpoint; -import pl.szczodrzynski.edziennik.models.Time; -import pl.szczodrzynski.edziennik.models.Week; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.api.AppError.CODE_OTHER; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_RELEASED; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_EXAM; -import static pl.szczodrzynski.edziennik.datamodels.Event.TYPE_SHORT_QUIZ; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER1_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_FINAL; -import static pl.szczodrzynski.edziennik.datamodels.Grade.TYPE_SEMESTER2_PROPOSED; -import static pl.szczodrzynski.edziennik.datamodels.LessonChange.TYPE_CANCELLED; -import static pl.szczodrzynski.edziennik.datamodels.LessonChange.TYPE_CHANGE; -import static pl.szczodrzynski.edziennik.datamodels.LoginStore.LOGIN_TYPE_VULCAN; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_RECEIVED; -import static pl.szczodrzynski.edziennik.datamodels.Message.TYPE_SENT; -import static pl.szczodrzynski.edziennik.datamodels.Metadata.TYPE_MESSAGE; -import static pl.szczodrzynski.edziennik.datamodels.Notice.TYPE_NEUTRAL; -import static pl.szczodrzynski.edziennik.utils.Utils.c; -import static pl.szczodrzynski.edziennik.utils.Utils.crc16; -import static pl.szczodrzynski.edziennik.utils.Utils.d; -import static pl.szczodrzynski.edziennik.utils.Utils.getGradeValue; -import static pl.szczodrzynski.edziennik.utils.Utils.getVulcanGradeColor; -import static pl.szczodrzynski.edziennik.utils.Utils.intToStr; - -public class Vulcan implements EdziennikInterface { - public Vulcan(App app) { - this.app = app; - } - - private static final String TAG = "api.Vulcan"; - private static final String MOBILE_APP_NAME = "VULCAN-Android-ModulUcznia"; - private static final String MOBILE_APP_VERSION = "19.4.1.436"; - private static final String ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat"; - private static final String ENDPOINT_STUDENT_LIST = "mobile-api/Uczen.v3.UczenStart/ListaUczniow"; - private static final String ENDPOINT_DICTIONARIES = "mobile-api/Uczen.v3.Uczen/Slowniki"; - private static final String ENDPOINT_TIMETABLE = "mobile-api/Uczen.v3.Uczen/PlanLekcjiZeZmianami"; - private static final String ENDPOINT_GRADES = "mobile-api/Uczen.v3.Uczen/Oceny"; - private static final String ENDPOINT_GRADES_PROPOSITIONS = "mobile-api/Uczen.v3.Uczen/OcenyPodsumowanie"; - private static final String ENDPOINT_EVENTS = "mobile-api/Uczen.v3.Uczen/Sprawdziany"; - private static final String ENDPOINT_HOMEWORKS = "mobile-api/Uczen.v3.Uczen/ZadaniaDomowe"; - private static final String ENDPOINT_NOTICES = "mobile-api/Uczen.v3.Uczen/UwagiUcznia"; - private static final String ENDPOINT_ATTENDANCES = "mobile-api/Uczen.v3.Uczen/Frekwencje"; - private static final String ENDPOINT_MESSAGES_RECEIVED = "mobile-api/Uczen.v3.Uczen/WiadomosciOdebrane"; - private static final String ENDPOINT_MESSAGES_SENT = "mobile-api/Uczen.v3.Uczen/WiadomosciWyslane"; - private static final String ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken"; - private static final String userAgent = "MobileUserAgent"; - - private App app; - private Context activityContext = null; - private SyncCallback callback = null; - private int profileId = -1; - private Profile profile = null; - private LoginStore loginStore = null; - private boolean fullSync = true; - private Date today = Date.getToday(); - private List targetEndpoints = new ArrayList<>(); - - // PROGRESS - private static final int PROGRESS_LOGIN = 10; - private int PROGRESS_COUNT = 1; - private int PROGRESS_STEP = (90/PROGRESS_COUNT); - - private int onlyFeature = FEATURE_ALL; - - private List teamList; - private List teacherList; - private List subjectList; - private List lessonList; - private List lessonChangeList; - private List gradeCategoryList; - private List gradeList; - private List eventList; - private List noticeList; - private List attendanceList; - private List messageList; - private List messageRecipientList; - private List messageRecipientIgnoreList; - private List metadataList; - private List messageMetadataList; - - private String apiUrl = null; - private String certificateKey = null; - private String certificatePfx = null; - private String deviceToken = null; - private String deviceSymbol = null; - private String devicePin = null; - /** - * deviceSymbol_JednostkaSprawozdawczaSymbol - */private String schoolName = null; - /** - * JednostkaSprawozdawczaSymbol - */private String schoolSymbol = null; - /** - * Id - */private int studentId = -1; - /** - * UzytkownikLoginId - */private int studentLoginId = -1; - /** - * IdOddzial - */private int studentClassId = -1; - /** - * IdOkresKlasyfikacyjny - */private int studentSemesterId = -1; - /** - * OkresNumer - */private int studentSemesterNumber = -1; - private boolean syncingSemester1 = false; - private OkHttpClient signingHttp = null; - private Date oneMonthBack = today.clone().stepForward(0, -1, 0); - - /* _____ - | __ \ - | |__) | __ ___ _ __ __ _ _ __ ___ - | ___/ '__/ _ \ '_ \ / _` | '__/ _ \ - | | | | | __/ |_) | (_| | | | __/ - |_| |_| \___| .__/ \__,_|_| \___| - | | - |*/ - private boolean prepare(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - this.activityContext = activityContext; - this.callback = callback; - this.profileId = profileId; - // here we must have a login store: either with a correct ID or -1 - // there may be no profile and that's when onLoginFirst happens - this.profile = profile; - this.loginStore = loginStore; - this.fullSync = profile == null || profile.getEmpty() || profile.shouldFullSync(activityContext); - this.today = Date.getToday(); - - if (this.signingHttp == null) { - this.signingHttp = this.app.http.newBuilder().addInterceptor(chain -> { - okhttp3.Request request = chain.request(); - Buffer buffer = new Buffer(); - assert request.body() != null; - request.body().writeTo(buffer); - String signature = ""; - // cannot use Utils.exception, because we are not in the main thread here! - // Utils.exception would show the dialog which has to be shown in the UI thread - try { - signature = Utils.VulcanRequestEncryptionUtils.signContent(buffer.readByteArray(), new ByteArrayInputStream(Base64.decode(this.certificatePfx, Base64.DEFAULT))); - } catch (IOException e) { - e.printStackTrace(); - } catch (GeneralSecurityException e) { - e.printStackTrace(); - } - request = request.newBuilder().addHeader("RequestSignatureValue", signature).addHeader("RequestCertificateKey", this.certificateKey).build(); - return chain.proceed(request); - }).build(); - } - - - if (certificateKey == null || certificatePfx == null || apiUrl == null) { - c(TAG, "first login, use TOKEN, SYMBOL, PIN"); - - } - - teamList = profileId == -1 ? new ArrayList<>() : app.db.teamDao().getAllNow(profileId); - teacherList = profileId == -1 ? new ArrayList<>() : app.db.teacherDao().getAllNow(profileId); - subjectList = new ArrayList<>(); - lessonList = new ArrayList<>(); - lessonChangeList = new ArrayList<>(); - gradeCategoryList = new ArrayList<>(); - gradeList = new ArrayList<>(); - eventList = new ArrayList<>(); - noticeList = new ArrayList<>(); - attendanceList = new ArrayList<>(); - messageList = new ArrayList<>(); - messageRecipientList = new ArrayList<>(); - messageRecipientIgnoreList = new ArrayList<>(); - metadataList = new ArrayList<>(); - messageMetadataList = new ArrayList<>(); - - return true; - } - - @Override - public void sync(@NonNull Context activityContext, @NonNull SyncCallback callback, int profileId, @Nullable Profile profile, @NonNull LoginStore loginStore) { - if (!prepare(activityContext, callback, profileId, profile, loginStore)) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - targetEndpoints.add("SetPushToken"); - targetEndpoints.add("Dictionaries"); - targetEndpoints.add("Timetable"); - targetEndpoints.add("Grades"); - targetEndpoints.add("ProposedGrades"); - targetEndpoints.add("Events"); - targetEndpoints.add("Homeworks"); - targetEndpoints.add("Notices"); - targetEndpoints.add("Attendances"); - targetEndpoints.add("MessagesInbox"); - targetEndpoints.add("MessagesOutbox"); - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - @Override - public void syncFeature(@NonNull Context activityContext, @NonNull SyncCallback callback, @NonNull ProfileFull profile, int ... featureList) { - if (featureList == null) { - sync(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile)); - return; - } - if (!prepare(activityContext, callback, profile.getId(), profile, LoginStore.fromProfileFull(profile))) - return; - - login(() -> { - targetEndpoints = new ArrayList<>(); - if (featureList.length == 1) - onlyFeature = featureList[0]; - targetEndpoints.add("SetPushToken"); - targetEndpoints.add("Dictionaries"); - for (int feature: featureList) { - switch (feature) { - case FEATURE_TIMETABLE: - targetEndpoints.add("Timetable"); - break; - case FEATURE_AGENDA: - targetEndpoints.add("Events"); - break; - case FEATURE_GRADES: - targetEndpoints.add("Grades"); - targetEndpoints.add("ProposedGrades"); - break; - case FEATURE_HOMEWORKS: - targetEndpoints.add("Homeworks"); - break; - case FEATURE_NOTICES: - targetEndpoints.add("Notices"); - break; - case FEATURE_ATTENDANCES: - targetEndpoints.add("Attendances"); - break; - case FEATURE_MESSAGES_INBOX: - targetEndpoints.add("MessagesInbox"); - break; - case FEATURE_MESSAGES_OUTBOX: - targetEndpoints.add("MessagesOutbox"); - break; - } - } - targetEndpoints.add("Finish"); - PROGRESS_COUNT = targetEndpoints.size()-1; - PROGRESS_STEP = (90/PROGRESS_COUNT); - begin(); - }); - } - - private void begin() { - schoolName = profile.getStudentData("schoolName", null); - schoolSymbol = profile.getStudentData("schoolSymbol", null); - studentId = profile.getStudentData("studentId", -1); - studentLoginId = profile.getStudentData("studentLoginId", -1); - studentClassId = profile.getStudentData("studentClassId", -1); - studentSemesterId = profile.getStudentData("studentSemesterId", -1); - studentSemesterNumber = profile.getStudentData("studentSemesterNumber", profile.getCurrentSemester()); - if (profile.getEmpty() && studentSemesterNumber == 2) { - syncingSemester1 = true; - studentSemesterId -= 1; - studentSemesterNumber -= 1; - } - else { - syncingSemester1 = false; - } - d(TAG, "Syncing student "+studentId+", class "+studentClassId+", semester "+studentSemesterId); - - callback.onProgress(PROGRESS_LOGIN); - - r("get", null); - } - - private void r(String type, String endpoint) { - // endpoint == null when beginning - if (endpoint == null) - endpoint = targetEndpoints.get(0); - int index = -1; - for (String request: targetEndpoints) { - index++; - if (request.equals(endpoint)) { - break; - } - } - if (type.equals("finish")) { - // called when finishing the action - callback.onProgress(PROGRESS_STEP); - index++; - } - d(TAG, "Called r("+type+", "+endpoint+"). Getting "+targetEndpoints.get(index)); - switch (targetEndpoints.get(index)) { - case "SetPushToken": - setPushToken(); - break; - case "Dictionaries": - getDictionaries(); - break; - case "Timetable": - getTimetable(); - break; - case "Grades": - getGrades(); - break; - case "ProposedGrades": - getProposedGrades(); - break; - case "Events": - getEvents(); - break; - case "Homeworks": - getHomeworks(); - break; - case "Notices": - getNotices(); - break; - case "Attendances": - getAttendances(); - break; - case "MessagesInbox": - getMessagesInbox(); - break; - case "MessagesOutbox": - getMessagesOutbox(); - break; - case "Finish": - finish(); - break; - } - } - private void saveData() { - if (teamList.size() > 0) { - app.db.teamDao().addAll(teamList); - } - if (teacherList.size() > 0) - app.db.teacherDao().addAll(teacherList); - if (subjectList.size() > 0) - app.db.subjectDao().addAll(subjectList); - if (lessonList.size() > 0) { - app.db.lessonDao().clear(profileId); - app.db.lessonDao().addAll(lessonList); - } - if (lessonChangeList.size() > 0) - app.db.lessonChangeDao().addAll(lessonChangeList); - if (gradeCategoryList.size() > 0) - app.db.gradeCategoryDao().addAll(gradeCategoryList); - if (gradeList.size() > 0) { - app.db.gradeDao().clearForSemester(profileId, studentSemesterNumber); - app.db.gradeDao().addAll(gradeList); - } - if (eventList.size() > 0) { - app.db.eventDao().removeFuture(profileId, Date.getToday()); - app.db.eventDao().addAll(eventList); - } - if (noticeList.size() > 0) { - app.db.noticeDao().clearForSemester(profileId, studentSemesterNumber); - app.db.noticeDao().addAll(noticeList); - } - if (attendanceList.size() > 0) - app.db.attendanceDao().addAll(attendanceList); - if (messageList.size() > 0) - app.db.messageDao().addAllIgnore(messageList); - if (messageRecipientList.size() > 0) - app.db.messageRecipientDao().addAll(messageRecipientList); - if (messageRecipientIgnoreList.size() > 0) - app.db.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList); - if (metadataList.size() > 0) - app.db.metadataDao().addAllIgnore(metadataList); - if (messageMetadataList.size() > 0) - app.db.metadataDao().setSeen(messageMetadataList); - } - private void finish() { - if (syncingSemester1) { - syncingSemester1 = false; - studentSemesterId += 1; - studentSemesterNumber += 1; - // no need to download dictionaries again - r("get", null);// TODO: 2019-06-04 start with Timetables or first element (if partial sync) - return; - } - try { - saveData(); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 425, CODE_OTHER, app.getString(R.string.sync_error_saving_data), null, null, e, null)); - } - if (fullSync) { - profile.setLastFullSync(System.currentTimeMillis()); - fullSync = false; - } - profile.setEmpty(false); - callback.onSuccess(activityContext, new ProfileFull(profile, loginStore)); - } - private void finishWithError(AppError error) { - try { - saveData(); - } - catch (Exception e) { - Crashlytics.logException(e); - } - callback.onError(activityContext, error); - } - - /* _ _ - | | (_) - | | ___ __ _ _ _ __ - | | / _ \ / _` | | '_ \ - | |___| (_) | (_| | | | | | - |______\___/ \__, |_|_| |_| - __/ | - |__*/ - private void login(@NonNull LoginCallback loginCallback) { - if (profile == null) { - // FIRST LOGIN - this.deviceToken = loginStore.getLoginData("deviceToken", null); - this.deviceSymbol = loginStore.getLoginData("deviceSymbol", null); - this.devicePin = loginStore.getLoginData("devicePin", null); - if (deviceToken == null || deviceSymbol == null || devicePin == null) { - finishWithError(new AppError(TAG, 443, AppError.CODE_INVALID_LOGIN, "Login field is empty")); - return; - } - - setApiUrl(deviceToken, deviceSymbol); - getCertificate(deviceToken, devicePin, ((certificateKey, certificatePfx, apiUrl, userLogin, userName) -> { - loginStore.removeLoginData("deviceToken"); - loginStore.removeLoginData("devicePin"); - loginStore.putLoginData("certificateKey", certificateKey); - loginStore.putLoginData("certificatePfx", certificatePfx); - loginStore.putLoginData("apiUrl", apiUrl); - loginStore.putLoginData("userLogin", userLogin); - this.apiUrl = apiUrl; - this.certificateKey = certificateKey; - this.certificatePfx = certificatePfx; - this.deviceToken = null; - this.devicePin = null; - - c(TAG, "first login. get the list of students"); - getStudentList(usersMap -> { - List profileList = new ArrayList<>(); - for (int index = 0; index < usersMap.size(); index++) { - JsonObject account = new ArrayList<>(usersMap.values()).get(index); - - Profile newProfile = new Profile(); - newProfile.setEmpty(true); - newProfile.setLoggedIn(true); - saveStudentData(newProfile, account); - - profileList.add(newProfile); - } - callback.onLoginFirst(profileList, loginStore); - }, false); - - })); - } - else { - this.apiUrl = loginStore.getLoginData("apiUrl", null); - this.certificateKey = loginStore.getLoginData("certificateKey", null); - this.certificatePfx = loginStore.getLoginData("certificatePfx", null); - if (apiUrl == null || certificateKey == null || certificatePfx == null) { - finishWithError(new AppError(TAG, 489, AppError.CODE_INVALID_LOGIN, "Login field is empty")); - return; - } - if (profile.getStudentData("currentSemesterEndDate", System.currentTimeMillis()/*default always larger*/) < System.currentTimeMillis()/1000 - || (studentLoginId = profile.getStudentData("studentLoginId", -1)) == -1) { - // current semester is over, we need to get student data again - callback.onActionStarted(R.string.sync_action_getting_account); - getStudentList(usersMap -> { - studentId = profile.getStudentData("studentId", -1); - d(TAG, "Searching the student ID "+studentId); - JsonObject foundUser = null; - for (JsonObject user: usersMap.values()) { - if (user.get("Id").getAsInt() == studentId) { - foundUser = user; - } - } - if (foundUser == null || foundUser.get("IdOkresKlasyfikacyjny").getAsInt() == 0) { - d(TAG, "Not found"); - finishWithError(new AppError(TAG, 507, AppError.CODE_OTHER, app.getString(R.string.error_register_student_no_term), usersMap.toString())); - } - else { - d(TAG, "Saving updated student data"); - saveStudentData(profile, foundUser); - loginCallback.onSuccess(); - } - }, true); - } - else { - loginCallback.onSuccess(); - } - } - } - - /* _ _ _ _ _ _ _ - | | | | | | ___ | | | | | | - | |__| | ___| |_ __ ___ _ __ ___ ( _ ) ___ __ _| | | |__ __ _ ___| | _____ - | __ |/ _ \ | '_ \ / _ \ '__/ __| / _ \/\ / __/ _` | | | '_ \ / _` |/ __| |/ / __| - | | | | __/ | |_) | __/ | \__ \ | (_> < | (_| (_| | | | |_) | (_| | (__| <\__ \ - |_| |_|\___|_| .__/ \___|_| |___/ \___/\/ \___\__,_|_|_|_.__/ \__,_|\___|_|\_\___/ - | | - |*/ - private void getCertificate(String token, String pin, CertificateCallback certificateCallback) { - callback.onActionStarted(R.string.sync_action_getting_certificate); - d(TAG, "Post JSON "+apiUrl + ENDPOINT_CERTIFICATE); - if (apiUrl == null) { - finishWithError(new AppError(TAG, 1215, AppError.CODE_INVALID_TOKEN, "Empty apiUrl(1)")); - return; - } - Request.builder() - .url(apiUrl + ENDPOINT_CERTIFICATE) - .userAgent(userAgent) - .addParameter("PIN", pin) - .addParameter("TokenKey", token) - .addParameter("AppVersion", MOBILE_APP_VERSION) - .addParameter("DeviceId", UUID.randomUUID().toString()) - .addParameter("DeviceName", "Szkolny.eu "+Build.MODEL) - .addParameter("DeviceNameUser", "") - .addParameter("DeviceDescription", "") - .addParameter("DeviceSystemType", "Android") - .addParameter("DeviceSystemVersion", Build.VERSION.RELEASE) - .addParameter("RemoteMobileTimeKey", getUnixTime()) - .addParameter("TimeKey", getUnixTime() - 1) - .addParameter("RequestId", UUID.randomUUID().toString()) - .addParameter("RemoteMobileAppVersion", MOBILE_APP_VERSION) - .addParameter("RemoteMobileAppName", MOBILE_APP_NAME) - .postJson() - .addHeader("RequestMobileType", "RegisterDevice") - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 1241, AppError.CODE_MAINTENANCE, response)); - return; - } - d(TAG, "Certificate data "+data.toString()); - boolean isError = data.get("IsError").getAsBoolean(); - if (isError) { - JsonElement message = data.get("Message"); - JsonElement tokenStatus = data.get("TokenStatus"); - String msg = null; - String tokenStatusStr = null; - if (message != null) { - msg = message.getAsString(); - } - if (tokenStatus != null) { - tokenStatusStr = tokenStatus.getAsString(); - } - if ("TokenNotFound".equals(msg)) { - finishWithError(new AppError(TAG, 1258, AppError.CODE_INVALID_TOKEN, response, data)); - return; - } - if ("TokenDead".equals(msg)) { - finishWithError(new AppError(TAG, 1262, AppError.CODE_EXPIRED_TOKEN, response, data)); - return; - } - if ("WrongPIN".equals(tokenStatusStr)) { - Matcher matcher = Pattern.compile("Liczba pozostałych prób: ([0-9])", Pattern.DOTALL).matcher(tokenStatusStr); - finishWithError(new AppError(TAG, 1267, AppError.CODE_INVALID_PIN, matcher.matches() ? matcher.group(1) : "?", response, data)); - return; - } - if ("Broken".equals(tokenStatusStr)) { - finishWithError(new AppError(TAG, 1262, AppError.CODE_INVALID_PIN, "0", response, data)); - return; - } - finishWithError(new AppError(TAG, 1274, AppError.CODE_OTHER, "Sprawdź wprowadzone dane.\n\nBłąd certyfikatu "+(message == null ? "" : message.getAsString()), response, data)); - return; - } - JsonElement tokenCert = data.get("TokenCert"); - if (tokenCert == null || tokenCert instanceof JsonNull) { - finishWithError(new AppError(TAG, 1279, AppError.CODE_OTHER, "Sprawdź wprowadzone dane.\n\nCertificate error. TokenCert null", response, data)); - return; - } - data = tokenCert.getAsJsonObject(); - try { - certificateCallback.onSuccess( - data.get("CertyfikatKlucz").getAsString(), - data.get("CertyfikatPfx").getAsString(), - data.get("AdresBazowyRestApi").getAsString(), - data.get("UzytkownikLogin").getAsString(), - data.get("UzytkownikNazwa").getAsString() - ); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1293, AppError.CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - if (response.code() == 400) { - finishWithError(new AppError(TAG, 1300, AppError.CODE_INVALID_SYMBOL, response, throwable)); - return; - } - finishWithError(new AppError(TAG, 1303, AppError.CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private void apiRequest(String endpoint, JsonObject payload, ApiRequestCallback apiRequestCallback) { - d(TAG, "API request "+apiUrl + endpoint); - if (apiUrl == null) { - finishWithError(new AppError(TAG, 1313, AppError.CODE_OTHER, app.getString(R.string.sync_error_no_api_url), "Empty apiUrl(2)")); - return; - } - if (payload == null) - payload = new JsonObject(); - payload.addProperty("RemoteMobileTimeKey", getUnixTime()); - payload.addProperty("TimeKey", getUnixTime() - 1); - payload.addProperty("RequestId", UUID.randomUUID().toString()); - payload.addProperty("RemoteMobileAppVersion", MOBILE_APP_VERSION); - payload.addProperty("RemoteMobileAppName", MOBILE_APP_NAME); - Request.builder() - .url(apiUrl + endpoint) - .userAgent(userAgent) - .withClient(signingHttp) - .setJsonBody(payload) - .callback(new JsonCallbackHandler() { - @Override - public void onSuccess(JsonObject data, Response response) { - if (data == null) { - finishWithError(new AppError(TAG, 1332, AppError.CODE_MAINTENANCE, response)); - return; - } - try { - apiRequestCallback.onSuccess(data); - } - catch (Exception e) { - finishWithError(new AppError(TAG, 1339, AppError.CODE_OTHER, response, e, data)); - } - } - - @Override - public void onFailure(Response response, Throwable throwable) { - finishWithError(new AppError(TAG, 1345, AppError.CODE_OTHER, response, throwable)); - } - }) - .build() - .enqueue(); - } - - private void getStudentList(StudentListCallback studentListCallback, boolean reportStudentsWithNoSemester) { - apiRequest(ENDPOINT_STUDENT_LIST, null, data -> { - d(TAG, "Got: " + data.toString()); - JsonElement listEl = data.get("Data"); - JsonArray accountList; - if (listEl == null || listEl instanceof JsonNull || (accountList = listEl.getAsJsonArray()).size() == 0) { - finishWithError(new AppError(TAG, 1369, AppError.CODE_OTHER, app.getString(R.string.sync_error_register_no_students), data)); - return; - } - LinkedHashMap usersMap = new LinkedHashMap<>(); - for (JsonElement userEl : accountList) { - JsonObject user = userEl.getAsJsonObject(); - if (reportStudentsWithNoSemester || (user != null && user.get("IdOkresKlasyfikacyjny").getAsInt() != 0)) { - usersMap.put(user.get("Imie").getAsString() + " " + user.get("Nazwisko").getAsString() + " " + (user.get("OddzialKod") instanceof JsonNull ? "" : user.get("OddzialKod").getAsString()), user); - } else if (accountList.size() == 1) { - finishWithError(new AppError(TAG, 1377, AppError.CODE_OTHER, app.getString(R.string.error_register_student_no_term), data)); - return; - } - } - studentListCallback.onSuccess(usersMap); - }); - } - - private void saveStudentData(Profile targetProfile, JsonObject account) { - String firstName = account.get("Imie").getAsString(); - String lastName = account.get("Nazwisko").getAsString(); - schoolName = loginStore.getLoginData("deviceSymbol", getSymbolFromApiUrl())+"_"+account.get("JednostkaSprawozdawczaSymbol").getAsString(); - schoolSymbol = account.get("JednostkaSprawozdawczaSymbol").getAsString(); - studentId = account.get("Id").getAsInt(); - studentLoginId = account.get("UzytkownikLoginId").getAsInt(); - studentClassId = account.get("IdOddzial").getAsInt(); - studentSemesterId = account.get("IdOkresKlasyfikacyjny").getAsInt(); - String studentClassName = account.get("OddzialKod").getAsString(); - targetProfile.putStudentData("userName", account.get("UzytkownikNazwa").getAsString()); - targetProfile.putStudentData("schoolName", schoolName); - targetProfile.putStudentData("schoolSymbol", schoolSymbol); - targetProfile.putStudentData("studentId", studentId); - targetProfile.putStudentData("studentLoginId", studentLoginId); - targetProfile.putStudentData("studentClassId", studentClassId); - targetProfile.putStudentData("studentClassName", studentClassName); - targetProfile.putStudentData("studentSemesterId", studentSemesterId); - targetProfile.putStudentData("currentSemesterEndDate", account.get("OkresDataDo").getAsInt()+86400); - targetProfile.putStudentData("studentSemesterNumber", account.get("OkresNumer").getAsInt()); - targetProfile.setCurrentSemester(account.get("OkresNumer").getAsInt()); - if (targetProfile.getCurrentSemester() == 1) { - targetProfile.setDateSemester1Start(Date.fromMillis((long) account.get("OkresDataOd").getAsInt() * 1000)); - targetProfile.setDateSemester2Start(Date.fromMillis(((long) account.get("OkresDataDo").getAsInt() + 86400) * 1000)); - } - else if (targetProfile.getCurrentSemester() == 2) { - targetProfile.setDateSemester2Start(Date.fromMillis((long) account.get("OkresDataOd").getAsInt() * 1000)); - targetProfile.setDateYearEnd(Date.fromMillis(((long) account.get("OkresDataDo").getAsInt() + 86400) * 1000)); - } - //db.teamDao().add(new Team(profileId, studentClassId, account.get("OddzialKod").getAsString(), 1, -1)); - targetProfile.setStudentNameLong(firstName + " " + lastName); - targetProfile.setStudentNameShort(firstName + " " + lastName.charAt(0) + "."); - if (targetProfile.getName() == null || targetProfile.getName().equals("")) { - targetProfile.setName(targetProfile.getStudentNameLong()); - } - targetProfile.setSubname(account.get("UzytkownikLogin").getAsString()); - } - - private Team searchTeam(String name, String code, long teacherId) { - Team team; - team = Team.getByName(teamList, name); - - if (team == null) { - team = new Team(profileId, crc16(name.getBytes()), name, 2, code, teacherId); - teamList.add(team); - } - else { - team.teacherId = teacherId; - } - return team; - } - - public interface CertificateCallback { - void onSuccess(String certificateKey, String certificatePfx, String apiUrl, String userLogin, String userName); - } - private interface StudentListCallback { - void onSuccess(LinkedHashMap usersMap); - } - private interface ApiRequestCallback { - void onSuccess(JsonObject data); - } - - private String getSymbolFromApiUrl() { - String[] parts = apiUrl.split("/"); - return parts[parts.length - 2]; - } - - private String getClassTeamName() { - if (teamList.size() == 0) - return ""; - if (teamList.get(0).type != 1) - return ""; - return teamList.get(0).name; - } - - private long getClassTeamId() { - if (teamList.size() == 0) - return -1; - if (teamList.get(0).type != 1) - return -1; - return teamList.get(0).id; - } - - private Date getCurrentSemesterStartDate() { - return profile.getSemesterStart(studentSemesterNumber); - } - - private Date getCurrentSemesterEndDate() { - return profile.getSemesterEnd(studentSemesterNumber); - } - - private long getUnixTime() { - return System.currentTimeMillis() / 1000; - } - - private void setApiUrl(String token, String symbol) { - String rule = token.substring(0, 3); - switch (rule) { - case "3S1": - if (!App.devMode) - apiUrl = "https://lekcjaplus.vulcan.net.pl/"+symbol+"/"; - else - apiUrl = "http://hack.szkolny.eu/"+symbol+"/"; - break; - case "TA1": - apiUrl = "https://uonetplus-komunikacja.umt.tarnow.pl/"+symbol+"/"; - break; - case "OP1": - apiUrl = "https://uonetplus-komunikacja.eszkola.opolskie.pl/"+symbol+"/"; - break; - case "RZ1": - apiUrl = "https://uonetplus-komunikacja.resman.pl/"+symbol+"/"; - break; - case "GD1": - apiUrl = "https://uonetplus-komunikacja.edu.gdansk.pl/"+symbol+"/"; - break; - case "KA1": - apiUrl = "https://uonetplus-komunikacja.mcuw.katowice.eu/"+symbol+"/"; - break; - case "KA2": - apiUrl = "https://uonetplus-komunikacja-test.mcuw.katowice.eu/"+symbol+"/"; - break; - case "P03": - apiUrl = "https://efeb-komunikacja-pro-efebmobile.pro.vulcan.pl/"+symbol+"/"; - break; - case "P01": - apiUrl = "http://efeb-komunikacja.pro-hudson.win.vulcan.pl/"+symbol+"/"; - break; - case "P02": - apiUrl = "http://efeb-komunikacja.pro-hudsonrc.win.vulcan.pl/"+symbol+"/"; - break; - case "P90": - apiUrl = "http://efeb-komunikacja-pro-mwujakowska.neo.win.vulcan.pl/"+symbol+"/"; - break; - case "FK1": - apiUrl = "http://api.fakelog.cf/"+symbol+"/"; - break; - case "SZ9": - //apiUrl = "http://vulcan.szkolny.eu/"+symbol+"/"; - break; - default: - apiUrl = null; - break; - } - } - - /* _____ _ _____ _ - | __ \ | | | __ \ | | - | | | | __ _| |_ __ _ | |__) |___ __ _ _ _ ___ ___| |_ ___ - | | | |/ _` | __/ _` | | _ // _ \/ _` | | | |/ _ \/ __| __/ __| - | |__| | (_| | || (_| | | | \ \ __/ (_| | |_| | __/\__ \ |_\__ \ - |_____/ \__,_|\__\__,_| |_| \_\___|\__, |\__,_|\___||___/\__|___/ - | | - |*/ - private void setPushToken() { - String token; - Pair> pair = app.appConfig.fcmTokens.get(LOGIN_TYPE_VULCAN); - if (pair == null || (token = pair.first) == null || (pair.second != null && pair.second.contains(profileId))) { - r("finish", "SetPushToken"); - return; - } - callback.onActionStarted(R.string.sync_action_setting_push_token); - JsonObject json = new JsonObject(); - json.addProperty("IdUczen", studentId); - json.addProperty("Token", token); - json.addProperty("PushOcena", true); - json.addProperty("PushFrekwencja", true); - json.addProperty("PushUwaga", true); - json.addProperty("PushWiadomosc", true); - apiRequest(schoolSymbol+"/"+ENDPOINT_PUSH, json, result -> { - if (pair.second == null) { - List list = new ArrayList<>(); - list.add(profileId); - app.appConfig.fcmTokens.put(LOGIN_TYPE_VULCAN, new Pair<>(token, list)); - } - else { - pair.second.add(profileId); - app.appConfig.fcmTokens.put(LOGIN_TYPE_VULCAN, new Pair<>(token, pair.second)); - } - r("finish", "SetPushToken"); - }); - } - - private SparseArray> lessonRanges = new SparseArray<>(); - private SparseArray noticeCategories = new SparseArray<>(); - private SparseArray> attendanceCategories = new SparseArray<>(); - private void getDictionaries() { - callback.onActionStarted(R.string.sync_action_syncing_dictionaries); - if (teamList.size() == 0 || teamList.get(0).type != 1) { - String name = profile.getStudentData("studentClassName", null); - if (name != null) { - long id = crc16(name.getBytes()); - teamList.add(new Team(profileId, id, name, 1, schoolName + ":" + name, -1)); - } - } - apiRequest(schoolSymbol+"/"+ENDPOINT_DICTIONARIES, null, result -> { - JsonObject data = result.getAsJsonObject("Data"); - JsonArray teachers = data.getAsJsonArray("Nauczyciele"); - JsonArray subjects = data.getAsJsonArray("Przedmioty"); - JsonArray lessonRanges = data.getAsJsonArray("PoryLekcji"); - JsonArray gradeCategories = data.getAsJsonArray("KategorieOcen"); - JsonArray noticeCategories = data.getAsJsonArray("KategorieUwag"); - JsonArray attendanceCategories = data.getAsJsonArray("KategorieFrekwencji"); - - for (JsonElement teacherEl : teachers) { - JsonObject teacher = teacherEl.getAsJsonObject(); - JsonElement loginId = teacher.get("LoginId"); - teacherList.add( - new Teacher( - profileId, - teacher.get("Id").getAsInt(), - teacher.get("Imie").getAsString(), - teacher.get("Nazwisko").getAsString(), - loginId == null || loginId instanceof JsonNull ? "-1" : loginId.getAsString() - ) - ); - } - - for (JsonElement subjectEl : subjects) { - JsonObject subject = subjectEl.getAsJsonObject(); - subjectList.add( - new Subject( - profileId, - subject.get("Id").getAsInt(), - subject.get("Nazwa").getAsString(), - subject.get("Kod").getAsString() - ) - ); - } - - this.lessonRanges.clear(); - for (JsonElement lessonRangeEl : lessonRanges) { - JsonObject lessonRange = lessonRangeEl.getAsJsonObject(); - this.lessonRanges.put( - lessonRange.get("Id").getAsInt(), - new Pair<>( - Time.fromH_m(lessonRange.get("PoczatekTekst").getAsString()), - Time.fromH_m(lessonRange.get("KoniecTekst").getAsString()) - ) - ); - } - - for (JsonElement gradeCategoryEl : gradeCategories) { - JsonObject gradeCategory = gradeCategoryEl.getAsJsonObject(); - gradeCategoryList.add( - new GradeCategory( - profileId, - gradeCategory.get("Id").getAsInt(), - -1, - -1, - gradeCategory.get("Nazwa").getAsString() - ) - ); - } - - this.noticeCategories.clear(); - for (JsonElement noticeCategoryEl : noticeCategories) { - JsonObject noticeCategory = noticeCategoryEl.getAsJsonObject(); - this.noticeCategories.put( - noticeCategory.get("Id").getAsInt(), - noticeCategory.get("Nazwa").getAsString() - ); - } - - this.attendanceCategories.clear(); - for (JsonElement attendanceCategoryEl : attendanceCategories) { - JsonObject attendanceCategory = attendanceCategoryEl.getAsJsonObject(); - int type = -1; - boolean absent = attendanceCategory.get("Nieobecnosc").getAsBoolean(); - boolean excused = attendanceCategory.get("Usprawiedliwione").getAsBoolean(); - if (absent) { - type = excused ? TYPE_ABSENT_EXCUSED : TYPE_ABSENT; - } - else { - if (attendanceCategory.get("Spoznienie").getAsBoolean()) { - type = excused ? TYPE_BELATED_EXCUSED : TYPE_BELATED; - } - else if (attendanceCategory.get("Zwolnienie").getAsBoolean()) { - type = TYPE_RELEASED; - } - else if (attendanceCategory.get("Obecnosc").getAsBoolean()) { - type = TYPE_PRESENT; - } - } - this.attendanceCategories.put( - attendanceCategory.get("Id").getAsInt(), - new Pair<>( - type, - attendanceCategory.get("Nazwa").getAsString() - ) - ); - } - r("finish", "Dictionaries"); - }); - } - - private void getTimetable() { - if (syncingSemester1) { - // we are in semester 2. we're syncing semester 1 to show old data. skip the timetable. - r("finish", "Timetable"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_timetable); - JsonObject json = new JsonObject(); - json.addProperty("IdUczen", studentId); - - Date weekStart = Week.getWeekStart(); - if (Date.getToday().getWeekDay() > 4) { - weekStart.stepForward(0, 0, 7); - } - Date weekEnd = weekStart.clone().stepForward(0, 0, 6); - - json.addProperty("DataPoczatkowa", weekStart.getStringY_m_d()); - json.addProperty("DataKoncowa", weekEnd.getStringY_m_d()); - json.addProperty("IdOddzial", studentClassId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_TIMETABLE, json, result -> { - JsonArray lessons = result.getAsJsonArray("Data"); - - for (JsonElement lessonEl: lessons) { - JsonObject lesson = lessonEl.getAsJsonObject(); - - if (!lesson.get("PlanUcznia").getAsBoolean()) { - continue; - } - - Date lessonDate = Date.fromY_m_d(lesson.get("DzienTekst").getAsString()); - Pair lessonRange = lessonRanges.get(lesson.get("IdPoraLekcji").getAsInt()); - Lesson lessonObject = new Lesson( - profileId, - lessonDate.getWeekDay(), - lessonRange.first, - lessonRange.second - ); - - JsonElement subject = lesson.get("IdPrzedmiot"); - if (!(subject instanceof JsonNull)) { - lessonObject.subjectId = subject.getAsInt(); - } - JsonElement teacher = lesson.get("IdPracownik"); - if (!(teacher instanceof JsonNull)) { - lessonObject.teacherId = teacher.getAsInt(); - } - - lessonObject.teamId = getClassTeamId(); - JsonElement team = lesson.get("PodzialSkrot"); - if (team != null && !(team instanceof JsonNull)) { - String name = getClassTeamName()+" "+team.getAsString(); - Team teamObject = searchTeam(name, schoolName+":"+name, lessonObject.teacherId); - lessonObject.teamId = teamObject.id; - } - JsonElement classroom = lesson.get("Sala"); - if (classroom != null && !(classroom instanceof JsonNull)) { - lessonObject.classroomName = classroom.getAsString(); - } - - JsonElement isCancelled = lesson.get("PrzekreslonaNazwa"); - JsonElement oldTeacherId = lesson.get("IdPracownikOld"); - if (isCancelled != null && isCancelled.getAsBoolean()) { - LessonChange lessonChangeObject = new LessonChange( - profileId, - lessonDate, lessonObject.startTime, lessonObject.endTime - ); - lessonChangeObject.type = TYPE_CANCELLED; - lessonChangeObject.teacherId = lessonObject.teacherId; - lessonChangeObject.subjectId = lessonObject.subjectId; - lessonChangeObject.teamId = lessonObject.teamId; - lessonChangeObject.classroomName = lessonObject.classroomName; - - lessonChangeList.add(lessonChangeObject); - metadataList.add(new Metadata(profileId,Metadata.TYPE_LESSON_CHANGE, lessonChangeObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - else if (!(oldTeacherId instanceof JsonNull)) { - LessonChange lessonChangeObject = new LessonChange( - profileId, - lessonDate, lessonObject.startTime, lessonObject.endTime - ); - lessonChangeObject.type = TYPE_CHANGE; - lessonChangeObject.teacherId = lessonObject.teacherId; - lessonChangeObject.subjectId = lessonObject.subjectId; - lessonChangeObject.teamId = lessonObject.teamId; - lessonChangeObject.classroomName = lessonObject.classroomName; - lessonObject.teacherId = oldTeacherId.getAsInt(); - - lessonChangeList.add(lessonChangeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_LESSON_CHANGE, lessonChangeObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - lessonList.add(lessonObject); - } - r("finish", "Timetable"); - }); - } - - private void getGrades() { - callback.onActionStarted(R.string.sync_action_syncing_grades); - JsonObject json = new JsonObject(); - json.addProperty("IdUczen", studentId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_GRADES, json, result -> { - JsonArray grades = result.getAsJsonArray("Data"); - - for (JsonElement gradeEl: grades) { - JsonObject grade = gradeEl.getAsJsonObject(); - JsonElement name = grade.get("Wpis"); - JsonElement description = grade.get("Opis"); - JsonElement comment = grade.get("Komentarz"); - JsonElement value = grade.get("Wartosc"); - JsonElement modificatorValue = grade.get("WagaModyfikatora"); - JsonElement numerator = grade.get("Licznik"); - JsonElement denominator = grade.get("Mianownik"); - - int id = grade.get("Id").getAsInt(); - int weight = grade.get("WagaOceny").getAsInt(); - int subjectId = grade.get("IdPrzedmiot").getAsInt(); - int teacherId = grade.get("IdPracownikD").getAsInt(); - int categoryId = grade.get("IdKategoria").getAsInt(); - long addedDate = Date.fromY_m_d(grade.get("DataModyfikacjiTekst").getAsString()).getInMillis(); - - float finalValue = 0.0f; - String finalName; - String finalDescription = ""; - - if (!(numerator instanceof JsonNull) && !(denominator instanceof JsonNull)) { - // calculate the grade's value and name: divide - float numeratorF = numerator.getAsFloat(); - float denominatorF = denominator.getAsFloat(); - finalValue = numeratorF / denominatorF; - weight = 0; - finalName = intToStr(Math.round(finalValue*100))+"%"; - finalDescription += new DecimalFormat("#.##").format(numeratorF)+"/"+new DecimalFormat("#.##").format(denominatorF); - } - else { - // "normal" grade. Get the name and value if set. Otherwise zero the weight. - finalName = name.getAsString(); - if (!(value instanceof JsonNull)) { - finalValue = value.getAsFloat(); - if (!(modificatorValue instanceof JsonNull)) { - finalValue += modificatorValue.getAsFloat(); - } - } - else { - weight = 0; - } - } - - if (!(comment instanceof JsonNull)) { - if (finalName.equals("")) { - finalName = comment.getAsString(); - } - else { - finalDescription += (finalDescription.equals("") ? "" : " ")+comment.getAsString(); - } - } - if (!(description instanceof JsonNull)) { - finalDescription += (finalDescription.equals("") ? "" : " - ")+description.getAsString(); - } - - int semester = studentSemesterNumber; - /*Date addedDateObj = Date.fromMillis(addedDate); - if (app.register.dateSemester2Start != null && addedDateObj.compareTo(app.register.dateSemester2Start) > 0) { - semester = 2; - }*/ - - String category = ""; - for (GradeCategory gradeCategory: gradeCategoryList) { - if (gradeCategory.categoryId == categoryId) { - category = gradeCategory.text; - } - } - - int color = 0xff3D5F9C; - switch (finalName) { - case "1-": - case "1": - case "1+": - color = 0xffd65757; - break; - case "2-": - case "2": - case "2+": - color = 0xff9071b3; - break; - case "3-": - case "3": - case "3+": - color = 0xffd2ab24; - break; - case "4-": - case "4": - case "4+": - color = 0xff50b6d6; - break; - case "5-": - case "5": - case "5+": - color = 0xff2cbd92; - break; - case "6-": - case "6": - case "6+": - color = 0xff91b43c; - break; - } - - Grade gradeObject = new Grade( - profileId, - id, - finalDescription, - color, - category, - finalName, - finalValue, - weight, - semester, - teacherId, - subjectId - ); - - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "Grades"); - }); - } - - private void processGradeList(JsonArray jsonGrades, List gradeList, List metadataList, boolean isFinal) { - for (JsonElement gradeEl: jsonGrades) { - JsonObject grade = gradeEl.getAsJsonObject(); - - String name = grade.get("Wpis").getAsString(); - float value = getGradeValue(name); - long subjectId = grade.get("IdPrzedmiot").getAsLong(); - - long id = subjectId * -100 - studentSemesterNumber; - - int color = getVulcanGradeColor(name); - - Grade gradeObject = new Grade( - profileId, - id, - "", - color, - "", - name, - value, - 0, - studentSemesterNumber, - -1, - subjectId - ); - if (studentSemesterNumber == 1) { - gradeObject.type = isFinal ? TYPE_SEMESTER1_FINAL : TYPE_SEMESTER1_PROPOSED; - } - else { - gradeObject.type = isFinal ? TYPE_SEMESTER2_FINAL : TYPE_SEMESTER2_PROPOSED; - } - gradeList.add(gradeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_GRADE, gradeObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - } - private void getProposedGrades() { - callback.onActionStarted(R.string.sync_action_syncing_proposition_grades); - JsonObject json = new JsonObject(); - json.addProperty("IdUczen", studentId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_GRADES_PROPOSITIONS, json, result -> { - JsonObject grades = result.getAsJsonObject("Data"); - JsonArray gradesProposed = grades.getAsJsonArray("OcenyPrzewidywane"); - JsonArray gradesFinal = grades.getAsJsonArray("OcenyKlasyfikacyjne"); - - processGradeList(gradesProposed, gradeList, metadataList, false); - processGradeList(gradesFinal, gradeList, metadataList, true); - r("finish", "ProposedGrades"); - }); - } - - private void getEvents() { - callback.onActionStarted(R.string.sync_action_syncing_exams); - JsonObject json = new JsonObject(); - // from today to the end of the current semester - // or if empty: from the beginning of the semester - json.addProperty("DataPoczatkowa", profile.getEmpty() ? getCurrentSemesterStartDate().getStringY_m_d() : oneMonthBack.getStringY_m_d()); - json.addProperty("DataKoncowa", getCurrentSemesterEndDate().getStringY_m_d()); - json.addProperty("IdOddzial", studentClassId); - json.addProperty("IdUczen", studentId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_EVENTS, json, result -> { - JsonArray events = result.getAsJsonArray("Data"); - - for (JsonElement eventEl: events) { - JsonObject event = eventEl.getAsJsonObject(); - - int id = event.get("Id").getAsInt(); - - int eventType = event.get("Rodzaj").getAsBoolean() ? TYPE_EXAM : TYPE_SHORT_QUIZ; - - int subjectId = event.get("IdPrzedmiot").getAsInt(); - - Date lessonDate = Date.fromY_m_d(event.get("DataTekst").getAsString()); - Time startTime = null; - - int weekDay = lessonDate.getWeekDay(); - for (Lesson lesson: lessonList) { - if (lesson.weekDay == weekDay && lesson.subjectId == subjectId) { - startTime = lesson.startTime; - } - } - - long teacherId = event.get("IdPracownik").getAsInt(); - - Event eventObject = new Event( - profileId, - id, - lessonDate, - startTime, - event.get("Opis").getAsString(), - -1, - eventType, - false, - teacherId, - subjectId, - getClassTeamId() - ); - - JsonElement team = event.get("PodzialSkrot"); - if (team != null && !(team instanceof JsonNull)) { - String name = getClassTeamName()+" "+team.getAsString(); - Team teamObject = searchTeam(name, schoolName+":"+name, teacherId); - eventObject.teamId = teamObject.id; - } - - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_EVENT, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - r("finish", "Events"); - }); - } - - private void getHomeworks() { - callback.onActionStarted(R.string.sync_action_syncing_homework); - JsonObject json = new JsonObject(); - json.addProperty("DataPoczatkowa", profile.getEmpty() ? getCurrentSemesterStartDate().getStringY_m_d() : oneMonthBack.getStringY_m_d()); - json.addProperty("DataKoncowa", getCurrentSemesterEndDate().getStringY_m_d()); - json.addProperty("IdOddzial", studentClassId); - json.addProperty("IdUczen", studentId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_HOMEWORKS, json, result -> { - JsonArray homeworks = result.getAsJsonArray("Data"); - - for (JsonElement homeworkEl: homeworks) { - JsonObject homework = homeworkEl.getAsJsonObject(); - - int id = homework.get("Id").getAsInt(); - - int subjectId = homework.get("IdPrzedmiot").getAsInt(); - - Date lessonDate = Date.fromY_m_d(homework.get("DataTekst").getAsString()); - Time startTime = null; - - int weekDay = lessonDate.getWeekDay(); - for (Lesson lesson: lessonList) { - if (lesson.weekDay == weekDay && lesson.subjectId == subjectId) { - startTime = lesson.startTime; - } - } - - Event eventObject = new Event( - profileId, - id, - lessonDate, - startTime, - homework.get("Opis").getAsString(), - -1, - Event.TYPE_HOMEWORK, - false, - homework.get("IdPracownik").getAsInt(), - subjectId, - getClassTeamId() - ); - - eventList.add(eventObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_HOMEWORK, eventObject.id, profile.getEmpty(), profile.getEmpty(), System.currentTimeMillis())); - } - r("finish", "Homeworks"); - }); - } - - private void getNotices() { - callback.onActionStarted(R.string.sync_action_syncing_notices); - JsonObject json = new JsonObject(); - json.addProperty("IdUczen", studentId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_NOTICES, json, result -> { - JsonArray notices = result.getAsJsonArray("Data"); - - for (JsonElement noticeEl: notices) { - JsonObject notice = noticeEl.getAsJsonObject(); - - int id = notice.get("Id").getAsInt(); - long addedDate = Date.fromY_m_d(notice.get("DataWpisuTekst").getAsString()).getInMillis(); - - int semester = studentSemesterNumber; - /*Date addedDateObj = Date.fromMillis(addedDate); - if (profile.dateSemester2Start != null && addedDateObj.compareTo(profile.dateSemester2Start) > 0) { - semester = 2; - }*/ - - Notice noticeObject = new Notice( - profileId, - id, - (notice.get("IdKategoriaUwag") instanceof JsonNull ? "" : noticeCategories.get(notice.get("IdKategoriaUwag").getAsInt())) +"\n"+notice.get("TrescUwagi").getAsString(), - semester, - TYPE_NEUTRAL, - notice.get("IdPracownik").getAsInt() - ); - - noticeList.add(noticeObject); - metadataList.add(new Metadata(profileId, Metadata.TYPE_NOTICE, noticeObject.id, profile.getEmpty(), profile.getEmpty(), addedDate)); - } - r("finish", "Notices"); - }); - } - - private void getAttendances() { - callback.onActionStarted(R.string.sync_action_syncing_attendances); - JsonObject json = new JsonObject(); - json.addProperty("DataPoczatkowa", profile.getEmpty() ? getCurrentSemesterStartDate().getStringY_m_d() : oneMonthBack.getStringY_m_d()); - json.addProperty("DataKoncowa", getCurrentSemesterEndDate().getStringY_m_d()); - json.addProperty("IdOddzial", studentClassId); - json.addProperty("IdUczen", studentId); - json.addProperty("IdOkresKlasyfikacyjny", studentSemesterId); - apiRequest(schoolSymbol+"/"+ENDPOINT_ATTENDANCES, json, result -> { - JsonArray attendances = result.getAsJsonObject("Data").getAsJsonArray("Frekwencje"); - - for (JsonElement attendanceEl: attendances) { - JsonObject attendance = attendanceEl.getAsJsonObject(); - - Pair attendanceCategory = attendanceCategories.get(attendance.get("IdKategoria").getAsInt()); - if (attendanceCategory == null) - continue; - - int type = attendanceCategory.first; - - int id = attendance.get("Dzien").getAsInt() + attendance.get("Numer").getAsInt(); - - long lessonDateMillis = Date.fromY_m_d(attendance.get("DzienTekst").getAsString()).getInMillis(); - Date lessonDate = Date.fromMillis(lessonDateMillis); - - int lessonSemester = profile.dateToSemester(lessonDate); - - Attendance attendanceObject = new Attendance( - profileId, - id, - 0, - attendance.get("IdPrzedmiot").getAsInt(), - lessonSemester, - attendance.get("PrzedmiotNazwa").getAsString()+" - "+attendanceCategory.second, - lessonDate, - lessonRanges.get(attendance.get("IdPoraLekcji").getAsInt()).first, - type); - - attendanceList.add(attendanceObject); - if (attendanceObject.type != TYPE_PRESENT) { - metadataList.add(new Metadata(profileId, Metadata.TYPE_ATTENDANCE, attendanceObject.id, profile.getEmpty(), profile.getEmpty(), attendanceObject.lessonDate.combineWith(attendanceObject.startTime))); - } - } - r("finish", "Attendances"); - }); - } - - private void getMessagesInbox() { - callback.onActionStarted(R.string.sync_action_syncing_messages_inbox); - JsonObject json = new JsonObject(); - json.addProperty("DataPoczatkowa", profile.getEmpty() ? getCurrentSemesterStartDate().getInUnix() : oneMonthBack.getInUnix()); - json.addProperty("DataKoncowa", Date.getToday().getInUnix()); - json.addProperty("LoginId", studentLoginId); - json.addProperty("IdUczen", studentId); - apiRequest(schoolSymbol+"/"+ENDPOINT_MESSAGES_RECEIVED, json, result -> { - JsonArray messages = result.getAsJsonArray("Data"); - - for (JsonElement messageEl: messages) { - JsonObject message = messageEl.getAsJsonObject(); - - long id = message.get("WiadomoscId").getAsLong(); - - long senderId = -1; - int senderLoginId = message.get("NadawcaId").getAsInt(); - String senderLoginIdStr = String.valueOf(senderLoginId); - - for (Teacher teacher: teacherList) { - if (senderLoginIdStr.equals(teacher.loginId)) { - senderId = teacher.id; - } - } - - String subject = message.get("Tytul").getAsString(); - String body = message.get("Tresc").getAsString(); - body = body.replaceAll("\n", "
"); - - long addedDate = message.get("DataWyslaniaUnixEpoch").getAsLong() * 1000; - long readDate = 0; - JsonElement readDateUnix; - if (!((readDateUnix = message.get("DataPrzeczytaniaUnixEpoch")) instanceof JsonNull)) { - readDate = readDateUnix.getAsLong() * 1000L; - } - - Message messageObject = new Message(profileId, id, subject, body, TYPE_RECEIVED, senderId, -1); - MessageRecipient messageRecipientObject = new MessageRecipient(profileId, -1, -1, readDate, id); - - messageList.add(messageObject); - messageRecipientList.add(messageRecipientObject); - messageMetadataList.add(new Metadata(profileId, TYPE_MESSAGE, id, readDate > 0, readDate > 0 || profile.getEmpty(), addedDate)); - } - r("finish", "MessagesInbox"); - }); - } - - private void getMessagesOutbox() { - if (!fullSync && onlyFeature != FEATURE_MESSAGES_OUTBOX) { - r("finish", "MessagesOutbox"); - return; - } - callback.onActionStarted(R.string.sync_action_syncing_messages_outbox); - JsonObject json = new JsonObject(); - json.addProperty("DataPoczatkowa", profile.getEmpty() ? getCurrentSemesterStartDate().getInUnix() : oneMonthBack.getInUnix()); - json.addProperty("DataKoncowa", Date.getToday().getInUnix()); - json.addProperty("LoginId", studentLoginId); - json.addProperty("IdUczen", studentId); - apiRequest(schoolSymbol+"/"+ENDPOINT_MESSAGES_SENT, json, result -> { - JsonArray messages = result.getAsJsonArray("Data"); - - for (JsonElement jMessageEl: messages) { - JsonObject jMessage = jMessageEl.getAsJsonObject(); - - long messageId = jMessage.get("WiadomoscId").getAsLong(); - - String subject = jMessage.get("Tytul").getAsString(); - - String body = jMessage.get("Tresc").getAsString(); - body = body.replaceAll("\n", "
"); - - long sentDate = jMessage.get("DataWyslaniaUnixEpoch").getAsLong() * 1000; - - Message message = new Message( - profileId, - messageId, - subject, - body, - TYPE_SENT, - -1, - -1 - ); - - int readBy = jMessage.get("Przeczytane").getAsInt(); - int unreadBy = jMessage.get("Nieprzeczytane").getAsInt(); - for (JsonElement recipientEl: jMessage.getAsJsonArray("Adresaci")) { - JsonObject recipient = recipientEl.getAsJsonObject(); - long recipientId = -1; - int recipientLoginId = recipient.get("LoginId").getAsInt(); - String recipientLoginIdStr = String.valueOf(recipientLoginId); - for (Teacher teacher: teacherList) { - if (recipientLoginIdStr.equals(teacher.loginId)) { - recipientId = teacher.id; - } - } - MessageRecipient messageRecipient = new MessageRecipient( - profileId, - recipientId, - -1, - readBy == 0 ? 0 : unreadBy == 0 ? 1 : -1, - /*messageId*/ messageId - ); - messageRecipientIgnoreList.add(messageRecipient); - } - - messageList.add(message); - metadataList.add(new Metadata(profileId, Metadata.TYPE_MESSAGE, messageId, true, true, sentDate)); - } - r("finish", "MessagesOutbox"); - }); - } - - /* __ __ - | \/ | - | \ / | ___ ___ ___ __ _ __ _ ___ ___ - | |\/| |/ _ \/ __/ __|/ _` |/ _` |/ _ \/ __| - | | | | __/\__ \__ \ (_| | (_| | __/\__ \ - |_| |_|\___||___/___/\__,_|\__, |\___||___/ - __/ | - |__*/ - @Override - public void getMessage(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, @NonNull MessageGetCallback messageCallback) { - if (message.body != null) { - message.recipients = app.db.messageRecipientDao().getAllByMessageId(profile.getId(), message.id); - for (MessageRecipientFull recipient: message.recipients) { - if (recipient.id == -1) - recipient.fullName = profile.getStudentNameLong(); - } - if (!message.seen) { - app.db.metadataDao().setSeen(profile.getId(), message, true); - if (message.type != TYPE_SENT) { - app.db.messageRecipientDao().add(new MessageRecipient(profile.getId(), -1, -1, System.currentTimeMillis(), message.id)); - } - } - new Handler(activityContext.getMainLooper()).post(() -> { - messageCallback.onSuccess(message); - }); - return; - } - } - - @Override - public void getAttachment(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull MessageFull message, long attachmentId, @NonNull AttachmentGetCallback attachmentCallback) { - - } - - @Override - public void getRecipientList(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile, @NonNull RecipientListGetCallback recipientListGetCallback) { - - } - - @Override - public MessagesComposeInfo getComposeInfo(@NonNull ProfileFull profile) { - return new MessagesComposeInfo(0, 0, -1, -1); - } - - @Override - public void syncMessages(@NonNull Context activityContext, @NonNull SyncCallback errorCallback, @NonNull ProfileFull profile) { - - } - - @Override - public Map getConfigurableEndpoints(Profile profile) { - return null; - } - - @Override - public boolean isEndpointEnabled(Profile profile, boolean defaultActive, String name) { - return defaultActive; - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/AttachmentGetCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/AttachmentGetCallback.java deleted file mode 100644 index 1a24ee68..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/AttachmentGetCallback.java +++ /dev/null @@ -1,11 +0,0 @@ -package pl.szczodrzynski.edziennik.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); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/EdziennikInterface.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/EdziennikInterface.java deleted file mode 100644 index d7bcde5f..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/EdziennikInterface.java +++ /dev/null @@ -1,92 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Map; - -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.MessageFull; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.ProfileFull; -import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo; -import pl.szczodrzynski.edziennik.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_HOMEWORKS = 4; - int FEATURE_NOTICES = 5; - int FEATURE_ATTENDANCES = 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 pl.szczodrzynski.edziennik.datamodels.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 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); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/ErrorCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/ErrorCallback.java deleted file mode 100644 index 4f84f640..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/ErrorCallback.java +++ /dev/null @@ -1,11 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import pl.szczodrzynski.edziennik.api.AppError; - -public interface ErrorCallback { - void onError(Context activityContext, @NonNull AppError error); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/LoginCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/LoginCallback.java deleted file mode 100644 index 4720eee4..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/LoginCallback.java +++ /dev/null @@ -1,5 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -public interface LoginCallback { - void onSuccess(); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/MessageGetCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/MessageGetCallback.java deleted file mode 100644 index 2fca992c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/MessageGetCallback.java +++ /dev/null @@ -1,12 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import pl.szczodrzynski.edziennik.datamodels.Message; -import pl.szczodrzynski.edziennik.datamodels.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); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/MessageListCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/MessageListCallback.java deleted file mode 100644 index 77137a0e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/MessageListCallback.java +++ /dev/null @@ -1,9 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import java.util.List; - -import pl.szczodrzynski.edziennik.datamodels.MessageFull; - -public interface MessageListCallback { - void onSuccess(List messageList); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/ProgressCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/ProgressCallback.java deleted file mode 100644 index 07a88987..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/ProgressCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import androidx.annotation.StringRes; - -public interface ProgressCallback extends ErrorCallback { - void onProgress(int progressStep); - void onActionStarted(@StringRes int stringResId); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/RecipientListGetCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/RecipientListGetCallback.java deleted file mode 100644 index dce9ec9d..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/RecipientListGetCallback.java +++ /dev/null @@ -1,9 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import java.util.List; - -import pl.szczodrzynski.edziennik.datamodels.Teacher; - -public interface RecipientListGetCallback { - void onSuccess(List teacherList); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/SyncCallback.java b/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/SyncCallback.java deleted file mode 100644 index 54329973..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/interfaces/SyncCallback.java +++ /dev/null @@ -1,22 +0,0 @@ -package pl.szczodrzynski.edziennik.api.interfaces; - -import android.content.Context; - -import java.util.List; - -import androidx.annotation.StringRes; - -import pl.szczodrzynski.edziennik.api.AppError; -import pl.szczodrzynski.edziennik.datamodels.LoginStore; -import pl.szczodrzynski.edziennik.datamodels.Profile; -import pl.szczodrzynski.edziennik.datamodels.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 profileList, LoginStore loginStore); - void onSuccess(Context activityContext, ProfileFull profileFull); - -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiLoginResult.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiLoginResult.kt deleted file mode 100644 index 30b0edad..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/ApiLoginResult.kt +++ /dev/null @@ -1,6 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2 - -import pl.szczodrzynski.edziennik.api.AppError -import pl.szczodrzynski.edziennik.datamodels.LoginStore - -data class ApiLoginResult(val loginStore: LoginStore, val error: AppError?) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt deleted file mode 100644 index 3aae0da1..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/Constants.kt +++ /dev/null @@ -1,32 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2 - -const val FEATURE_ALL = 0 -const val FEATURE_TIMETABLE = 1 -const val FEATURE_AGENDA = 2 -const val FEATURE_GRADES = 3 -const val FEATURE_HOMEWORKS = 4 -const val FEATURE_NOTICES = 5 -const val FEATURE_ATTENDANCES = 6 -const val FEATURE_MESSAGES_INBOX = 7 -const val FEATURE_MESSAGES_OUTBOX = 8 -const val FEATURE_ANNOUNCEMENTS = 9 - -const val LOGIN_TYPE_MOBIDZIENNIK = 1 -const val LOGIN_TYPE_LIBRUS = 2 -const val LOGIN_TYPE_IUCZNIOWIE = 3 -const val LOGIN_TYPE_VULCAN = 4 -const val LOGIN_TYPE_DEMO = 20 - -const val LOGIN_MODE_LIBRUS_EMAIL = 0 -const val LOGIN_MODE_LIBRUS_SYNERGIA = 1 -const val LOGIN_MODE_LIBRUS_JST = 2 - -const val LIBRUS_USER_AGENT = "Dalvik/2.1.0 Android LibrusMobileApp" -const val LIBRUS_CLIENT_ID = "wmSyUMo8llDAs4y9tJVYY92oyZ6h4lAt7KCuy0Gv" -const val LIBRUS_REDIRECT_URL = "http://localhost/bar" -const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code" -const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action" -const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token" - -const val LIBRUS_ACCOUNT_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts/fresh/" // + login -const val LIBRUS_ACCOUNTS_URL = "https://portal.librus.pl/api/v2/SynergiaAccounts" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/FirstLoginResult.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/FirstLoginResult.kt deleted file mode 100644 index 92d3da9e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/FirstLoginResult.kt +++ /dev/null @@ -1,6 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2 - -import pl.szczodrzynski.edziennik.api.AppError -import pl.szczodrzynski.edziennik.datamodels.Profile - -data class FirstLoginResult(val profileList: ArrayList, val error: AppError?) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt deleted file mode 100644 index 8ba1f79e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/Librus.kt +++ /dev/null @@ -1,201 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus - -import android.content.Context -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.AppError -import pl.szczodrzynski.edziennik.api.AppError.* -import pl.szczodrzynski.edziennik.api.interfaces.* -import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_EMAIL -import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_JST -import pl.szczodrzynski.edziennik.api.v2.LOGIN_MODE_LIBRUS_SYNERGIA -import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.firstlogin.FirstLoginSynergia -import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginJst -import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginLibrus -import pl.szczodrzynski.edziennik.api.v2.librus.login.LoginSynergia -import pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor -import pl.szczodrzynski.edziennik.api.v2.models.DataStore -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.datamodels.MessageFull -import pl.szczodrzynski.edziennik.datamodels.Profile -import pl.szczodrzynski.edziennik.datamodels.ProfileFull -import pl.szczodrzynski.edziennik.messages.MessagesComposeInfo -import pl.szczodrzynski.edziennik.models.Endpoint -import pl.szczodrzynski.edziennik.utils.Utils.d -import java.lang.Exception - -class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore) : EdziennikInterface { - private val TAG = "librus.Librus" - - lateinit var syncCallback: SyncCallback - lateinit var featureList: ArrayList - lateinit var dataStore: DataStore - var onLogin: (() -> Unit)? = null - val internalErrorList = ArrayList() - - fun isError(error: AppError?): Boolean { - if (error == null) - return false - syncCallback.onError(null, error) - return true - } - - /* _ _ _ - | | (_) | - | | _| |__ _ __ _ _ ___ - | | | | '_ \| '__| | | / __| - | |____| | |_) | | | |_| \__ \ - |______|_|_.__/|_| \__,_|__*/ - private fun loginLibrus() { - LoginLibrus(app, loginStore, syncCallback) { - if (profile == null) { - firstLoginLibrus() - return@LoginLibrus - } - synergiaTokenExtractor() - } - } - private fun firstLoginLibrus() { - FirstLoginLibrus(app, loginStore, syncCallback) { profileList -> - syncCallback.onLoginFirst(profileList, loginStore) - } - } - private fun synergiaTokenExtractor() { - if (profile == null) { - throw Exception("Profile may not be null") - } - SynergiaTokenExtractor(app, profile, loginStore, syncCallback) { - d(TAG, "Profile $profile") - d(TAG, "LoginStore $loginStore") - onLogin?.invoke() - } - } - /* _____ _ - / ____| (_) - | (___ _ _ _ __ ___ _ __ __ _ _ __ _ - \___ \| | | | '_ \ / _ \ '__/ _` | |/ _` | - ____) | |_| | | | | __/ | | (_| | | (_| | - |_____/ \__, |_| |_|\___|_| \__, |_|\__,_| - __/ | __/ | - |___/ |__*/ - private fun loginSynergia() { - LoginSynergia(app, loginStore, syncCallback) { - if (profile == null) { - firstLoginSynergia() - return@LoginSynergia - } - onLogin?.invoke() - } - } - private fun firstLoginSynergia() { - FirstLoginSynergia(app, loginStore, syncCallback) { profileList -> - syncCallback.onLoginFirst(profileList, loginStore) - } - } - /* _ _____ _______ - | |/ ____|__ __| - | | (___ | | - _ | |\___ \ | | - | |__| |____) | | | - \____/|_____/ |*/ - private fun loginJst() { - LoginJst(app, loginStore, syncCallback) { - if (profile == null) { - firstLoginSynergia() - return@LoginJst - } - onLogin?.invoke() - } - } - - private fun wrapCallback(callback: SyncCallback): SyncCallback { - return object : SyncCallback { - override fun onSuccess(activityContext: Context?, profileFull: ProfileFull?) { - callback.onSuccess(activityContext, profileFull) - } - - override fun onProgress(progressStep: Int) { - callback.onProgress(progressStep) - } - - override fun onActionStarted(stringResId: Int) { - callback.onActionStarted(stringResId) - } - - override fun onLoginFirst(profileList: MutableList?, loginStore: LoginStore?) { - callback.onLoginFirst(profileList, loginStore) - } - - override fun onError(activityContext: Context?, error: AppError) { - when (error.errorCode) { - in internalErrorList -> { - // finish immediately if the same error occurs twice during the same sync - callback.onError(activityContext, error) - } - CODE_INTERNAL_LIBRUS_ACCOUNT_410 -> { - internalErrorList.add(error.errorCode) - loginStore.removeLoginData("refreshToken") // force a clean login - loginLibrus() - } - else -> callback.onError(activityContext, error) - } - } - } - } - - fun login(callback: SyncCallback) { - this.internalErrorList.clear() - this.syncCallback = wrapCallback(callback) - when (loginStore.mode) { - LOGIN_MODE_LIBRUS_EMAIL -> { - loginLibrus() - } - LOGIN_MODE_LIBRUS_SYNERGIA -> { - - } - LOGIN_MODE_LIBRUS_JST -> { - - } - } - } - - fun getData() { - - } - - override fun sync(activityContext: Context, callback: SyncCallback, profileId: Int, profile: Profile?, loginStore: LoginStore) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun syncMessages(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun syncFeature(activityContext: Context, callback: SyncCallback, profile: ProfileFull, vararg featureList: Int) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getMessage(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, messageCallback: MessageGetCallback) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getAttachment(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, message: MessageFull, attachmentId: Long, attachmentCallback: AttachmentGetCallback) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getRecipientList(activityContext: Context, errorCallback: SyncCallback, profile: ProfileFull, recipientListGetCallback: RecipientListGetCallback) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getComposeInfo(profile: ProfileFull): MessagesComposeInfo { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun getConfigurableEndpoints(profile: Profile?): MutableMap { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - override fun isEndpointEnabled(profile: Profile?, defaultActive: Boolean, name: String?): Boolean { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/DataLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/DataLibrus.kt deleted file mode 100644 index 66fc9c39..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/data/DataLibrus.kt +++ /dev/null @@ -1,10 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.data - -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.datamodels.Profile - -class DataLibrus(val app: App, val profile: Profile, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { - -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/FirstLoginLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/FirstLoginLibrus.kt deleted file mode 100644 index e8255006..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/FirstLoginLibrus.kt +++ /dev/null @@ -1,12 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.firstlogin - -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.datamodels.Profile - -class FirstLoginLibrus(val app: App, val loginStore: LoginStore, val progressCallback: ProgressCallback, val onSuccess: (profileList: List) -> Unit) { - init { - - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/FirstLoginSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/FirstLoginSynergia.kt deleted file mode 100644 index 376a86ac..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/firstlogin/FirstLoginSynergia.kt +++ /dev/null @@ -1,12 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.firstlogin - -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.datamodels.Profile - -class FirstLoginSynergia(val app: App, val loginStore: LoginStore, val progressCallback: ProgressCallback, val onSuccess: (profileList: List) -> Unit) { - init { - - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginJst.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginJst.kt deleted file mode 100644 index 78ed7672..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginJst.kt +++ /dev/null @@ -1,13 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.login - -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.api.v2.ApiLoginResult -import pl.szczodrzynski.edziennik.datamodels.LoginStore - -class LoginJst(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "librus.LoginJst" - } - -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginLibrus.kt deleted file mode 100644 index 959fc74c..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginLibrus.kt +++ /dev/null @@ -1,183 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.login - -import android.util.Pair -import com.google.gson.JsonObject -import im.wangchao.mhttp.Request -import im.wangchao.mhttp.Response -import im.wangchao.mhttp.body.MediaTypeUtils -import im.wangchao.mhttp.callback.JsonCallbackHandler -import im.wangchao.mhttp.callback.TextCallbackHandler -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.R -import pl.szczodrzynski.edziennik.api.AppError -import pl.szczodrzynski.edziennik.api.AppError.* -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.api.v2.* -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.getInt -import pl.szczodrzynski.edziennik.getString -import pl.szczodrzynski.edziennik.utils.Utils.c -import java.net.HttpURLConnection.HTTP_UNAUTHORIZED -import java.util.ArrayList -import java.util.regex.Pattern - -class LoginLibrus(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "librus.LoginLibrus" - } - - init { - // ustawiamy tokeny, generujemy itp - // nic nie robimy z dostępem do api.librus.pl - // to będzie później - val accessToken = loginStore.getLoginData("accessToken", null) - val refreshToken = loginStore.getLoginData("refreshToken", null) - val tokenExpiryTime = loginStore.getLoginData("tokenExpiryTime", 0L) - - // succeed having a non-expired access token and a refresh token - if (tokenExpiryTime-30 > System.currentTimeMillis() / 1000 && refreshToken != null && accessToken != null) { - onSuccess() - } - else if (refreshToken != null) { - app.cookieJar.clearForDomain("portal.librus.pl") - accessToken(null, refreshToken) - } - else { - app.cookieJar.clearForDomain("portal.librus.pl") - authorize(LIBRUS_AUTHORIZE_URL) - } - } - - private fun authorize(url: String?) { - callback.onActionStarted(R.string.sync_action_authorizing) - Request.builder() - .url(url) - .userAgent(LIBRUS_USER_AGENT) - .withClient(app.httpLazy) - .callback(object : TextCallbackHandler() { - override fun onSuccess(data: String, response: Response) { - //d("headers "+response.headers().toString()); - val location = response.headers().get("Location") - if (location != null) { - val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) - if (authMatcher.find()) { - accessToken(authMatcher.group(1), null) - } else { - //callback.onError(activityContext, Edziennik.CODE_OTHER, "Auth code not found: "+location); - authorize(location) - } - } else { - val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(data) - if (csrfMatcher.find()) { - login(csrfMatcher.group(1)) - } else { - callback.onError(null, AppError(TAG, 463, CODE_OTHER, "CSRF token not found.", response, data)) - } - } - } - - override fun onFailure(response: Response, throwable: Throwable) { - callback.onError(null, AppError(TAG, 207, CODE_OTHER, response, throwable)) - } - }) - .build() - .enqueue() - } - - private fun login(csrfToken: String) { - callback.onActionStarted(R.string.sync_action_logging_in) - val email = loginStore.getLoginData("email", "") - val password = loginStore.getLoginData("password", "") - Request.builder() - .url(LIBRUS_LOGIN_URL) - .userAgent(LIBRUS_USER_AGENT) - .addParameter("email", email) - .addParameter("password", password) - .addHeader("X-CSRF-TOKEN", csrfToken) - .contentType(MediaTypeUtils.APPLICATION_JSON) - .post() - .callback(object : JsonCallbackHandler() { - override fun onSuccess(data: JsonObject?, response: Response) { - if (data == null) { - if (response.parserErrorBody != null && response.parserErrorBody.contains("wciąż nieaktywne")) { - callback.onError(null, AppError(TAG, 487, CODE_LIBRUS_NOT_ACTIVATED, response)) - } - callback.onError(null, AppError(TAG, 489, CODE_MAINTENANCE, response)) - return - } - if (data.get("errors") != null) { - callback.onError(null, AppError(TAG, 490, CODE_OTHER, data.get("errors").asJsonArray.get(0).asString, response, data)) - return - } - authorize(data.getString("redirect") ?: LIBRUS_AUTHORIZE_URL) - } - - override fun onFailure(response: Response, throwable: Throwable) { - if (response.code() == 403 || response.code() == 401) { - callback.onError(null, AppError(TAG, 248, CODE_INVALID_LOGIN, response, throwable)) - return - } - callback.onError(null, AppError(TAG, 251, CODE_OTHER, response, throwable)) - } - }) - .build() - .enqueue() - } - - private var refreshTokenFailed = false - private fun accessToken(code: String?, refreshToken: String?) { - callback.onActionStarted(R.string.sync_action_getting_token) - val params = ArrayList>() - params.add(Pair("client_id", LIBRUS_CLIENT_ID)) - if (code != null) { - params.add(Pair("grant_type", "authorization_code")) - params.add(Pair("code", code)) - params.add(Pair("redirect_uri", LIBRUS_REDIRECT_URL)) - } else if (refreshToken != null) { - params.add(Pair("grant_type", "refresh_token")) - params.add(Pair("refresh_token", refreshToken)) - } - Request.builder() - .url(LIBRUS_TOKEN_URL) - .userAgent(LIBRUS_USER_AGENT) - .addParams(params) - .allowErrorCode(HTTP_UNAUTHORIZED) - .post() - .callback(object : JsonCallbackHandler() { - override fun onSuccess(data: JsonObject?, response: Response) { - if (data == null) { - callback.onError(null, AppError(TAG, 539, CODE_MAINTENANCE, response)) - return - } - if (data.get("error") != null) { - val hint = data.getString("hint") - if (!refreshTokenFailed && refreshToken != null && (hint == "Token has been revoked" || hint == "Token has expired")) { - c(TAG, "refreshing the token failed. Trying to log in again.") - refreshTokenFailed = true - authorize(LIBRUS_AUTHORIZE_URL) - return - } - val errorText = data.getString("error") + " " + (data.getString("message") ?: "") + " " + (hint ?: "") - callback.onError(null, AppError(TAG, 552, CODE_OTHER, errorText, response, data)) - return - } - try { - loginStore.putLoginData("tokenType", data.getString("token_type")) - loginStore.putLoginData("accessToken", data.getString("access_token")) - loginStore.putLoginData("refreshToken", data.getString("refresh_token")) - loginStore.putLoginData("tokenExpiryTime", System.currentTimeMillis() / 1000 + (data.getInt("expires_in") ?: 86400)) - onSuccess() - } catch (e: NullPointerException) { - callback.onError(null, AppError(TAG, 311, CODE_OTHER, response, e, data)) - } - - } - - override fun onFailure(response: Response, throwable: Throwable) { - callback.onError(null, AppError(TAG, 317, CODE_OTHER, response, throwable)) - } - }) - .build() - .enqueue() - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginSynergia.kt deleted file mode 100644 index 026f3e50..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/LoginSynergia.kt +++ /dev/null @@ -1,12 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.login - -import pl.szczodrzynski.edziennik.App -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.datamodels.LoginStore - -class LoginSynergia(val app: App, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "librus.LoginSynergia" - } - -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt deleted file mode 100644 index c7f3f69e..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/librus/login/SynergiaTokenExtractor.kt +++ /dev/null @@ -1,110 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.librus.login - -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import im.wangchao.mhttp.Request -import im.wangchao.mhttp.Response -import im.wangchao.mhttp.callback.JsonCallbackHandler -import pl.szczodrzynski.edziennik.* -import pl.szczodrzynski.edziennik.api.AppError -import pl.szczodrzynski.edziennik.api.AppError.* -import pl.szczodrzynski.edziennik.api.v2.LIBRUS_USER_AGENT -import pl.szczodrzynski.edziennik.api.interfaces.ProgressCallback -import pl.szczodrzynski.edziennik.api.v2.LIBRUS_ACCOUNT_URL -import pl.szczodrzynski.edziennik.datamodels.LoginStore -import pl.szczodrzynski.edziennik.datamodels.Profile -import pl.szczodrzynski.edziennik.utils.Utils.d -import java.net.HttpURLConnection.* - -class SynergiaTokenExtractor(val app: App, val profile: Profile, val loginStore: LoginStore, val callback: ProgressCallback, val onSuccess: () -> Unit) { - companion object { - private const val TAG = "librus.SynergiaToken" - } - - init { - val accountToken = profile.getStudentData("accountToken", null) - val accountTokenTime = profile.getStudentData("accountTokenTime", 0L) - if (accountToken.isNotNullNorEmpty() && currentTimeUnix() - accountTokenTime < 3 * 60 * 60) { - onSuccess() - } - else { - if (!synergiaAccount()) - callback.onError(null, AppError(TAG, 33, CODE_INTERNAL_MISSING_DATA)) - } - } - - private fun synergiaAccount(): Boolean { - val accountLogin = profile.getStudentData("accountLogin", null) ?: return false - val tokenType = loginStore.getLoginData("tokenType", null) ?: return false - val accessToken = loginStore.getLoginData("accessToken", null) ?: return false - callback.onActionStarted(R.string.sync_action_getting_account) - d(TAG, "Requesting " + (LIBRUS_ACCOUNT_URL + accountLogin)) - Request.builder() - .url(LIBRUS_ACCOUNT_URL + accountLogin) - .userAgent(LIBRUS_USER_AGENT) - .addHeader("Authorization", "$tokenType $accessToken") - .get() - .allowErrorCode(HTTP_NOT_FOUND) - .allowErrorCode(HTTP_FORBIDDEN) - .allowErrorCode(HTTP_UNAUTHORIZED) - .allowErrorCode(HTTP_BAD_REQUEST) - .allowErrorCode(HTTP_GONE) - .callback(object : JsonCallbackHandler() { - override fun onSuccess(data: JsonObject?, response: Response) { - if (data == null) { - callback.onError(null, AppError(TAG, 641, CODE_MAINTENANCE, response)) - return - } - if (response.code() == 410) { - val reason = data.get("reason") - if (reason != null && reason !is JsonNull && reason.asString == "requires_an_action") { - callback.onError(null, AppError(TAG, 1078, CODE_LIBRUS_DISCONNECTED, response, data)) - return - } - callback.onError(null, AppError(TAG, 70, CODE_INTERNAL_LIBRUS_ACCOUNT_410)) - return - } - if (data.get("message") != null) { - val message = data.get("message").asString - if (message == "Account not found") { - callback.onError(null, AppError(TAG, 651, CODE_OTHER, app.getString(R.string.sync_error_register_student_not_associated_format, profile.studentNameLong, accountLogin), response, data)) - return - } - callback.onError(null, AppError(TAG, 654, CODE_OTHER, message + "\n\n" + accountLogin, response, data)) - return - } - if (response.code() == HTTP_OK) { - try { - // synergiaAccount is executed when a synergia token needs a refresh - val accountId = data.getInt("id") - val accountToken = data.getString("accessToken") - if (accountId == null || accountToken == null) { - callback.onError(null, AppError(TAG, 1284, CODE_OTHER, data)) - return - } - profile.putStudentData("accountId", accountId) - profile.putStudentData("accountToken", accountToken) - profile.putStudentData("accountTokenTime", System.currentTimeMillis() / 1000) - profile.studentNameLong = data.getString("studentName") - val nameParts = data.getString("studentName")?.split(" ")?.toTypedArray() - profile.studentNameShort = nameParts?.get(0) + " " + nameParts?.get(1)?.get(0) - onSuccess() - } catch (e: NullPointerException) { - e.printStackTrace() - callback.onError(null, AppError(TAG, 662, CODE_OTHER, response, e, data)) - } - - } else { - callback.onError(null, AppError(TAG, 425, CODE_OTHER, response, data)) - } - } - - override fun onFailure(response: Response, throwable: Throwable) { - callback.onError(null, AppError(TAG, 432, CODE_OTHER, response, throwable)) - } - }) - .build() - .enqueue() - return true - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataStore.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataStore.kt deleted file mode 100644 index 552340fa..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/DataStore.kt +++ /dev/null @@ -1,118 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.models - -import android.util.LongSparseArray -import androidx.core.util.forEach -import androidx.core.util.isNotEmpty -import pl.szczodrzynski.edziennik.datamodels.* -import pl.szczodrzynski.edziennik.models.Date - -data class DataStore(private val appDb: AppDb, val profileId: Int) { - val teacherList: LongSparseArray = LongSparseArray() - val subjectList: LongSparseArray = LongSparseArray() - val teamList = mutableListOf() - val lessonList = mutableListOf() - val lessonChangeList = mutableListOf() - val gradeCategoryList = mutableListOf() - val gradeList = mutableListOf() - val eventList = mutableListOf() - val eventTypeList = mutableListOf() - val noticeList = mutableListOf() - val attendanceList = mutableListOf() - val announcementList = mutableListOf() - val messageList = mutableListOf() - val messageRecipientList = mutableListOf() - val messageRecipientIgnoreList = mutableListOf() - val metadataList = mutableListOf() - val messageMetadataList = mutableListOf() - - init { - - clear() - - appDb.teacherDao().getAllNow(profileId).forEach { teacher -> - teacherList.put(teacher.id, teacher) - } - appDb.subjectDao().getAllNow(profileId).forEach { subject -> - subjectList.put(subject.id, subject) - } - - /*val teacher = teachers.byNameFirstLast("Jan Kowalski") ?: Teacher(1, 1, "", "").let { - teachers.add(it) - }*/ - - } - - fun clear() { - teacherList.clear() - subjectList.clear() - teamList.clear() - lessonList.clear() - lessonChangeList.clear() - gradeCategoryList.clear() - gradeList.clear() - eventTypeList.clear() - noticeList.clear() - attendanceList.clear() - announcementList.clear() - messageList.clear() - messageRecipientList.clear() - messageRecipientIgnoreList.clear() - metadataList.clear() - messageMetadataList.clear() - } - - fun saveData() { - if (teacherList.isNotEmpty()) { - val tempList: ArrayList = ArrayList() - teacherList.forEach { _, teacher -> - tempList.add(teacher) - } - appDb.teacherDao().addAll(tempList) - } - if (subjectList.isNotEmpty()) { - val tempList: ArrayList = ArrayList() - subjectList.forEach { _, subject -> - tempList.add(subject) - } - appDb.subjectDao().addAll(tempList) - } - if (teamList.isNotEmpty()) - appDb.teamDao().addAll(teamList) - if (lessonList.isNotEmpty()) { - appDb.lessonDao().clear(profileId) - appDb.lessonDao().addAll(lessonList) - } - if (lessonChangeList.isNotEmpty()) - appDb.lessonChangeDao().addAll(lessonChangeList) - if (gradeCategoryList.isNotEmpty()) - appDb.gradeCategoryDao().addAll(gradeCategoryList) - if (gradeList.isNotEmpty()) { - appDb.gradeDao().clear(profileId) - appDb.gradeDao().addAll(gradeList) - } - if (eventList.isNotEmpty()) { - appDb.eventDao().removeFuture(profileId, Date.getToday()) - appDb.eventDao().addAll(eventList) - } - if (eventTypeList.isNotEmpty()) - appDb.eventTypeDao().addAll(eventTypeList) - if (noticeList.isNotEmpty()) { - appDb.noticeDao().clear(profileId) - appDb.noticeDao().addAll(noticeList) - } - if (attendanceList.isNotEmpty()) - appDb.attendanceDao().addAll(attendanceList) - if (announcementList.isNotEmpty()) - appDb.announcementDao().addAll(announcementList) - if (messageList.isNotEmpty()) - appDb.messageDao().addAllIgnore(messageList) - if (messageRecipientList.isNotEmpty()) - appDb.messageRecipientDao().addAll(messageRecipientList) - if (messageRecipientIgnoreList.isNotEmpty()) - appDb.messageRecipientDao().addAllIgnore(messageRecipientIgnoreList) - if (metadataList.isNotEmpty()) - appDb.metadataDao().addAllIgnore(metadataList) - if (messageMetadataList.isNotEmpty()) - appDb.metadataDao().setSeen(messageMetadataList) - } -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Feature.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Feature.kt deleted file mode 100644 index b502fe85..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Feature.kt +++ /dev/null @@ -1,10 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.models - - -data class Feature(val featureId: Int, val loginOptions: Map>) { - - init { - - } - -} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Features.kt b/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Features.kt deleted file mode 100644 index bf40bd80..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/api/v2/models/Features.kt +++ /dev/null @@ -1,13 +0,0 @@ -package pl.szczodrzynski.edziennik.api.v2.models - -import pl.szczodrzynski.edziennik.api.v2.* - -val Features = listOf( - Feature(FEATURE_TIMETABLE, mapOf( - LOGIN_TYPE_LIBRUS to listOf( - LOGIN_MODE_LIBRUS_EMAIL, - LOGIN_MODE_LIBRUS_SYNERGIA, - LOGIN_MODE_LIBRUS_JST - ) - )) -) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt new file mode 100644 index 00000000..d597a119 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/AbstractConfig.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +interface AbstractConfig { + fun set(key: String, value: String?) +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt new file mode 100644 index 00000000..bf4f743f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/Config.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import com.google.gson.JsonObject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.config.utils.ConfigMigration +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.config.utils.toHashMap +import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update +import pl.szczodrzynski.edziennik.data.db.AppDb +import kotlin.coroutines.CoroutineContext + +class Config(val db: AppDb) : CoroutineScope, AbstractConfig { + companion object { + const val DATA_VERSION = 11 + } + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + val values: HashMap = hashMapOf() + + val ui by lazy { ConfigUI(this) } + val sync by lazy { ConfigSync(this) } + val timetable by lazy { ConfigTimetable(this) } + val grades by lazy { ConfigGrades(this) } + + private var mDataVersion: Int? = null + var dataVersion: Int + get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 } + set(value) { set("dataVersion", value); mDataVersion = value } + + private var mHash: String? = null + var hash: String + get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" } + set(value) { set("hash", value); mHash = value } + + private var mLastProfileId: Int? = null + var lastProfileId: Int + get() { mLastProfileId = mLastProfileId ?: values.get("lastProfileId", 0); return mLastProfileId ?: 0 } + set(value) { set("lastProfileId", value); mLastProfileId = value } + + private var mUpdatesChannel: String? = null + var updatesChannel: String + get() { mUpdatesChannel = mUpdatesChannel ?: values.get("updatesChannel", "release"); return mUpdatesChannel ?: "release" } + set(value) { set("updatesChannel", value); mUpdatesChannel = value } + private var mUpdate: Update? = null + var update: Update? + get() { mUpdate = mUpdate ?: values.get("update", null as Update?); return mUpdate ?: null as Update? } + set(value) { set("update", value); mUpdate = value } + + private var mAppVersion: Int? = null + var appVersion: Int + get() { mAppVersion = mAppVersion ?: values.get("appVersion", BuildConfig.VERSION_CODE); return mAppVersion ?: BuildConfig.VERSION_CODE } + set(value) { set("appVersion", value); mAppVersion = value } + + private var mLoginFinished: Boolean? = null + var loginFinished: Boolean + get() { mLoginFinished = mLoginFinished ?: values.get("loginFinished", false); return mLoginFinished ?: false } + set(value) { set("loginFinished", value); mLoginFinished = value } + + private var mPrivacyPolicyAccepted: Boolean? = null + var privacyPolicyAccepted: Boolean + get() { mPrivacyPolicyAccepted = mPrivacyPolicyAccepted ?: values.get("privacyPolicyAccepted", false); return mPrivacyPolicyAccepted ?: false } + set(value) { set("privacyPolicyAccepted", value); mPrivacyPolicyAccepted = value } + + private var mDebugMode: Boolean? = null + var debugMode: Boolean + get() { mDebugMode = mDebugMode ?: values.get("debugMode", false); return mDebugMode ?: false } + set(value) { set("debugMode", value); mDebugMode = value } + + private var mDevModePassword: String? = null + var devModePassword: String? + get() { mDevModePassword = mDevModePassword ?: values.get("devModePassword", null as String?); return mDevModePassword } + set(value) { set("devModePassword", value); mDevModePassword = value } + + private var mAppInstalledTime: Long? = null + var appInstalledTime: Long + get() { mAppInstalledTime = mAppInstalledTime ?: values.get("appInstalledTime", 0L); return mAppInstalledTime ?: 0L } + set(value) { set("appInstalledTime", value); mAppInstalledTime = value } + + private var mAppRateSnackbarTime: Long? = null + var appRateSnackbarTime: Long + get() { mAppRateSnackbarTime = mAppRateSnackbarTime ?: values.get("appRateSnackbarTime", 0L); return mAppRateSnackbarTime ?: 0L } + set(value) { set("appRateSnackbarTime", value); mAppRateSnackbarTime = value } + + private var mRunSync: Boolean? = null + var runSync: Boolean + get() { mRunSync = mRunSync ?: values.get("runSync", false); return mRunSync ?: false } + set(value) { set("runSync", value); mRunSync = value } + + private var mWidgetConfigs: JsonObject? = null + var widgetConfigs: JsonObject + get() { mWidgetConfigs = mWidgetConfigs ?: values.get("widgetConfigs", JsonObject()); return mWidgetConfigs ?: JsonObject() } + set(value) { set("widgetConfigs", value); mWidgetConfigs = value } + + private var rawEntries: List = db.configDao().getAllNow() + private val profileConfigs: HashMap = hashMapOf() + init { + rawEntries.toHashMap(-1, values) + } + fun migrate(app: App) { + if (dataVersion < DATA_VERSION) + ConfigMigration(app, this) + } + fun getFor(profileId: Int): ProfileConfig { + return profileConfigs[profileId] ?: ProfileConfig(db, profileId, db.configDao().getAllNow(profileId)).also { + profileConfigs[profileId] = it + } + } + fun forProfile() = getFor(App.profileId) + + fun setProfile(profileId: Int) { + } + + override fun set(key: String, value: String?) { + values[key] = value + launch { + db.configDao().add(ConfigEntry(-1, key, value)) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt new file mode 100644 index 00000000..267ec322 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigGrades.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.managers.GradesManager + +class ConfigGrades(private val config: Config) { + private var mOrderBy: Int? = null + var orderBy: Int + get() { mOrderBy = mOrderBy ?: config.values.get("gradesOrderBy", 0); return mOrderBy ?: GradesManager.ORDER_BY_DATE_DESC } + set(value) { config.set("gradesOrderBy", value); mOrderBy = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt new file mode 100644 index 00000000..a6bb68df --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigSync.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.getIntList +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.models.Time + +class ConfigSync(private val config: Config) { + private var mDontShowAppManagerDialog: Boolean? = null + var dontShowAppManagerDialog: Boolean + get() { mDontShowAppManagerDialog = mDontShowAppManagerDialog ?: config.values.get("dontShowAppManagerDialog", false); return mDontShowAppManagerDialog ?: false } + set(value) { config.set("dontShowAppManagerDialog", value); mDontShowAppManagerDialog = value } + + private var mSyncEnabled: Boolean? = null + var enabled: Boolean + get() { mSyncEnabled = mSyncEnabled ?: config.values.get("syncEnabled", true); return mSyncEnabled ?: true } + set(value) { config.set("syncEnabled", value); mSyncEnabled = value } + + private var mWebPushEnabled: Boolean? = null + var webPushEnabled: Boolean + get() { mWebPushEnabled = mWebPushEnabled ?: config.values.get("webPushEnabled", true); return mWebPushEnabled ?: true } + set(value) { config.set("webPushEnabled", value); mWebPushEnabled = value } + + private var mSyncOnlyWifi: Boolean? = null + var onlyWifi: Boolean + get() { mSyncOnlyWifi = mSyncOnlyWifi ?: config.values.get("syncOnlyWifi", false); return mSyncOnlyWifi ?: notifyAboutUpdates } + set(value) { config.set("syncOnlyWifi", value); mSyncOnlyWifi = value } + + private var mSyncInterval: Int? = null + var interval: Int + get() { mSyncInterval = mSyncInterval ?: config.values.get("syncInterval", 60*60); return mSyncInterval ?: 60*60 } + set(value) { config.set("syncInterval", value); mSyncInterval = value } + + private var mNotifyAboutUpdates: Boolean? = null + var notifyAboutUpdates: Boolean + get() { mNotifyAboutUpdates = mNotifyAboutUpdates ?: config.values.get("notifyAboutUpdates", true); return mNotifyAboutUpdates ?: true } + set(value) { config.set("notifyAboutUpdates", value); mNotifyAboutUpdates = value } + + private var mLastAppSync: Long? = null + var lastAppSync: Long + get() { mLastAppSync = mLastAppSync ?: config.values.get("lastAppSync", 0L); return mLastAppSync ?: 0L } + set(value) { config.set("lastAppSync", value); mLastAppSync = value } + + /* ____ _ _ _ + / __ \ (_) | | | | + | | | |_ _ _ ___| |_ | |__ ___ _ _ _ __ ___ + | | | | | | | |/ _ \ __| | '_ \ / _ \| | | | '__/ __| + | |__| | |_| | | __/ |_ | | | | (_) | |_| | | \__ \ + \___\_\\__,_|_|\___|\__| |_| |_|\___/ \__,_|_| |__*/ + private var mQuietHoursEnabled: Boolean? = null + var quietHoursEnabled: Boolean + get() { mQuietHoursEnabled = mQuietHoursEnabled ?: config.values.get("quietHoursEnabled", false); return mQuietHoursEnabled ?: false } + set(value) { config.set("quietHoursEnabled", value); mQuietHoursEnabled = value } + + private var mQuietHoursStart: Time? = null + var quietHoursStart: Time? + get() { mQuietHoursStart = mQuietHoursStart ?: config.values.get("quietHoursStart", null as Time?); return mQuietHoursStart } + set(value) { config.set("quietHoursStart", value); mQuietHoursStart = value } + + private var mQuietHoursEnd: Time? = null + var quietHoursEnd: Time? + get() { mQuietHoursEnd = mQuietHoursEnd ?: config.values.get("quietHoursEnd", null as Time?); return mQuietHoursEnd } + set(value) { config.set("quietHoursEnd", value); mQuietHoursEnd = value } + + private var mQuietDuringLessons: Boolean? = null + var quietDuringLessons: Boolean + get() { mQuietDuringLessons = mQuietDuringLessons ?: config.values.get("quietDuringLessons", false); return mQuietDuringLessons ?: false } + set(value) { config.set("quietDuringLessons", value); mQuietDuringLessons = value } + + /* ______ _____ __ __ _______ _ + | ____/ ____| \/ | |__ __| | | + | |__ | | | \ / | | | ___ | | _____ _ __ ___ + | __|| | | |\/| | | |/ _ \| |/ / _ \ '_ \/ __| + | | | |____| | | | | | (_) | < __/ | | \__ \ + |_| \_____|_| |_| |_|\___/|_|\_\___|_| |_|__*/ + private var mTokenApp: String? = null + var tokenApp: String? + get() { mTokenApp = mTokenApp ?: config.values.get("tokenApp", null as String?); return mTokenApp } + set(value) { config.set("tokenApp", value); mTokenApp = value } + private var mTokenMobidziennik: String? = null + var tokenMobidziennik: String? + get() { mTokenMobidziennik = mTokenMobidziennik ?: config.values.get("tokenMobidziennik", null as String?); return mTokenMobidziennik } + set(value) { config.set("tokenMobidziennik", value); mTokenMobidziennik = value } + private var mTokenLibrus: String? = null + var tokenLibrus: String? + get() { mTokenLibrus = mTokenLibrus ?: config.values.get("tokenLibrus", null as String?); return mTokenLibrus } + set(value) { config.set("tokenLibrus", value); mTokenLibrus = value } + private var mTokenVulcan: String? = null + var tokenVulcan: String? + get() { mTokenVulcan = mTokenVulcan ?: config.values.get("tokenVulcan", null as String?); return mTokenVulcan } + set(value) { config.set("tokenVulcan", value); mTokenVulcan = value } + + private var mTokenMobidziennikList: List? = null + var tokenMobidziennikList: List + get() { mTokenMobidziennikList = mTokenMobidziennikList ?: config.values.getIntList("tokenMobidziennikList", listOf()); return mTokenMobidziennikList ?: listOf() } + set(value) { config.set("tokenMobidziennikList", value); mTokenMobidziennikList = value } + private var mTokenLibrusList: List? = null + var tokenLibrusList: List + get() { mTokenLibrusList = mTokenLibrusList ?: config.values.getIntList("tokenLibrusList", listOf()); return mTokenLibrusList ?: listOf() } + set(value) { config.set("tokenLibrusList", value); mTokenLibrusList = value } + private var mTokenVulcanList: List? = null + var tokenVulcanList: List + get() { mTokenVulcanList = mTokenVulcanList ?: config.values.getIntList("tokenVulcanList", listOf()); return mTokenVulcanList ?: listOf() } + set(value) { config.set("tokenVulcanList", value); mTokenVulcanList = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt new file mode 100644 index 00000000..2418f1d3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigTimetable.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.models.Time + +class ConfigTimetable(private val config: Config) { + private var mBellSyncMultiplier: Int? = null + var bellSyncMultiplier: Int + get() { mBellSyncMultiplier = mBellSyncMultiplier ?: config.values.get("bellSyncMultiplier", 0); return mBellSyncMultiplier ?: 0 } + set(value) { config.set("bellSyncMultiplier", value); mBellSyncMultiplier = value } + + private var mBellSyncDiff: Time? = null + var bellSyncDiff: Time? + get() { mBellSyncDiff = mBellSyncDiff ?: config.values.get("bellSyncDiff", null as Time?); return mBellSyncDiff } + set(value) { config.set("bellSyncDiff", value); mBellSyncDiff = value } + + private var mCountInSeconds: Boolean? = null + var countInSeconds: Boolean + get() { mCountInSeconds = mCountInSeconds ?: config.values.get("countInSeconds", false); return mCountInSeconds ?: false } + set(value) { config.set("countInSeconds", value); mCountInSeconds = value } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt new file mode 100644 index 00000000..7b39383e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ConfigUI.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.getIntList +import pl.szczodrzynski.edziennik.config.utils.set + +class ConfigUI(private val config: Config) { + private var mTheme: Int? = null + var theme: Int + get() { mTheme = mTheme ?: config.values.get("theme", 1); return mTheme ?: 1 } + set(value) { config.set("theme", value); mTheme = value } + + private var mLanguage: String? = null + var language: String? + get() { mLanguage = mLanguage ?: config.values.get("language", null as String?); return mLanguage } + set(value) { config.set("language", value); mLanguage = value } + + private var mHeaderBackground: String? = null + var headerBackground: String? + get() { mHeaderBackground = mHeaderBackground ?: config.values.get("headerBg", null as String?); return mHeaderBackground } + set(value) { config.set("headerBg", value); mHeaderBackground = value } + + private var mAppBackground: String? = null + var appBackground: String? + get() { mAppBackground = mAppBackground ?: config.values.get("appBg", null as String?); return mAppBackground } + set(value) { config.set("appBg", value); mAppBackground = value } + + private var mMiniMenuVisible: Boolean? = null + var miniMenuVisible: Boolean + get() { mMiniMenuVisible = mMiniMenuVisible ?: config.values.get("miniMenuVisible", false); return mMiniMenuVisible ?: false } + set(value) { config.set("miniMenuVisible", value); mMiniMenuVisible = value } + + private var mMiniMenuButtons: List? = null + var miniMenuButtons: List + get() { mMiniMenuButtons = mMiniMenuButtons ?: config.values.getIntList("miniMenuButtons", listOf()); return mMiniMenuButtons ?: listOf() } + set(value) { config.set("miniMenuButtons", value); mMiniMenuButtons = value } + + private var mOpenDrawerOnBackPressed: Boolean? = null + var openDrawerOnBackPressed: Boolean + get() { mOpenDrawerOnBackPressed = mOpenDrawerOnBackPressed ?: config.values.get("openDrawerOnBackPressed", false); return mOpenDrawerOnBackPressed ?: false } + set(value) { config.set("openDrawerOnBackPressed", value); mOpenDrawerOnBackPressed = value } + + private var mSnowfall: Boolean? = null + var snowfall: Boolean + get() { mSnowfall = mSnowfall ?: config.values.get("snowfall", false); return mSnowfall ?: false } + set(value) { config.set("snowfall", value); mSnowfall = value } + + private var mBottomSheetOpened: Boolean? = null + var bottomSheetOpened: Boolean + get() { mBottomSheetOpened = mBottomSheetOpened ?: config.values.get("bottomSheetOpened", false); return mBottomSheetOpened ?: false } + set(value) { config.set("bottomSheetOpened", value); mBottomSheetOpened = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt new file mode 100644 index 00000000..6281225d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfig.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.config.utils.ProfileConfigMigration +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.config.utils.toHashMap +import pl.szczodrzynski.edziennik.data.db.AppDb +import kotlin.coroutines.CoroutineContext + +class ProfileConfig(val db: AppDb, val profileId: Int, rawEntries: List) : CoroutineScope, AbstractConfig { + companion object { + const val DATA_VERSION = 1 + } + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + val values: HashMap = hashMapOf() + + val grades by lazy { ProfileConfigGrades(this) } + val ui by lazy { ProfileConfigUI(this) } + val sync by lazy { ProfileConfigSync(this) } + /* + val timetable by lazy { ConfigTimetable(this) } + val grades by lazy { ConfigGrades(this) }*/ + + private var mDataVersion: Int? = null + var dataVersion: Int + get() { mDataVersion = mDataVersion ?: values.get("dataVersion", 0); return mDataVersion ?: 0 } + set(value) { set("dataVersion", value); mDataVersion = value } + + private var mHash: String? = null + var hash: String + get() { mHash = mHash ?: values.get("hash", ""); return mHash ?: "" } + set(value) { set("hash", value); mHash = value } + + init { + rawEntries.toHashMap(profileId, values) + if (dataVersion < DATA_VERSION) + ProfileConfigMigration(this) + } + + override fun set(key: String, value: String?) { + values[key] = value + launch { + db.configDao().add(ConfigEntry(profileId, key, value)) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt new file mode 100644 index 00000000..490fe2dd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigGrades.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.getFloat +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES + +class ProfileConfigGrades(private val config: ProfileConfig) { + private var mColorMode: Int? = null + var colorMode: Int + get() { mColorMode = mColorMode ?: config.values.get("gradesColorMode", COLOR_MODE_WEIGHTED); return mColorMode ?: COLOR_MODE_WEIGHTED } + set(value) { config.set("gradesColorMode", value); mColorMode = value } + + private var mYearAverageMode: Int? = null + var yearAverageMode: Int + get() { mYearAverageMode = mYearAverageMode ?: config.values.get("yearAverageMode", YEAR_ALL_GRADES); return mYearAverageMode ?: YEAR_ALL_GRADES } + set(value) { config.set("yearAverageMode", value); mYearAverageMode = value } + + private var mHideImproved: Boolean? = null + var hideImproved: Boolean + get() { mHideImproved = mHideImproved ?: config.values.get("hideImproved", false); return mHideImproved ?: false } + set(value) { config.set("hideImproved", value); mHideImproved = value } + + private var mAverageWithoutWeight: Boolean? = null + var averageWithoutWeight: Boolean + get() { mAverageWithoutWeight = mAverageWithoutWeight ?: config.values.get("averageWithoutWeight", true); return mAverageWithoutWeight ?: true } + set(value) { config.set("averageWithoutWeight", value); mAverageWithoutWeight = value } + + private var mPlusValue: Float? = null + var plusValue: Float? + get() { mPlusValue = mPlusValue ?: config.values.getFloat("plusValue"); return mPlusValue } + set(value) { config.set("plusValue", value); mPlusValue = value } + private var mMinusValue: Float? = null + var minusValue: Float? + get() { mMinusValue = mMinusValue ?: config.values.getFloat("minusValue"); return mMinusValue } + set(value) { config.set("minusValue", value); mMinusValue = value } + + private var mDontCountEnabled: Boolean? = null + var dontCountEnabled: Boolean + get() { mDontCountEnabled = mDontCountEnabled ?: config.values.get("dontCountEnabled", false); return mDontCountEnabled ?: false } + set(value) { config.set("dontCountEnabled", value); mDontCountEnabled = value } + + private var mDontCountGrades: List? = null + var dontCountGrades: List + get() { mDontCountGrades = mDontCountGrades ?: config.values.get("dontCountGrades", listOf()); return mDontCountGrades ?: listOf() } + set(value) { config.set("dontCountGrades", value); mDontCountGrades = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt new file mode 100644 index 00000000..6d8f95e3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigSync.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set + +class ProfileConfigSync(private val config: ProfileConfig) { + private var mNotificationFilter: List? = null + var notificationFilter: List + get() { mNotificationFilter = mNotificationFilter ?: config.values.get("notificationFilter", listOf()); return mNotificationFilter ?: listOf() } + set(value) { config.set("notificationFilter", value); mNotificationFilter = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt new file mode 100644 index 00000000..5ce237a0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/ProfileConfigUI.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-19. + */ + +package pl.szczodrzynski.edziennik.config + +import pl.szczodrzynski.edziennik.config.utils.get +import pl.szczodrzynski.edziennik.config.utils.set +import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT +import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel + +class ProfileConfigUI(private val config: ProfileConfig) { + private var mAgendaViewType: Int? = null + var agendaViewType: Int + get() { mAgendaViewType = mAgendaViewType ?: config.values.get("agendaViewType", 0); return mAgendaViewType ?: AGENDA_DEFAULT } + set(value) { config.set("agendaViewType", value); mAgendaViewType = value } + + private var mHomeCards: List? = null + var homeCards: List + get() { mHomeCards = mHomeCards ?: config.values.get("homeCards", listOf(), HomeCardModel::class.java); return mHomeCards ?: listOf() } + set(value) { config.set("homeCards", value); mHomeCards = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt new file mode 100644 index 00000000..f55df857 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigDao.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config.db + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query + +@Dao +interface ConfigDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun add(entry: ConfigEntry) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addAll(list: List) + + @Query("SELECT * FROM config WHERE profileId = -1") + fun getAllNow(): List + + @Query("SELECT * FROM config WHERE profileId = :profileId") + fun getAllNow(profileId: Int): List + + @Query("DELETE FROM config WHERE profileId = :profileId") + fun clear(profileId: Int) +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigEntry.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigEntry.kt new file mode 100644 index 00000000..6da441fe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/db/ConfigEntry.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-26. + */ + +package pl.szczodrzynski.edziennik.config.db + +import androidx.room.Entity + +@Entity(tableName = "config", primaryKeys = ["profileId", "key"]) +data class ConfigEntry( + val profileId: Int = -1, + val key: String, + val value: String? +) \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt new file mode 100644 index 00000000..78a1d8a6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/AppConfigMigrationV3.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-19. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import android.content.SharedPreferences +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_LIBRUS +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_MOBIDZIENNIK +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_VULCAN +import pl.szczodrzynski.edziennik.utils.models.Time +import kotlin.math.abs + +class AppConfigMigrationV3(p: SharedPreferences, config: Config) { + init { config.apply { + val s = "app.appConfig" + if (dataVersion < 1) { + ui.theme = p.getString("$s.appTheme", null)?.toIntOrNull() ?: 1 + sync.enabled = p.getString("$s.registerSyncEnabled", null)?.toBoolean() ?: true + sync.interval = p.getString("$s.registerSyncInterval", null)?.toIntOrNull() ?: 3600 + val oldButtons = p.getString("$s.miniDrawerButtonIds", null)?.let { str -> + str.replace("[\\[\\]]*".toRegex(), "") + .split(",\\s?".toRegex()) + .mapNotNull { it.toIntOrNull() } + } + ui.miniMenuButtons = oldButtons ?: listOf( + MainActivity.DRAWER_ITEM_HOME, + MainActivity.DRAWER_ITEM_TIMETABLE, + MainActivity.DRAWER_ITEM_AGENDA, + MainActivity.DRAWER_ITEM_GRADES, + MainActivity.DRAWER_ITEM_MESSAGES, + MainActivity.DRAWER_ITEM_HOMEWORK, + MainActivity.DRAWER_ITEM_SETTINGS + ) + dataVersion = 1 + } + if (dataVersion < 2) { + devModePassword = p.getString("$s.devModePassword", null).fix() + sync.tokenApp = p.getString("$s.fcmToken", null).fix() + timetable.bellSyncMultiplier = p.getString("$s.bellSyncMultiplier", null)?.toIntOrNull() ?: 0 + appRateSnackbarTime = p.getString("$s.appRateSnackbarTime", null)?.toLongOrNull() ?: 0 + timetable.countInSeconds = p.getString("$s.countInSeconds", null)?.toBoolean() ?: false + ui.headerBackground = p.getString("$s.headerBackground", null).fix() + ui.appBackground = p.getString("$s.appBackground", null).fix() + ui.language = p.getString("$s.language", null).fix() + appVersion = p.getString("$s.lastAppVersion", null)?.toIntOrNull() ?: BuildConfig.VERSION_CODE + appInstalledTime = p.getString("$s.appInstalledTime", null)?.toLongOrNull() ?: 0 + grades.orderBy = p.getString("$s.gradesOrderBy", null)?.toIntOrNull() ?: 0 + sync.quietDuringLessons = p.getString("$s.quietDuringLessons", null)?.toBoolean() ?: false + ui.miniMenuVisible = p.getString("$s.miniDrawerVisible", null)?.toBoolean() ?: false + loginFinished = p.getString("$s.loginFinished", null)?.toBoolean() ?: false + sync.onlyWifi = p.getString("$s.registerSyncOnlyWifi", null)?.toBoolean() ?: false + sync.notifyAboutUpdates = p.getString("$s.notifyAboutUpdates", null)?.toBoolean() ?: true + timetable.bellSyncDiff = p.getString("$s.bellSyncDiff", null)?.let { Gson().fromJson(it, Time::class.java) } + + val startMillis = p.getString("$s.quietHoursStart", null)?.toLongOrNull() ?: 0 + val endMillis = p.getString("$s.quietHoursEnd", null)?.toLongOrNull() ?: 0 + if (startMillis > 0) { + try { + sync.quietHoursStart = Time.fromMillis(abs(startMillis)) + sync.quietHoursEnd = Time.fromMillis(abs(endMillis)) + sync.quietHoursEnabled = true + } + catch (_: Exception) {} + } + else { + sync.quietHoursEnabled = false + sync.quietHoursStart = null + sync.quietHoursEnd = null + } + + sync.tokenMobidziennikList = listOf() + sync.tokenVulcanList = listOf() + sync.tokenLibrusList = listOf() + val tokens = p.getString("$s.fcmTokens", null)?.let { Gson().fromJson>>>(it, object: TypeToken>>>(){}.type) } + tokens?.forEach { + val token = it.value.first + when (it.key) { + LOGIN_TYPE_MOBIDZIENNIK -> sync.tokenMobidziennik = token + LOGIN_TYPE_VULCAN -> sync.tokenVulcan = token + LOGIN_TYPE_LIBRUS -> sync.tokenLibrus = token + } + } + dataVersion = 2 + } + }} + + private fun String?.fix(): String? { + return this?.replace("\"", "")?.let { if (it == "null") null else it } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt new file mode 100644 index 00000000..1188b1a4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigExtensions.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import com.google.gson.* +import com.google.gson.reflect.TypeToken +import pl.szczodrzynski.edziennik.config.AbstractConfig +import pl.szczodrzynski.edziennik.config.db.ConfigEntry +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +private val gson = Gson() + +fun AbstractConfig.set(key: String, value: Int) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Boolean) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Long) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Float) { + set(key, value.toString()) +} +fun AbstractConfig.set(key: String, value: Date?) { + set(key, value?.stringY_m_d) +} +fun AbstractConfig.set(key: String, value: Time?) { + set(key, value?.stringValue) +} +fun AbstractConfig.set(key: String, value: JsonElement?) { + set(key, value?.toString()) +} +fun AbstractConfig.set(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.set(key: String, value: Any?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setStringList(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setIntList(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} +fun AbstractConfig.setLongList(key: String, value: List?) { + set(key, value?.let { gson.toJson(it) }) +} + +fun HashMap.get(key: String, default: String?): String? { + return this[key] ?: default +} +fun HashMap.get(key: String, default: Boolean): Boolean { + return this[key]?.toBoolean() ?: default +} +fun HashMap.get(key: String, default: Int): Int { + return this[key]?.toIntOrNull() ?: default +} +fun HashMap.get(key: String, default: Long): Long { + return this[key]?.toLongOrNull() ?: default +} +fun HashMap.get(key: String, default: Float): Float { + return this[key]?.toFloatOrNull() ?: default +} +fun HashMap.get(key: String, default: Date?): Date? { + return this[key]?.let { Date.fromY_m_d(it) } ?: default +} +fun HashMap.get(key: String, default: Time?): Time? { + return this[key]?.let { Time.fromHms(it) } ?: default +} +fun HashMap.get(key: String, default: JsonObject?): JsonObject? { + return this[key]?.let { JsonParser().parse(it)?.asJsonObject } ?: default +} +fun HashMap.get(key: String, default: JsonArray?): JsonArray? { + return this[key]?.let { JsonParser().parse(it)?.asJsonArray } ?: default +} +inline fun HashMap.get(key: String, default: T?): T? { + return this[key]?.let { Gson().fromJson(it, T::class.java) } ?: default +} +/* !!! cannot use mutable list here - modifying it will not update the DB */ +fun HashMap.get(key: String, default: List?, classOfT: Class): List? { + return this[key]?.let { ConfigGsonUtils().deserializeList(gson, it, classOfT) } ?: default +} +fun HashMap.getStringList(key: String, default: List?): List? { + return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default +} +fun HashMap.getIntList(key: String, default: List?): List? { + return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default +} +fun HashMap.getLongList(key: String, default: List?): List? { + return this[key]?.let { gson.fromJson>(it, object: TypeToken>(){}.type) } ?: default +} + +fun HashMap.getFloat(key: String): Float? { + return this[key]?.toFloatOrNull() +} + +fun List.toHashMap(profileId: Int, map: HashMap) { + map.clear() + forEach { + if (it.profileId == profileId) + map[it.key] = it.value + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt new file mode 100644 index 00000000..eb04578d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigGsonUtils.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-2. + */ +package pl.szczodrzynski.edziennik.config.utils + +import com.google.gson.Gson +import com.google.gson.JsonParser +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.ui.modules.home.HomeCardModel +import pl.szczodrzynski.edziennik.utils.models.Time + +class ConfigGsonUtils { + fun deserializeList(gson: Gson, str: String?, classOfT: Class): List { + val json = JsonParser().parse(str) + val list: MutableList = mutableListOf() + if (!json.isJsonArray) + return list + + json.asJsonArray.forEach { e -> + when (classOfT) { + String::class.java -> { + list += e.asString as T + } + HomeCardModel::class.java -> { + val o = e.asJsonObject + list += HomeCardModel( + o.getInt("profileId", 0), + o.getInt("cardId", 0) + ) as T + } + Time::class.java -> { + val o = e.asJsonObject + list += Time( + o.getInt("hour", 0), + o.getInt("minute", 0), + o.getInt("second", 0) + ) as T + } + } + } + + return list + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt new file mode 100644 index 00000000..a30c996a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ConfigMigration.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-27. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import android.content.Context +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.HOUR +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.config.Config +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC +import pl.szczodrzynski.edziennik.utils.models.Time +import kotlin.math.abs + +class ConfigMigration(app: App, config: Config) { + init { config.apply { + + val p = app.getSharedPreferences("pl.szczodrzynski.edziennik_profiles", Context.MODE_PRIVATE) + if (p.contains("app.appConfig.appTheme")) { + // migrate appConfig from app version 3.x and lower. + // Updates dataVersion to level 2. + AppConfigMigrationV3(p, config) + } + + if (dataVersion < 2) { + appVersion = BuildConfig.VERSION_CODE + loginFinished = false + ui.language = null + ui.theme = 1 + ui.appBackground = null + ui.headerBackground = null + ui.miniMenuVisible = false + ui.miniMenuButtons = listOf( + MainActivity.DRAWER_ITEM_HOME, + MainActivity.DRAWER_ITEM_TIMETABLE, + MainActivity.DRAWER_ITEM_AGENDA, + MainActivity.DRAWER_ITEM_GRADES, + MainActivity.DRAWER_ITEM_MESSAGES, + MainActivity.DRAWER_ITEM_HOMEWORK, + MainActivity.DRAWER_ITEM_SETTINGS + ) + sync.enabled = true + sync.interval = 1*HOUR.toInt() + sync.notifyAboutUpdates = true + sync.onlyWifi = false + sync.quietHoursEnabled = false + sync.quietHoursStart = null + sync.quietHoursEnd = null + sync.quietDuringLessons = false + sync.tokenApp = null + sync.tokenMobidziennik = null + sync.tokenMobidziennikList = listOf() + sync.tokenLibrus = null + sync.tokenLibrusList = listOf() + sync.tokenVulcan = null + sync.tokenVulcanList = listOf() + timetable.bellSyncMultiplier = 0 + timetable.bellSyncDiff = null + timetable.countInSeconds = false + grades.orderBy = ORDER_BY_DATE_DESC + + dataVersion = 2 + } + + if (dataVersion < 10) { + ui.openDrawerOnBackPressed = false + ui.snowfall = false + ui.bottomSheetOpened = false + sync.dontShowAppManagerDialog = false + + dataVersion = 10 + } + + if (dataVersion < 11) { + val startMillis = config.values.get("quietHoursStart", 0L) + val endMillis = config.values.get("quietHoursEnd", 0L) + if (startMillis > 0) { + try { + sync.quietHoursStart = Time.fromMillis(abs(startMillis)) + sync.quietHoursEnd = Time.fromMillis(abs(endMillis)) + sync.quietHoursEnabled = true + } + catch (_: Exception) {} + } + else { + sync.quietHoursEnabled = false + sync.quietHoursStart = null + sync.quietHoursEnd = null + } + + dataVersion = 11 + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt new file mode 100644 index 00000000..67861d25 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/config/utils/ProfileConfigMigration.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-1. + */ + +package pl.szczodrzynski.edziennik.config.utils + +import pl.szczodrzynski.edziennik.config.ProfileConfig +import pl.szczodrzynski.edziennik.data.db.entity.Profile.Companion.AGENDA_DEFAULT +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES + +class ProfileConfigMigration(config: ProfileConfig) { + init { config.apply { + + if (dataVersion < 1) { + grades.colorMode = COLOR_MODE_WEIGHTED + grades.dontCountEnabled = false + grades.yearAverageMode = YEAR_ALL_GRADES + ui.agendaViewType = AGENDA_DEFAULT + + dataVersion = 1 + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt new file mode 100644 index 00000000..2b0b63f2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/ApiService.kt @@ -0,0 +1,327 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-28. + */ + +package pl.szczodrzynski.edziennik.data.api + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.IBinder +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.* +import pl.szczodrzynski.edziennik.data.api.events.requests.ServiceCloseRequest +import pl.szczodrzynski.edziennik.data.api.events.requests.TaskCancelRequest +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.task.ErrorReportTask +import pl.szczodrzynski.edziennik.data.api.task.IApiTask +import pl.szczodrzynski.edziennik.data.api.task.SzkolnyTask +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.toApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import kotlin.math.min +import kotlin.math.roundToInt + +class ApiService : Service() { + companion object { + const val TAG = "ApiService" + const val NOTIFICATION_API_CHANNEL_ID = "pl.szczodrzynski.edziennik.SYNC" + fun start(context: Context) { + context.startService(Intent(context, ApiService::class.java)) + } + fun startAndRequest(context: Context, request: Any) { + context.startService(Intent(context, ApiService::class.java)) + EventBus.getDefault().postSticky(request) + } + } + + private val app by lazy { applicationContext as App } + + private val syncingProfiles = mutableListOf() + + private var szkolnyTaskFinished = false + private val allTaskRequestList = mutableListOf() + private val taskQueue = mutableListOf() + private val errorList = mutableListOf() + + private var serviceClosed = false + set(value) { field = value; notification.serviceClosed = value } + private var taskCancelled = false + private var taskIsRunning = false + private var taskRunning: IApiTask? = null // for debug purposes + private var taskRunningId = -1 + private var taskStartTime = 0L + private var taskMaximumId = 0 + + private var taskProfileId = -1 + private var taskProgress = -1f + private var taskProgressText: String? = null + + private val notification by lazy { EdziennikNotification(app) } + + private var lastEventTime = System.currentTimeMillis() + private var taskCancelTries = 0 + + /* ______ _ _ _ _ _____ _ _ _ _ + | ____| | | (_) (_) | / ____| | | | | | | + | |__ __| |_____ ___ _ __ _ __ _| | __ | | __ _| | | |__ __ _ ___| | __ + | __| / _` |_ / |/ _ \ '_ \| '_ \| | |/ / | | / _` | | | '_ \ / _` |/ __| |/ / + | |___| (_| |/ /| | __/ | | | | | | | < | |___| (_| | | | |_) | (_| | (__| < + |______\__,_/___|_|\___|_| |_|_| |_|_|_|\_\ \_____\__,_|_|_|_.__/ \__,_|\___|_|\*/ + private val taskCallback = object : EdziennikCallback { + override fun onCompleted() { + lastEventTime = System.currentTimeMillis() + d(TAG, "Task $taskRunningId (profile $taskProfileId) finished in ${System.currentTimeMillis()-taskStartTime}") + EventBus.getDefault().postSticky(ApiTaskFinishedEvent(taskProfileId)) + clearTask() + + notification.setIdle().post() + runTask() + } + + override fun onError(apiError: ApiError) { + lastEventTime = System.currentTimeMillis() + d(TAG, "Task $taskRunningId threw an error - $apiError") + apiError.profileId = taskProfileId + + if (app.userActionManager.requiresUserAction(apiError)) { + app.userActionManager.sendToUser(apiError) + } + else { + EventBus.getDefault().postSticky(ApiTaskErrorEvent(apiError)) + errorList.add(apiError) + apiError.throwable?.printStackTrace() + } + + if (apiError.isCritical) { + taskRunning?.cancel() + notification.setCriticalError().post() + clearTask() + runTask() + } + else { + notification.addError().post() + } + } + + override fun onProgress(step: Float) { + lastEventTime = System.currentTimeMillis() + if (step <= 0) + return + if (taskProgress < 0) + taskProgress = 0f + taskProgress += step + taskProgress = min(100f, taskProgress) + d(TAG, "Task $taskRunningId progress: ${taskProgress.roundToInt()}%") + EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText)) + notification.setProgress(taskProgress).post() + } + + override fun onStartProgress(stringRes: Int) { + lastEventTime = System.currentTimeMillis() + taskProgressText = getString(stringRes) + d(TAG, "Task $taskRunningId progress: $taskProgressText") + EventBus.getDefault().post(ApiTaskProgressEvent(taskProfileId, taskProgress, taskProgressText)) + notification.setProgressText(taskProgressText).post() + } + } + + /* _______ _ _ _ + |__ __| | | | | (_) + | | __ _ ___| | __ _____ _____ ___ _ _| |_ _ ___ _ __ + | |/ _` / __| |/ / / _ \ \/ / _ \/ __| | | | __| |/ _ \| '_ \ + | | (_| \__ \ < | __/> < __/ (__| |_| | |_| | (_) | | | | + |_|\__,_|___/_|\_\ \___/_/\_\___|\___|\__,_|\__|_|\___/|_| |*/ + private fun runTask() { + checkIfTaskFrozen() + if (taskIsRunning) + return + if (taskCancelled || serviceClosed || (taskQueue.isEmpty() && szkolnyTaskFinished)) { + allCompleted() + return + } + + lastEventTime = System.currentTimeMillis() + + val task = if (taskQueue.isNotEmpty()) { + taskQueue.removeAt(0) + } else { + szkolnyTaskFinished = true + SzkolnyTask(app, syncingProfiles) + } + + task.taskId = ++taskMaximumId + task.prepare(app) + taskIsRunning = true + taskRunningId = task.taskId + taskRunning = task + taskProfileId = task.profileId + taskProgress = -1f + taskProgressText = task.taskName + + d(TAG, "Executing task $taskRunningId - ${task::class.java.name}") + + // update the notification + notification.setCurrentTask(taskRunningId, taskProgressText).post() + + // post an event + EventBus.getDefault().post(ApiTaskStartedEvent(taskProfileId, task.profile)) + + task.profile?.let { syncingProfiles.add(it) } + + taskStartTime = System.currentTimeMillis() + try { + when (task) { + is EdziennikTask -> task.run(app, taskCallback) + is ErrorReportTask -> task.run(app, taskCallback, notification, errorList) + is SzkolnyTask -> task.run(taskCallback) + } + } catch (e: Exception) { + taskCallback.onError(e.toApiError(TAG)) + } + } + + /** + * Check if a task is inactive for more than 30 seconds. + * If the user tries to cancel a task with no success at least three times, + * consider it frozen as well. + * + * This usually means it is broken and won't become active again. + * This method cancels the task and removes any pointers to it. + */ + private fun checkIfTaskFrozen(): Boolean { + if (System.currentTimeMillis() - lastEventTime > 30*1000 + || taskCancelTries >= 3) { + val time = System.currentTimeMillis() - lastEventTime + d(TAG, "!!! Task $taskRunningId froze for $time ms. $taskRunning") + clearTask() + return true + } + return false + } + + /** + * Stops the service if the current task is frozen/broken. + */ + private fun stopIfTaskFrozen() { + if (checkIfTaskFrozen()) { + allCompleted() + } + } + + /** + * Remove any task descriptors or pointers from the service. + */ + private fun clearTask() { + taskIsRunning = false + taskRunningId = -1 + taskRunning = null + taskProfileId = -1 + taskProgress = -1f + taskProgressText = null + taskCancelled = false + taskCancelTries = 0 + } + + private fun allCompleted() { + serviceClosed = true + EventBus.getDefault().postSticky(ApiTaskAllFinishedEvent()) + stopSelf() + } + + /* ______ _ ____ + | ____| | | | _ \ + | |____ _____ _ __ | |_| |_) |_ _ ___ + | __\ \ / / _ \ '_ \| __| _ <| | | / __| + | |___\ V / __/ | | | |_| |_) | |_| \__ \ + |______\_/ \___|_| |_|\__|____/ \__,_|__*/ + @Subscribe(sticky = true, threadMode = ThreadMode.ASYNC) + fun onApiTask(task: IApiTask) { + EventBus.getDefault().removeStickyEvent(task) + d(TAG, task.toString()) + + if (task is EdziennikTask) { + // fix for duplicated tasks, thank you EventBus + if (task.request in allTaskRequestList) + return + allTaskRequestList += task.request + } + + if (task is EdziennikTask) { + when (task.request) { + is EdziennikTask.SyncRequest -> app.db.profileDao().idsForSyncNow.forEach { + taskQueue += EdziennikTask.syncProfile(it) + } + is EdziennikTask.SyncProfileListRequest -> task.request.profileList.forEach { + taskQueue += EdziennikTask.syncProfile(it) + } + else -> { + taskQueue += task + } + } + } + else { + taskQueue += task + } + d(TAG, "EventBus received an IApiTask: $task") + d(TAG, "Current queue:") + taskQueue.forEach { + d(TAG, " - $it") + } + runTask() + } + + @Subscribe(sticky = true, threadMode = ThreadMode.ASYNC) + fun onTaskCancelRequest(request: TaskCancelRequest) { + EventBus.getDefault().removeStickyEvent(request) + d(TAG, request.toString()) + + taskCancelTries++ + taskCancelled = true + taskRunning?.cancel() + stopIfTaskFrozen() + } + @Subscribe(sticky = true, threadMode = ThreadMode.ASYNC) + fun onServiceCloseRequest(request: ServiceCloseRequest) { + EventBus.getDefault().removeStickyEvent(request) + d(TAG, request.toString()) + + serviceClosed = true + taskCancelled = true + taskRunning?.cancel() + allCompleted() + } + + /* _____ _ _ _ + / ____| (_) (_) | | + | (___ ___ _ ____ ___ ___ ___ _____ _____ _ __ _ __ _ __| | ___ ___ + \___ \ / _ \ '__\ \ / / |/ __/ _ \ / _ \ \ / / _ \ '__| '__| |/ _` |/ _ \/ __| + ____) | __/ | \ V /| | (_| __/ | (_) \ V / __/ | | | | | (_| | __/\__ \ + |_____/ \___|_| \_/ |_|\___\___| \___/ \_/ \___|_| |_| |_|\__,_|\___||__*/ + override fun onCreate() { + d(TAG, "Service created") + EventBus.getDefault().register(this) + notification.setIdle().setCloseAction() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + d(TAG, "Foreground service onStartCommand") + startForeground(app.notificationChannelsManager.sync.id, notification.notification) + return START_NOT_STICKY + } + + override fun onDestroy() { + d(TAG, "Service destroyed") + serviceClosed = true + EventBus.getDefault().unregister(this) + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt new file mode 100644 index 00000000..4fba7d9b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Constants.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-19. + */ + +package pl.szczodrzynski.edziennik.data.api + +import android.os.Build +import pl.szczodrzynski.edziennik.BuildConfig + +const val GET = 0 +const val POST = 1 + +val SYSTEM_USER_AGENT = System.getProperty("http.agent") ?: "Dalvik/2.1.0 Android" + +val SERVER_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME} $SYSTEM_USER_AGENT" + +const val FAKE_LIBRUS_API = "https://librus.szkolny.eu/api" +const val FAKE_LIBRUS_PORTAL = "https://librus.szkolny.eu" +const val FAKE_LIBRUS_AUTHORIZE = "https://librus.szkolny.eu/authorize.php" +const val FAKE_LIBRUS_LOGIN = "https://librus.szkolny.eu/login_action.php" +const val FAKE_LIBRUS_TOKEN = "https://librus.szkolny.eu/access_token.php" +const val FAKE_LIBRUS_ACCOUNT = "/synergia_accounts_fresh.php?login=" +const val FAKE_LIBRUS_ACCOUNTS = "/synergia_accounts.php" + +val LIBRUS_USER_AGENT = "${SYSTEM_USER_AGENT}LibrusMobileApp" +const val SYNERGIA_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/62.0" +const val LIBRUS_CLIENT_ID = "6XPsKf10LPz1nxgHQLcvZ1KM48DYzlBAhxipaXY8" +const val LIBRUS_REDIRECT_URL = "http://localhost/bar" +const val LIBRUS_AUTHORIZE_URL = "https://portal.librus.pl/oauth2/authorize?client_id=$LIBRUS_CLIENT_ID&redirect_uri=$LIBRUS_REDIRECT_URL&response_type=code" +const val LIBRUS_LOGIN_URL = "https://portal.librus.pl/rodzina/login/action" +const val LIBRUS_TOKEN_URL = "https://portal.librus.pl/oauth2/access_token" + +const val LIBRUS_ACCOUNT_URL = "/v2/SynergiaAccounts/fresh/" // + login +const val LIBRUS_ACCOUNTS_URL = "/v2/SynergiaAccounts" + +/** https://api.librus.pl/2.0 */ +const val LIBRUS_API_URL = "https://api.librus.pl/2.0" +/** https://portal.librus.pl/api */ +const val LIBRUS_PORTAL_URL = "https://portal.librus.pl/api" +/** https://api.librus.pl/OAuth/Token */ +const val LIBRUS_API_TOKEN_URL = "https://api.librus.pl/OAuth/Token" +/** https://api.librus.pl/OAuth/TokenJST */ +const val LIBRUS_API_TOKEN_JST_URL = "https://api.librus.pl/OAuth/TokenJST" +const val LIBRUS_API_AUTHORIZATION = "Mjg6ODRmZGQzYTg3YjAzZDNlYTZmZmU3NzdiNThiMzMyYjE=" +const val LIBRUS_API_SECRET_JST = "18b7c1ee08216f636a1b1a2440e68398" +const val LIBRUS_API_CLIENT_ID_JST = "49" +//const val LIBRUS_API_CLIENT_ID_JST_REFRESH = "42" + +const val LIBRUS_JST_DEMO_CODE = "68656A21" +const val LIBRUS_JST_DEMO_PIN = "1290" + +const val LIBRUS_SYNERGIA_URL = "https://synergia.librus.pl" +/** https://synergia.librus.pl/loguj/token/TOKEN/przenies */ +const val LIBRUS_SYNERGIA_TOKEN_LOGIN_URL = "https://synergia.librus.pl/loguj/token/TOKEN/przenies" + +const val LIBRUS_MESSAGES_URL = "https://wiadomosci.librus.pl/module" +const val LIBRUS_SANDBOX_URL = "https://sandbox.librus.pl/index.php?action=" + +const val IDZIENNIK_USER_AGENT = SYNERGIA_USER_AGENT +const val IDZIENNIK_WEB_URL = "https://iuczniowie.progman.pl/idziennik" +const val IDZIENNIK_WEB_LOGIN = "login.aspx" +const val IDZIENNIK_WEB_SETTINGS = "mod_panelRodzica/Ustawienia.aspx" +const val IDZIENNIK_WEB_HOME = "mod_panelRodzica/StronaGlowna.aspx" +const val IDZIENNIK_WEB_TIMETABLE = "mod_panelRodzica/plan/WS_Plan.asmx/pobierzPlanZajec" +const val IDZIENNIK_WEB_GRADES = "mod_panelRodzica/oceny/WS_ocenyUcznia.asmx/pobierzOcenyUcznia" +const val IDZIENNIK_WEB_MISSING_GRADES = "mod_panelRodzica/brak_ocen/WS_BrakOcenUcznia.asmx/pobierzBrakujaceOcenyUcznia" +const val IDZIENNIK_WEB_EXAMS = "mod_panelRodzica/sprawdziany/mod_sprawdzianyPanel.asmx/pobierzListe" +const val IDZIENNIK_WEB_HOMEWORK = "mod_panelRodzica/pracaDomowa/WS_pracaDomowa.asmx/pobierzPraceDomowe" +const val IDZIENNIK_WEB_NOTICES = "mod_panelRodzica/uwagi/WS_uwagiUcznia.asmx/pobierzUwagiUcznia" +const val IDZIENNIK_WEB_ATTENDANCE = "mod_panelRodzica/obecnosci/WS_obecnosciUcznia.asmx/pobierzObecnosciUcznia" +const val IDZIENNIK_WEB_ANNOUNCEMENTS = "mod_panelRodzica/tabOgl/WS_tablicaOgloszen.asmx/GetOgloszenia" +const val IDZIENNIK_WEB_MESSAGES_LIST = "mod_komunikator/WS_wiadomosci.asmx/PobierzListeWiadomosci" +const val IDZIENNIK_WEB_GET_MESSAGE = "mod_komunikator/WS_wiadomosci.asmx/PobierzWiadomosc" +const val IDZIENNIK_WEB_GET_RECIPIENT_LIST = "mod_komunikator/WS_wiadomosci.asmx/pobierzListeOdbiorcowPanelRodzic" +const val IDZIENNIK_WEB_SEND_MESSAGE = "mod_komunikator/WS_wiadomosci.asmx/WyslijWiadomosc" +const val IDZIENNIK_WEB_GET_ATTACHMENT = "mod_komunikator/Download.ashx" + +val IDZIENNIK_API_USER_AGENT = SYSTEM_USER_AGENT +const val IDZIENNIK_API_URL = "https://iuczniowie.progman.pl/idziennik/api" +const val IDZIENNIK_API_CURRENT_REGISTER = "Uczniowie/\$STUDENT_ID/AktualnyDziennik" +const val IDZIENNIK_API_GRADES = "Uczniowie/\$STUDENT_ID/Oceny/" /* + semester */ +const val IDZIENNIK_API_MESSAGES_INBOX = "Wiadomosci/Odebrane" +const val IDZIENNIK_API_MESSAGES_SENT = "Wiadomosci/Wyslane" + + +val MOBIDZIENNIK_USER_AGENT = SYSTEM_USER_AGENT + +const val VULCAN_API_USER_AGENT = "MobileUserAgent" +const val VULCAN_API_APP_NAME = "VULCAN-Android-ModulUcznia" +const val VULCAN_API_APP_VERSION = "19.4.1.436" +const val VULCAN_API_PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06" +const val VULCAN_API_PASSWORD_FAKELOG = "012345678901234567890123456789AB" +val VULCAN_API_DEVICE_NAME = "Szkolny.eu ${Build.MODEL}" + +const val VULCAN_API_ENDPOINT_CERTIFICATE = "mobile-api/Uczen.v3.UczenStart/Certyfikat" +const val VULCAN_API_ENDPOINT_STUDENT_LIST = "mobile-api/Uczen.v3.UczenStart/ListaUczniow" +const val VULCAN_API_ENDPOINT_DICTIONARIES = "mobile-api/Uczen.v3.Uczen/Slowniki" +const val VULCAN_API_ENDPOINT_TIMETABLE = "mobile-api/Uczen.v3.Uczen/PlanLekcjiZeZmianami" +const val VULCAN_API_ENDPOINT_GRADES = "mobile-api/Uczen.v3.Uczen/Oceny" +const val VULCAN_API_ENDPOINT_GRADES_PROPOSITIONS = "mobile-api/Uczen.v3.Uczen/OcenyPodsumowanie" +const val VULCAN_API_ENDPOINT_EVENTS = "mobile-api/Uczen.v3.Uczen/Sprawdziany" +const val VULCAN_API_ENDPOINT_HOMEWORK = "mobile-api/Uczen.v3.Uczen/ZadaniaDomowe" +const val VULCAN_API_ENDPOINT_NOTICES = "mobile-api/Uczen.v3.Uczen/UwagiUcznia" +const val VULCAN_API_ENDPOINT_ATTENDANCE = "mobile-api/Uczen.v3.Uczen/Frekwencje" +const val VULCAN_API_ENDPOINT_MESSAGES_RECEIVED = "mobile-api/Uczen.v3.Uczen/WiadomosciOdebrane" +const val VULCAN_API_ENDPOINT_MESSAGES_SENT = "mobile-api/Uczen.v3.Uczen/WiadomosciWyslane" +const val VULCAN_API_ENDPOINT_MESSAGES_CHANGE_STATUS = "mobile-api/Uczen.v3.Uczen/ZmienStatusWiadomosci" +const val VULCAN_API_ENDPOINT_MESSAGES_ADD = "mobile-api/Uczen.v3.Uczen/DodajWiadomosc" +const val VULCAN_API_ENDPOINT_PUSH = "mobile-api/Uczen.v3.Uczen/UstawPushToken" + +const val EDUDZIENNIK_USER_AGENT = "Szkolny.eu/${BuildConfig.VERSION_NAME}" diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EdziennikNotification.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EdziennikNotification.kt new file mode 100644 index 00000000..8582fc03 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EdziennikNotification.kt @@ -0,0 +1,140 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-1. + */ + +package pl.szczodrzynski.edziennik.data.api + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.PRIORITY_MIN +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import kotlin.math.roundToInt + + +class EdziennikNotification(val app: App) { + private val notificationManager by lazy { app.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } + + private val notificationBuilder: NotificationCompat.Builder by lazy { + NotificationCompat.Builder(app, ApiService.NOTIFICATION_API_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setPriority(PRIORITY_MIN) + .setOngoing(true) + .setLocalOnly(true) + } + + val notification: Notification + get() = notificationBuilder.build() + + private var errorCount = 0 + private var criticalErrorCount = 0 + var serviceClosed = false + + private fun cancelPendingIntent(taskId: Int): PendingIntent { + val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN") + intent.putExtra("task", "TaskCancelRequest") + intent.putExtra("taskId", taskId) + return PendingIntent.getBroadcast(app, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent + } + private val closePendingIntent: PendingIntent + get() { + val intent = Intent("pl.szczodrzynski.edziennik.SZKOLNY_MAIN") + intent.putExtra("task", "ServiceCloseRequest") + return PendingIntent.getBroadcast(app, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) as PendingIntent + } + + private fun errorCountText(): String? { + var result = "" + if (criticalErrorCount > 0) { + result += app.resources.getQuantityString(R.plurals.critical_errors_format, criticalErrorCount, criticalErrorCount) + } + if (criticalErrorCount > 0 && errorCount > 0) { + result += ", " + } + if (errorCount > 0) { + result += app.resources.getQuantityString(R.plurals.normal_errors_format, errorCount, errorCount) + } + return if (result.isEmpty()) null else result + } + + fun setIdle(): EdziennikNotification { + notificationBuilder.setContentTitle(app.getString(R.string.edziennik_notification_api_title)) + notificationBuilder.setProgress(0, 0, false) + notificationBuilder.apply { + val str = app.getString(R.string.edziennik_notification_api_text) + setStyle(NotificationCompat.BigTextStyle().bigText(str)) + setContentText(str) + } + setCloseAction() + return this + } + + fun addError(): EdziennikNotification { + errorCount++ + return this + } + fun setCriticalError(): EdziennikNotification { + criticalErrorCount++ + notificationBuilder.setContentTitle(app.getString(R.string.edziennik_notification_api_error_title)) + notificationBuilder.setProgress(0, 0, false) + notificationBuilder.apply { + val str = errorCountText() + setStyle(NotificationCompat.BigTextStyle().bigText(str)) + setContentText(str) + } + setCloseAction() + return this + } + + fun setProgress(progress: Float): EdziennikNotification { + notificationBuilder.setProgress(100, progress.roundToInt(), progress < 0f) + return this + } + fun setProgressText(progressText: String?): EdziennikNotification { + notificationBuilder.setContentTitle(progressText) + return this + } + + fun setCurrentTask(taskId: Int, progressText: String?): EdziennikNotification { + notificationBuilder.setProgress(100, 0, true) + notificationBuilder.setContentTitle(progressText) + notificationBuilder.apply { + val str = errorCountText() + setStyle(NotificationCompat.BigTextStyle().bigText(str)) + setContentText(str) + } + setCancelAction(taskId) + return this + } + + fun setCloseAction(): EdziennikNotification { + notificationBuilder.mActions.clear() + notificationBuilder.addAction( + NotificationCompat.Action( + R.drawable.ic_notification, + app.getString(R.string.edziennik_notification_api_close), + closePendingIntent + )) + return this + } + private fun setCancelAction(taskId: Int) { + notificationBuilder.mActions.clear() + notificationBuilder.addAction( + NotificationCompat.Action( + R.drawable.ic_notification, + app.getString(R.string.edziennik_notification_api_cancel), + cancelPendingIntent(taskId) + )) + } + + fun post() { + if (serviceClosed) + return + notificationManager.notify(app.notificationChannelsManager.sync.id, notification) + } + +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt new file mode 100644 index 00000000..7f882123 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/EndpointChooser.kt @@ -0,0 +1,117 @@ +package pl.szczodrzynski.edziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.api.models.Feature +import pl.szczodrzynski.edziennik.data.api.models.LoginMethod +import pl.szczodrzynski.edziennik.data.db.entity.EndpointTimer +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_NEVER + +fun Data.prepare(loginMethods: List, features: List, featureIds: List, viewId: Int?, onlyEndpoints: List?) { + val data = this + + val possibleLoginMethods = data.loginMethods.toMutableList() + + for (loginMethod in loginMethods) { + if (loginMethod.isPossible(profile, loginStore)) + possibleLoginMethods += loginMethod.loginMethodId + } + + //var highestLoginMethod = 0 + var endpointList = mutableListOf() + val requiredLoginMethods = mutableListOf() + + data.targetEndpointIds.clear() + data.targetLoginMethodIds.clear() + + // get all endpoints for every feature, only if possible to login and possible/necessary to sync + for (featureId in featureIds) { + features.filter { + it.featureId == featureId // feature ID matches + && possibleLoginMethods.containsAll(it.requiredLoginMethods) // is possible to login + && it.shouldSync?.invoke(data) ?: true // is necessary/possible to sync + }.let { + endpointList.addAll(it) + } + } + + val timestamp = System.currentTimeMillis() + + endpointList = endpointList + // sort the endpoint list by feature ID and priority + .sortedWith(compareBy(Feature::featureId, Feature::priority)) + // select only the most important endpoint for each feature + .distinctBy { it.featureId } + .toMutableList() + // add all endpoint IDs and required login methods, filtering using timers + .onEach { feature -> + feature.endpointIds.forEach { endpoint -> + if (onlyEndpoints?.contains(endpoint.first) == false) + return@forEach + (data.endpointTimers + .singleOrNull { it.endpointId == endpoint.first } ?: EndpointTimer(data.profile?.id + ?: -1, endpoint.first)) + .let { timer -> + if ( + onlyEndpoints?.contains(endpoint.first) == true || + timer.nextSync == SYNC_ALWAYS || + viewId != null && timer.viewId == viewId || + timer.nextSync != SYNC_NEVER && timer.nextSync < timestamp + ) { + data.targetEndpointIds[endpoint.first] = timer.lastSync + requiredLoginMethods.add(endpoint.second) + } + } + } + } + + // check every login method for any dependencies + for (loginMethodId in requiredLoginMethods) { + var requiredLoginMethod: Int? = loginMethodId + while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { + loginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { loginMethod -> + if (requiredLoginMethod != null) + data.targetLoginMethodIds.add(requiredLoginMethod!!) + requiredLoginMethod = loginMethod.requiredLoginMethod(data.profile, data.loginStore) + } + } + } + + // sort and distinct every login method and endpoint + data.targetLoginMethodIds = data.targetLoginMethodIds.toHashSet().toMutableList() + data.targetLoginMethodIds.sort() + + //data.targetEndpointIds = data.targetEndpointIds.toHashSet().toMutableList() + //data.targetEndpointIds.sort() + + progressCount = targetLoginMethodIds.size + targetEndpointIds.size + progressStep = if (progressCount <= 0) 0f else 100f / progressCount.toFloat() +} + +fun Data.prepareFor(loginMethods: List, loginMethodId: Int) { + val possibleLoginMethods = this.loginMethods.toMutableList() + + loginMethods.forEach { + if (it.isPossible(profile, loginStore)) + possibleLoginMethods += it.loginMethodId + } + + targetLoginMethodIds.clear() + + // check the login method for any dependencies + var requiredLoginMethod: Int? = loginMethodId + while (requiredLoginMethod != LOGIN_METHOD_NOT_NEEDED) { + loginMethods.singleOrNull { it.loginMethodId == requiredLoginMethod }?.let { + if (requiredLoginMethod != null) + targetLoginMethodIds.add(requiredLoginMethod!!) + requiredLoginMethod = it.requiredLoginMethod(profile, loginStore) + } + } + + // sort and distinct every login method + targetLoginMethodIds = targetLoginMethodIds.toHashSet().toMutableList() + targetLoginMethodIds.sort() + + progressCount = 0 + progressStep = 0f +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt new file mode 100644 index 00000000..1d86abf6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Errors.kt @@ -0,0 +1,210 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-21. + */ + +package pl.szczodrzynski.edziennik.data.api + +/*const val CODE_OTHER = 0 +const val CODE_OK = 1 +const val CODE_NO_INTERNET = 10 +const val CODE_SSL_ERROR = 13 +const val CODE_ARCHIVED = 5 +const val CODE_MAINTENANCE = 6 +const val CODE_LOGIN_ERROR = 7 +const val CODE_ACCOUNT_MISMATCH = 8 +const val CODE_APP_SERVER_ERROR = 9 +const val CODE_MULTIACCOUNT_SETUP = 12 +const val CODE_TIMEOUT = 11 +const val CODE_PROFILE_NOT_FOUND = 14 +const val CODE_ATTACHMENT_NOT_AVAILABLE = 28 +const val CODE_INVALID_LOGIN = 2 +const val CODE_INVALID_SERVER_ADDRESS = 21 +const val CODE_INVALID_SCHOOL_NAME = 22 +const val CODE_INVALID_DEVICE = 23 +const val CODE_OLD_PASSWORD = 4 +const val CODE_INVALID_TOKEN = 24 +const val CODE_EXPIRED_TOKEN = 27 +const val CODE_INVALID_SYMBOL = 25 +const val CODE_INVALID_PIN = 26 +const val CODE_LIBRUS_NOT_ACTIVATED = 29 +const val CODE_SYNERGIA_NOT_ACTIVATED = 32 +const val CODE_LIBRUS_DISCONNECTED = 31 +const val CODE_PROFILE_ARCHIVED = 30*/ + +const val ERROR_APP_CRASH = 1 +const val ERROR_EXCEPTION = 2 +const val ERROR_API_EXCEPTION = 3 +const val ERROR_MESSAGE_NOT_SENT = 10 + +const val ERROR_REQUEST_FAILURE = 50 +const val ERROR_REQUEST_HTTP_400 = 51 +const val ERROR_REQUEST_HTTP_401 = 52 +const val ERROR_REQUEST_HTTP_403 = 53 +const val ERROR_REQUEST_HTTP_404 = 54 +const val ERROR_REQUEST_HTTP_405 = 55 +const val ERROR_REQUEST_HTTP_410 = 56 +const val ERROR_REQUEST_HTTP_424 = 57 +const val ERROR_REQUEST_HTTP_500 = 58 +const val ERROR_REQUEST_HTTP_503 = 59 +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_REQUEST_FAILURE_SSL_ERROR = 63 +const val ERROR_RESPONSE_EMPTY = 100 +const val ERROR_LOGIN_DATA_MISSING = 101 +const val ERROR_PROFILE_MISSING = 105 +const val ERROR_PROFILE_ARCHIVED = 106 +const val ERROR_INVALID_LOGIN_MODE = 110 +const val ERROR_LOGIN_METHOD_NOT_SATISFIED = 111 +const val ERROR_NOT_IMPLEMENTED = 112 +const val ERROR_FILE_DOWNLOAD = 113 + +const val ERROR_NO_STUDENTS_IN_ACCOUNT = 115 + +const val ERROR_CAPTCHA_NEEDED = 3000 +const val ERROR_CAPTCHA_LIBRUS_PORTAL = 3001 + +const val CODE_INTERNAL_LIBRUS_ACCOUNT_410 = 120 +const val CODE_INTERNAL_LIBRUS_SYNERGIA_EXPIRED = 121 +const val ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED = 124 +const val ERROR_LOGIN_LIBRUS_API_CONNECTION_PROBLEMS = 125 +const val ERROR_LOGIN_LIBRUS_API_INVALID_CLIENT = 126 +const val ERROR_LOGIN_LIBRUS_API_REG_ACCEPT_NEEDED = 127 +const val ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR = 128 +const val ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED = 129 +const val ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN = 130 +const val ERROR_LOGIN_LIBRUS_API_OTHER = 131 +const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING = 132 +const val ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED = 133 +const val ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR = 134 +const val ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING = 139 +const val ERROR_LIBRUS_API_TOKEN_EXPIRED = 140 +const val ERROR_LIBRUS_API_INSUFFICIENT_SCOPES = 141 +const val ERROR_LIBRUS_API_OTHER = 142 +const val ERROR_LIBRUS_API_ACCESS_DENIED = 143 +const val ERROR_LIBRUS_API_RESOURCE_NOT_FOUND = 144 +const val ERROR_LIBRUS_API_DATA_NOT_FOUND = 145 +const val ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC = 146 +const val ERROR_LIBRUS_API_RESOURCE_ACCESS_DENIED = 147 +const val ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS = 148 +const val ERROR_LIBRUS_API_INCORRECT_ENDPOINT = 149 +const val ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE = 150 +const val ERROR_LIBRUS_API_NOTES_NOT_ACTIVE = 151 +const val ERROR_LOGIN_LIBRUS_SYNERGIA_NO_TOKEN = 152 +const val ERROR_LOGIN_LIBRUS_SYNERGIA_TOKEN_INVALID = 153 +const val ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID = 154 +const val ERROR_LIBRUS_MESSAGES_ACCESS_DENIED = 155 +const val ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED = 156 +const val ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID = 157 +const val ERROR_LIBRUS_PORTAL_ACCESS_DENIED = 158 +const val ERROR_LIBRUS_PORTAL_API_DISABLED = 159 +const val ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED = 160 +const val ERROR_LIBRUS_PORTAL_OTHER = 161 +const val ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND = 162 +const val ERROR_LOGIN_LIBRUS_PORTAL_OTHER = 163 +const val ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED = 164 +const val ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED = 165 +const val ERROR_LOGIN_LIBRUS_PORTAL_NO_CLIENT_ID = 166 +const val ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE = 167 +const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH = 168 +const val ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT = 169 +const val ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT = 170 +const val ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID = 171 +const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID = 172 +const val ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED = 173 +const val ERROR_LIBRUS_SYNERGIA_OTHER = 174 +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_LIBRUS_API_NOTICEBOARD_PROBLEM = 183 +const val ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED = 184 +const val ERROR_LIBRUS_API_DEVICE_REGISTERED = 185 +const val ERROR_LIBRUS_MESSAGES_NOT_FOUND = 186 +const val ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST = 187 + +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_LOGIN = 201 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OLD_PASSWORD = 202 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_DEVICE = 203 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_ARCHIVED = 204 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_MAINTENANCE = 205 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_INVALID_ADDRESS = 206 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_OTHER = 210 +const val ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED = 211 +const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY = 212 +const val ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE = 216 +const val ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID = 213 +const val ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE = 214 +const val ERROR_LOGIN_MOBIDZIENNIK_WEB_NO_SESSION_ID = 215 +const val ERROR_LOGIN_MOBIDZIENNIK_API2_INVALID_LOGIN = 216 +const val ERROR_LOGIN_MOBIDZIENNIK_API2_OTHER = 217 +const val ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM = 218 + +const val ERROR_LOGIN_VULCAN_INVALID_SYMBOL = 301 +const val ERROR_LOGIN_VULCAN_INVALID_TOKEN = 302 +const val ERROR_LOGIN_VULCAN_INVALID_PIN = 309 +const val ERROR_LOGIN_VULCAN_INVALID_PIN_0_REMAINING = 310 +const val ERROR_LOGIN_VULCAN_INVALID_PIN_1_REMAINING = 311 +const val ERROR_LOGIN_VULCAN_INVALID_PIN_2_REMAINING = 312 +const val ERROR_LOGIN_VULCAN_EXPIRED_TOKEN = 321 +const val ERROR_LOGIN_VULCAN_OTHER = 322 +const val ERROR_LOGIN_VULCAN_ONLY_KINDERGARTEN = 330 +const val ERROR_LOGIN_VULCAN_NO_PUPILS = 331 +const val ERROR_VULCAN_API_MAINTENANCE = 340 +const val ERROR_VULCAN_API_BAD_REQUEST = 341 +const val ERROR_VULCAN_API_OTHER = 342 + +const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN = 401 +const val ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME = 402 +const val ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 403 +const val ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE = 404 +const val ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR = 405 +const val ERROR_LOGIN_IDZIENNIK_WEB_OTHER = 410 +const val ERROR_LOGIN_IDZIENNIK_WEB_API_NO_ACCESS = 411 /* {"d":{"__type":"mds.Web.mod_komunikator.WS_mod_wiadomosci+detailWiadomosci","Wiadomosc":{"_recordId":0,"DataNadania":null,"DataOdczytania":null,"Nadawca":null,"ListaOdbiorcow":[],"Tytul":null,"Text":null,"ListaZal":[]},"Bledy":{"__type":"mds.Module.Globalne+sBledy","CzyJestBlad":true,"ListaBledow":["Nie masz dostępu do tych zasobów!"],"ListaKodowBledow":[]},"czyJestWiecej":false}} */ +const val ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION = 420 +const val ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH = 421 +const val ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER = 422 +const val ERROR_IDZIENNIK_WEB_ACCESS_DENIED = 430 +const val ERROR_IDZIENNIK_WEB_OTHER = 431 +const val ERROR_IDZIENNIK_WEB_MAINTENANCE = 432 +const val ERROR_IDZIENNIK_WEB_SERVER_ERROR = 433 +const val ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED = 434 +const val ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR = 440 +const val ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA = 441 +const val ERROR_IDZIENNIK_API_ACCESS_DENIED = 450 +const val ERROR_IDZIENNIK_API_OTHER = 451 +const val ERROR_IDZIENNIK_API_NO_REGISTER = 452 +const val ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION = 453 + +const val ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN = 501 +const val ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER = 510 +const val ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID = 511 +const val ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS = 521 +const val ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED = 522 +const val ERROR_EDUDZIENNIK_WEB_TEAM_MISSING = 530 + +const val ERROR_TEMPLATE_WEB_OTHER = 801 + +const val EXCEPTION_API_TASK = 900 +const val EXCEPTION_LOGIN_LIBRUS_API_TOKEN = 901 +const val EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN = 902 +const val EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN = 903 +const val EXCEPTION_LIBRUS_API_REQUEST = 904 +const val EXCEPTION_LIBRUS_SYNERGIA_REQUEST = 905 +const val EXCEPTION_MOBIDZIENNIK_WEB_REQUEST = 906 +const val EXCEPTION_VULCAN_API_REQUEST = 907 +const val EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST = 908 +const val EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST = 909 +const val EXCEPTION_NOTIFY = 910 +const val EXCEPTION_LIBRUS_MESSAGES_REQUEST = 911 +const val EXCEPTION_IDZIENNIK_WEB_REQUEST = 912 +const val EXCEPTION_IDZIENNIK_WEB_API_REQUEST = 913 +const val EXCEPTION_IDZIENNIK_API_REQUEST = 914 +const val EXCEPTION_EDUDZIENNIK_WEB_REQUEST = 920 +const val EXCEPTION_EDUDZIENNIK_FILE_REQUEST = 921 + +const val LOGIN_NO_ARGUMENTS = 1201 diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt new file mode 100644 index 00000000..0e75753b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Features.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-29. + */ + +package pl.szczodrzynski.edziennik.data.api + +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_ATTENDANCE +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_BEHAVIOUR +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_GRADES +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOME +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_TIMETABLE +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_SENT + +internal const val FEATURE_TIMETABLE = 1 +internal const val FEATURE_AGENDA = 2 +internal const val FEATURE_GRADES = 3 +internal const val FEATURE_HOMEWORK = 4 +internal const val FEATURE_BEHAVIOUR = 5 +internal const val FEATURE_ATTENDANCE = 6 +internal const val FEATURE_MESSAGES_INBOX = 7 +internal const val FEATURE_MESSAGES_SENT = 8 +internal const val FEATURE_ANNOUNCEMENTS = 9 + +internal const val FEATURE_ALWAYS_NEEDED = 100 +internal const val FEATURE_STUDENT_INFO = 101 +internal const val FEATURE_STUDENT_NUMBER = 109 +internal const val FEATURE_SCHOOL_INFO = 102 +internal const val FEATURE_CLASS_INFO = 103 +internal const val FEATURE_TEAM_INFO = 104 +internal const val FEATURE_LUCKY_NUMBER = 105 +internal const val FEATURE_TEACHERS = 106 +internal const val FEATURE_SUBJECTS = 107 +internal const val FEATURE_CLASSROOMS = 108 +internal const val FEATURE_PUSH_CONFIG = 120 + +object Features { + private fun getAllNecessary(): List = listOf( + FEATURE_ALWAYS_NEEDED, + FEATURE_PUSH_CONFIG, + FEATURE_STUDENT_INFO, + FEATURE_STUDENT_NUMBER, + FEATURE_SCHOOL_INFO, + FEATURE_CLASS_INFO, + FEATURE_TEAM_INFO, + FEATURE_LUCKY_NUMBER, + FEATURE_TEACHERS, + FEATURE_SUBJECTS, + FEATURE_CLASSROOMS) + + private fun getAllFeatures(): List = listOf( + FEATURE_TIMETABLE, + FEATURE_AGENDA, + FEATURE_GRADES, + FEATURE_HOMEWORK, + FEATURE_BEHAVIOUR, + FEATURE_ATTENDANCE, + FEATURE_MESSAGES_INBOX, + FEATURE_MESSAGES_SENT, + FEATURE_ANNOUNCEMENTS) + + fun getAllIds(): List = getAllFeatures() + getAllNecessary() + + fun getIdsByView(targetId: Int, targetType: Int): List { + return (when (targetId) { + DRAWER_ITEM_HOME -> getAllFeatures() + DRAWER_ITEM_TIMETABLE -> listOf(FEATURE_TIMETABLE) + DRAWER_ITEM_AGENDA -> listOf(FEATURE_AGENDA) + DRAWER_ITEM_GRADES -> listOf(FEATURE_GRADES) + DRAWER_ITEM_MESSAGES -> when (targetType) { + TYPE_RECEIVED -> listOf(FEATURE_MESSAGES_INBOX) + TYPE_SENT -> listOf(FEATURE_MESSAGES_SENT) + else -> listOf(FEATURE_MESSAGES_INBOX, FEATURE_MESSAGES_SENT) + } + DRAWER_ITEM_HOMEWORK -> listOf(FEATURE_HOMEWORK) + DRAWER_ITEM_BEHAVIOUR -> listOf(FEATURE_BEHAVIOUR) + DRAWER_ITEM_ATTENDANCE -> listOf(FEATURE_ATTENDANCE) + DRAWER_ITEM_ANNOUNCEMENTS -> listOf(FEATURE_ANNOUNCEMENTS) + else -> getAllFeatures() + } + getAllNecessary()).sorted() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt new file mode 100644 index 00000000..66b764cd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/LoginMethods.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-20. + */ + +package pl.szczodrzynski.edziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginMessages +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginSynergia +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginApi2 +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.template.login.TemplateLoginWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.vulcan.login.VulcanLoginApi +import pl.szczodrzynski.edziennik.data.api.models.LoginMethod + +// librus +// mobidziennik +// idziennik +// vulcan +// mobireg + +const val SYNERGIA_API_ENABLED = false + + + +const val LOGIN_TYPE_IDZIENNIK = 3 + +const val LOGIN_TYPE_TEMPLATE = 21 + +// LOGIN MODES +const val LOGIN_MODE_IDZIENNIK_WEB = 0 + +const val LOGIN_MODE_TEMPLATE_WEB = 0 + +// LOGIN METHODS +const val LOGIN_METHOD_NOT_NEEDED = -1 +const val LOGIN_METHOD_IDZIENNIK_WEB = 100 +const val LOGIN_METHOD_IDZIENNIK_API = 200 +const val LOGIN_METHOD_TEMPLATE_WEB = 100 +const val LOGIN_METHOD_TEMPLATE_API = 200 + +const val LOGIN_TYPE_LIBRUS = 2 +const val LOGIN_MODE_LIBRUS_EMAIL = 0 +const val LOGIN_MODE_LIBRUS_SYNERGIA = 1 +const val LOGIN_MODE_LIBRUS_JST = 2 +const val LOGIN_METHOD_LIBRUS_PORTAL = 100 +const val LOGIN_METHOD_LIBRUS_API = 200 +const val LOGIN_METHOD_LIBRUS_SYNERGIA = 300 +const val LOGIN_METHOD_LIBRUS_MESSAGES = 400 +val librusLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_PORTAL, LibrusLoginPortal::class.java) + .withIsPossible { _, loginStore -> + loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL + } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_API, LibrusLoginApi::class.java) + .withIsPossible { _, loginStore -> + loginStore.mode != LOGIN_MODE_LIBRUS_SYNERGIA || SYNERGIA_API_ENABLED + } + .withRequiredLoginMethod { _, loginStore -> + if (loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) LOGIN_METHOD_LIBRUS_PORTAL else LOGIN_METHOD_NOT_NEEDED + }, + + LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_SYNERGIA, LibrusLoginSynergia::class.java) + .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } + .withRequiredLoginMethod { profile, _ -> + if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_API else LOGIN_METHOD_NOT_NEEDED + }, + + LoginMethod(LOGIN_TYPE_LIBRUS, LOGIN_METHOD_LIBRUS_MESSAGES, LibrusLoginMessages::class.java) + .withIsPossible { _, loginStore -> !loginStore.hasLoginData("fakeLogin") } + .withRequiredLoginMethod { profile, _ -> + if (profile?.hasStudentData("accountPassword") == false || true) LOGIN_METHOD_LIBRUS_SYNERGIA else LOGIN_METHOD_NOT_NEEDED + } +) + +const val LOGIN_TYPE_MOBIDZIENNIK = 1 +const val LOGIN_MODE_MOBIDZIENNIK_WEB = 0 +const val LOGIN_METHOD_MOBIDZIENNIK_WEB = 100 +const val LOGIN_METHOD_MOBIDZIENNIK_API2 = 300 +val mobidziennikLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_WEB, MobidziennikLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_MOBIDZIENNIK, LOGIN_METHOD_MOBIDZIENNIK_API2, MobidziennikLoginApi2::class.java) + .withIsPossible { profile, _ -> profile?.getStudentData("email", null) != null } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } +) + +const val LOGIN_TYPE_VULCAN = 4 +const val LOGIN_MODE_VULCAN_API = 0 +const val LOGIN_MODE_VULCAN_WEB = 1 +const val LOGIN_METHOD_VULCAN_WEB_MAIN = 100 +const val LOGIN_METHOD_VULCAN_WEB_NEW = 200 +const val LOGIN_METHOD_VULCAN_WEB_OLD = 300 +const val LOGIN_METHOD_VULCAN_WEB_MESSAGES = 400 +const val LOGIN_METHOD_VULCAN_API = 500 +val vulcanLoginMethods = listOf( + /*LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_MAIN, VulcanLoginWebMain::class.java) + .withIsPossible { _, _ -> false } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_NEW, VulcanLoginWebNew::class.java) + .withIsPossible { _, _ -> false } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN }, + + LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_WEB_OLD, VulcanLoginWebOld::class.java) + .withIsPossible { _, _ -> false } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_VULCAN_WEB_MAIN },*/ + + LoginMethod(LOGIN_TYPE_VULCAN, LOGIN_METHOD_VULCAN_API, VulcanLoginApi::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, loginStore -> + if (loginStore.mode == LOGIN_MODE_VULCAN_WEB) LOGIN_METHOD_VULCAN_WEB_NEW else LOGIN_METHOD_NOT_NEEDED + } +) + +val idziennikLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_WEB, IdziennikLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_IDZIENNIK, LOGIN_METHOD_IDZIENNIK_API, IdziennikLoginApi::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_IDZIENNIK_WEB } +) + +const val LOGIN_TYPE_EDUDZIENNIK = 5 +const val LOGIN_METHOD_EDUDZIENNIK_WEB = 100 +val edudziennikLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_EDUDZIENNIK, LOGIN_METHOD_EDUDZIENNIK_WEB, EdudziennikLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED } +) + +val templateLoginMethods = listOf( + LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_WEB, TemplateLoginWeb::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_NOT_NEEDED }, + + LoginMethod(LOGIN_TYPE_TEMPLATE, LOGIN_METHOD_TEMPLATE_API, TemplateLoginApi::class.java) + .withIsPossible { _, _ -> true } + .withRequiredLoginMethod { _, _ -> LOGIN_METHOD_TEMPLATE_WEB } +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt new file mode 100644 index 00000000..633370f5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/Regexes.kt @@ -0,0 +1,219 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-6. + */ + +package pl.szczodrzynski.edziennik.data.api + +import kotlin.text.RegexOption.DOT_MATCHES_ALL +import kotlin.text.RegexOption.IGNORE_CASE + +object Regexes { + val STYLE_CSS_COLOR by lazy { + """color: (\w+);?""".toRegex() + } + + + + val MOBIDZIENNIK_GRADES_SUBJECT_NAME by lazy { + """\n*\s*(.+?)\s*\n*(?:<.*?)??
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_COLOR by lazy { + """background-color:([#A-Fa-f0-9]+);""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_CATEGORY by lazy { + """> (.+?):
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_CLASS_AVERAGE by lazy { + """Średnia ocen:.*([0-9]*\.?[0-9]*)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_ADDED_DATE by lazy { + """Wpisano:.*.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_COUNT_TO_AVG by lazy { + """Liczona do średniej:.*?nie
""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_GRADES_DETAILS by lazy { + """(.+?).*?.+?.*?(?:\((.+?)\).*?)?.*?Wartość oceny:.*?([0-9.]+).*?Wpisał\(a\):.*?(.+?).*?(?:Komentarz:.*?(.+?))?""".toRegex(DOT_MATCHES_ALL) + } + + val MOBIDZIENNIK_EVENT_TYPE by lazy { + """\(([0-9A-ząęóżźńśłć]*?)\)$""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_LUCKY_NUMBER by lazy { + """class="szczesliwy_numerek".*>0*([0-9]+)(?:/0*[0-9]+)*""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_CLASS_CALENDAR by lazy { + """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(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_MESSAGE_SENT_READ_DATE by lazy { + """.+?,\s([0-9]+)\s(.+?)\s([0-9]{4}),\sgodzina\s([0-9:]+)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_MESSAGE_ATTACHMENT by lazy { + """href="https://.+?\.mobidziennik.pl/.+?&(?:amp;)?zalacznik=([0-9]+)"(?:.+?(.+?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_LESSON_COUNT by lazy { + """rel="([0-9-]{10})" colspan="([0-9]+)"""".toRegex() + } + val MOBIDZIENNIK_ATTENDANCE_ENTRIES by lazy { + """font-size:.+?class=".*?">(.*?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_RANGE by lazy { + """([0-9:]+) - .+? (.+?)""".toRegex(DOT_MATCHES_ALL) + } + val MOBIDZIENNIK_ATTENDANCE_LESSON by lazy { + """(.+?) - (.*?).+?.+?\((.+?), .+?(.+?)\)""".toRegex(DOT_MATCHES_ALL) + } + + + + val IDZIENNIK_LOGIN_HIDDEN_FIELDS by lazy { + """""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_ERROR by lazy { + """id="spanErrorMessage">(.*?)(.+?)""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_IS_PARENT by lazy { + """id="ctl00_CzyRodzic" value="([01])" />""".toRegex() + } + val IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR by lazy { + """name="ctl00\${"$"}dxComboRokSzkolny".+?selected="selected".*?value="([0-9]+)">([0-9]+)/([0-9]+)<""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_STUDENT_SELECT by lazy { + """""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_LOGIN_FIRST_STUDENT by lazy { + """(.+?)\s(.+?)\s*\((.+?),\s*(.+?)\)""".toRegex(DOT_MATCHES_ALL) + } + val IDZIENNIK_MESSAGES_RECIPIENT_PARENT by lazy { + """(.+?)\s\((.+)\)""".toRegex() + } + /*Szczęśliwy los na dzisiaj to 19. Los na jutro to 22*/ + val IDZIENNIK_WEB_LUCKY_NUMBER by lazy { + """dzisiaj to ([0-9]+)""".toRegex() + } + val IDZIENNIK_WEB_SELECTED_REGISTER by lazy { + """selected="selected" value="([0-9]+)" data-id-ucznia""".toRegex() + } + + + + val VULCAN_SHIFT_ANNOTATION by lazy { + """\(przeniesiona (z|na) lekcj[ię] ([0-9]+), (.+)\)""".toRegex() + } + + + + val LIBRUS_ATTACHMENT_KEY by lazy { + """singleUseKey=([0-9A-z_]+)""".toRegex() + } + + + + val EDUDZIENNIK_STUDENTS_START by lazy { + """
  • (.*?)""".toRegex() + } + val EDUDZIENNIK_ACCOUNT_NAME_START by lazy { + """(.*?)""".toRegex() + } + val EDUDZIENNIK_SUBJECTS_START by lazy { + """(.+?)""".toRegex() + } + + val EDUDZIENNIK_ATTENDANCE_ENTRIES by lazy { + """(.+?)""".toRegex() + } + val EDUDZIENNIK_ATTENDANCE_TYPES by lazy { + """
    .*?

    (.*?)

    """.toRegex(DOT_MATCHES_ALL) + } + val EDUDZIENNIK_ATTENDANCE_TYPE by lazy { + """\((.+?)\) (.+)""".toRegex() + } + + val EDUDZIENNIK_ANNOUNCEMENT_DESCRIPTION by lazy { + """
    .*?

    (.*?)

    """.toRegex(DOT_MATCHES_ALL) + } + + val EDUDZIENNIK_SUBJECT_ID by lazy { + """/Courses/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_GRADE_ID by lazy { + """/Grades/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_EXAM_ID by lazy { + """/Evaluations/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_EVENT_TYPE_ID by lazy { + """/GradeLabels/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_ANNOUNCEMENT_ID by lazy { + """/Announcement/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_HOMEWORK_ID by lazy { + """/Homework/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_TEACHER_ID by lazy { + """/Teachers/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_EVENT_ID by lazy { + """/KlassEvent/([\w-_]+?)/""".toRegex() + } + val EDUDZIENNIK_NOTE_ID by lazy { + """/RegistryNotes/([\w-_]+?)/""".toRegex() + } + + val EDUDZIENNIK_SCHOOL_DETAIL_ID by lazy { + """.*?

    (.*?)

    .*?
  • """.toRegex(DOT_MATCHES_ALL) + } + val EDUDZIENNIK_CLASS_DETAIL_ID by lazy { + """(.*?)""".toRegex(DOT_MATCHES_ALL) + } + + val EDUDZIENNIK_TEACHERS by lazy { + """
    .*?

    (.+?) (.+?)

    """.toRegex(DOT_MATCHES_ALL) + } + + + + + val LINKIFY_DATE_YMD by lazy { + """(1\d{3}|20\d{2})[\-./](1[0-2]|0?\d)[\-./]([1-2]\d|3[0-1]|0?\d)""".toRegex() + } + val LINKIFY_DATE_DMY by lazy { + """(?>? = null, onlyEndpoints: List? = null, arguments: JsonObject? = null) = EdziennikTask(profileId, SyncProfileRequest(viewIds, onlyEndpoints, arguments)) + fun syncProfileList(profileList: List) = EdziennikTask(-1, SyncProfileListRequest(profileList)) + fun messageGet(profileId: Int, message: MessageFull) = EdziennikTask(profileId, MessageGetRequest(message)) + fun messageSend(profileId: Int, recipients: List, subject: String, text: String) = EdziennikTask(profileId, MessageSendRequest(recipients, subject, text)) + fun announcementsRead(profileId: Int) = EdziennikTask(profileId, AnnouncementsReadRequest()) + fun announcementGet(profileId: Int, announcement: AnnouncementFull) = EdziennikTask(profileId, AnnouncementGetRequest(announcement)) + fun attachmentGet(profileId: Int, message: Message, attachmentId: Long, attachmentName: String) = EdziennikTask(profileId, AttachmentGetRequest(message, attachmentId, attachmentName)) + fun recipientListGet(profileId: Int) = EdziennikTask(profileId, RecipientListGetRequest()) + } + + private lateinit var loginStore: LoginStore + + override fun prepare(app: App) { + if (request is FirstLoginRequest) { + // get the requested profile and login store + this.profile = null + loginStore = request.loginStore + // save the profile ID and name as the current task's + taskName = app.getString(R.string.edziennik_notification_api_first_login_title) + } else { + // get the requested profile and login store + val profile = app.db.profileDao().getByIdNow(profileId) ?: return + this.profile = profile + val loginStore = app.db.loginStoreDao().getByIdNow(profile.loginStoreId) ?: return + this.loginStore = loginStore + // save the profile ID and name as the current task's + taskName = app.getString(R.string.edziennik_notification_api_sync_title_format, profile.name) + } + } + + private var edziennikInterface: EdziennikInterface? = null + + internal fun run(app: App, taskCallback: EdziennikCallback) { + if (profile?.archived == true) { + taskCallback.onError(ApiError(TAG, ERROR_PROFILE_ARCHIVED)) + return + } + edziennikInterface = when (loginStore.type) { + LOGIN_TYPE_LIBRUS -> Librus(app, profile, loginStore, taskCallback) + LOGIN_TYPE_MOBIDZIENNIK -> Mobidziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_VULCAN -> Vulcan(app, profile, loginStore, taskCallback) + LOGIN_TYPE_IDZIENNIK -> Idziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_EDUDZIENNIK -> Edudziennik(app, profile, loginStore, taskCallback) + LOGIN_TYPE_TEMPLATE -> Template(app, profile, loginStore, taskCallback) + else -> null + } + if (edziennikInterface == null) { + return + } + + when (request) { + is SyncProfileRequest -> edziennikInterface?.sync( + featureIds = request.viewIds?.flatMap { Features.getIdsByView(it.first, it.second) } + ?: Features.getAllIds(), + viewId = request.viewIds?.get(0)?.first, + onlyEndpoints = request.onlyEndpoints, + arguments = request.arguments) + is MessageGetRequest -> edziennikInterface?.getMessage(request.message) + is MessageSendRequest -> edziennikInterface?.sendMessage(request.recipients, request.subject, request.text) + is FirstLoginRequest -> edziennikInterface?.firstLogin() + is AnnouncementsReadRequest -> edziennikInterface?.markAllAnnouncementsAsRead() + is AnnouncementGetRequest -> edziennikInterface?.getAnnouncement(request.announcement) + is AttachmentGetRequest -> edziennikInterface?.getAttachment(request.message, request.attachmentId, request.attachmentName) + is RecipientListGetRequest -> edziennikInterface?.getRecipientList() + } + } + + override fun cancel() { + edziennikInterface?.cancel() + } + + override fun toString(): String { + return "EdziennikTask(profileId=$profileId, request=$request, edziennikInterface=$edziennikInterface)" + } + + data class FirstLoginRequest(val loginStore: LoginStore) + class SyncRequest + data class SyncProfileRequest(val viewIds: List>? = null, val onlyEndpoints: List? = null, val arguments: JsonObject? = null) + data class SyncProfileListRequest(val profileList: List) + data class MessageGetRequest(val message: MessageFull) + data class MessageSendRequest(val recipients: List, val subject: String, val text: String) + class AnnouncementsReadRequest + data class AnnouncementGetRequest(val announcement: AnnouncementFull) + data class AttachmentGetRequest(val message: Message, val attachmentId: Long, val attachmentName: String) + class RecipientListGetRequest +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt new file mode 100644 index 00000000..40601249 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/DataEdudziennik.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.* + +/** + * Use http://patorjk.com/software/taag/#p=display&f=Big for the ascii art + * + * Use https://codepen.io/kubasz/pen/RwwwbGN to easily generate the student data getters/setters + */ +class DataEdudziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isWebLoginValid() = webSessionIdExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isWebLoginValid()) { + loginMethods += LOGIN_METHOD_EDUDZIENNIK_WEB + } + } + + override fun generateUserCode() = "$schoolName:$loginEmail:${studentId?.crc32()}" + + private var mLoginEmail: String? = null + var loginEmail: String? + get() { mLoginEmail = mLoginEmail ?: loginStore.getLoginData("email", null); return mLoginEmail } + set(value) { loginStore.putLoginData("email", value); mLoginEmail = value } + + private var mLoginPassword: String? = null + var loginPassword: String? + get() { mLoginPassword = mLoginPassword ?: loginStore.getLoginData("password", null); return mLoginPassword } + set(value) { loginStore.putLoginData("password", value); mLoginPassword = value } + + private var mStudentId: String? = null + var studentId: String? + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId } + set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + + private var mSchoolId: String? = null + var schoolId: String? + get() { mSchoolId = mSchoolId ?: profile?.getStudentData("schoolId", null); return mSchoolId } + set(value) { profile?.putStudentData("schoolId", value) ?: return; mSchoolId = value } + + private var mClassId: String? = null + var classId: String? + get() { mClassId = mClassId ?: profile?.getStudentData("classId", null); return mClassId } + set(value) { profile?.putStudentData("classId", value) ?: return; mClassId = value } + + /* __ __ _ + \ \ / / | | + \ \ /\ / /__| |__ + \ \/ \/ / _ \ '_ \ + \ /\ / __/ |_) | + \/ \/ \___|_._*/ + private var mWebSessionId: String? = null + var webSessionId: String? + get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId } + set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value } + + private var mWebSessionIdExpiryTime: Long? = null + var webSessionIdExpiryTime: Long + get() { mWebSessionIdExpiryTime = mWebSessionIdExpiryTime ?: loginStore.getLoginData("webSessionIdExpiryTime", 0L); return mWebSessionIdExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("webSessionIdExpiryTime", value); mWebSessionIdExpiryTime = value } + + /* ____ _ _ + / __ \| | | | + | | | | |_| |__ ___ _ __ + | | | | __| '_ \ / _ \ '__| + | |__| | |_| | | | __/ | + \____/ \__|_| |_|\___|*/ + private var mCurrentSemester: Int? = null + var currentSemester: Int + get() { mCurrentSemester = mCurrentSemester ?: profile?.getStudentData("currentSemester", 1); return mCurrentSemester ?: 1 } + set(value) { profile?.putStudentData("currentSemester", value) ?: return; mCurrentSemester = value } + + private var mSchoolName: String? = null + var schoolName: String? + get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName } + set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value } + + val studentEndpoint: String + get() = "Students/$studentId/" + + val schoolEndpoint: String + get() = "Schools/$schoolId/" + + val classStudentEndpoint: String + get() = "Class/$studentId/" + + val schoolClassEndpoint: String + get() = "Schools/$classId/" + + val studentAndClassEndpoint: String + get() = "Students/$studentId/Klass/$classId/" + + val studentAndClassesEndpoint: String + get() = "Students/$studentId/Classes/$classId/" + + val timetableEndpoint: String + get() = "Plan/$studentId/" + + val studentAndTeacherClassEndpoint: String + get() = "Students/$studentId/Teachers/$classId/" + + val courseStudentEndpoint: String + get() = "Course/$studentId/" + + fun getSubject(longId: String, name: String): Subject { + val id = longId.crc32() + return subjectList.singleOrNull { it.id == id } ?: run { + val subject = Subject(profileId, id, name, name) + subjectList.put(id, subject) + subject + } + } + + fun getTeacher(firstName: String, lastName: String, longId: String? = null): Teacher { + val name = "$firstName $lastName".fixName() + val id = name.crc32() + return teacherList.singleOrNull { it.id == id }?.also { + if (longId != null && it.loginId == null) it.loginId = longId + } ?: run { + val teacher = Teacher(profileId, id, firstName, lastName, longId) + teacherList.put(id, teacher) + teacher + } + } + + fun getTeacherByFirstLast(nameFirstLast: String, longId: String? = null): Teacher { + val nameParts = nameFirstLast.split(" ") + return getTeacher(nameParts[0], nameParts[1], longId) + } + + fun getTeacherByLastFirst(nameLastFirst: String, longId: String? = null): Teacher { + val nameParts = nameLastFirst.split(" ") + return getTeacher(nameParts[1], nameParts[0], longId) + } + + fun getEventType(longId: String, name: String): EventType { + val id = longId.crc16().toLong() + return eventTypes.singleOrNull { it.id == id } ?: run { + val eventType = EventType(profileId, id, name, colorFromName(name)) + eventTypes.put(id, eventType) + eventType + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt new file mode 100644 index 00000000..7bc50511 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/Edudziennik.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikData +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.EdudziennikWebGetAnnouncement +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.firstlogin.EdudziennikFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Edudziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Edudziennik" + } + + val internalErrorList = mutableListOf() + val data: DataEdudziennik + private var afterLogin: (() -> Unit)? = null + + init { + data = DataEdudziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Edudziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(edudziennikLoginMethods, EdudziennikFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(edudziennikLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + EdudziennikLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: EdudziennikData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) {} + override fun sendMessage(recipients: List, subject: String, text: String) {} + override fun markAllAnnouncementsAsRead() {} + + override fun getAnnouncement(announcement: AnnouncementFull) { + EdudziennikLoginWeb(data) { + EdudziennikWebGetAnnouncement(data, announcement) { + completed() + } + } + } + + override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) {} + override fun getRecipientList() {} + + override fun firstLogin() { EdudziennikFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED -> { + login() + } + ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID -> { + login() + } + ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS -> { + data() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt new file mode 100644 index 00000000..c3fd17ed --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/EdudziennikFeatures.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik + +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature + +const val ENDPOINT_EDUDZIENNIK_WEB_START = 1000 +const val ENDPOINT_EDUDZIENNIK_WEB_TEACHERS = 1001 +const val ENDPOINT_EDUDZIENNIK_WEB_GRADES = 1011 +const val ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE = 1012 +const val ENDPOINT_EDUDZIENNIK_WEB_EXAMS = 1013 +const val ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE = 1014 +const val ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS = 1015 +const val ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK = 1016 +const val ENDPOINT_EDUDZIENNIK_WEB_EVENTS = 1017 +const val ENDPOINT_EDUDZIENNIK_WEB_NOTES = 1018 +const val ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER = 1030 + +val EdudziennikFeatures = listOf( + /* School and team info and subjects */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_STUDENT_INFO, listOf( + ENDPOINT_EDUDZIENNIK_WEB_START to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Teachers */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_TEACHERS, listOf( + ENDPOINT_EDUDZIENNIK_WEB_TEACHERS to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Timetable */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_TIMETABLE, listOf( + ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Grades */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_GRADES, listOf( + ENDPOINT_EDUDZIENNIK_WEB_GRADES to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Agenda */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_AGENDA, listOf( + ENDPOINT_EDUDZIENNIK_WEB_EXAMS to LOGIN_METHOD_EDUDZIENNIK_WEB, + ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_EDUDZIENNIK_WEB, + ENDPOINT_EDUDZIENNIK_WEB_EVENTS to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Homework */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_HOMEWORK, listOf( + ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Behaviour */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_BEHAVIOUR, listOf( + ENDPOINT_EDUDZIENNIK_WEB_NOTES to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Attendance */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_ATTENDANCE, listOf( + ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Announcements */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_ANNOUNCEMENTS, listOf( + ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)), + + /* Lucky number */ + Feature(LOGIN_TYPE_EDUDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER to LOGIN_METHOD_EDUDZIENNIK_WEB + ), listOf(LOGIN_METHOD_EDUDZIENNIK_WEB)) +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt new file mode 100644 index 00000000..7eb58d1c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikData.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web.* +import pl.szczodrzynski.edziennik.utils.Utils + +class EdudziennikData(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_EDUDZIENNIK_WEB_START -> { + data.startProgress(R.string.edziennik_progress_endpoint_data) + EdudziennikWebStart(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_TEACHERS -> { + data.startProgress(R.string.edziennik_progress_endpoint_teachers) + EdudziennikWebTeachers(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + EdudziennikWebGrades(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + EdudziennikWebTimetable(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_EXAMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_exams) + EdudziennikWebExams(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + EdudziennikWebAttendance(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_announcements) + EdudziennikWebAnnouncements(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + EdudziennikWebHomework(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_EVENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_events) + EdudziennikWebEvents(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_NOTES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notices) + EdudziennikWebNotes(data, lastSync, onSuccess) + } + ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + EdudziennikWebLuckyNumber(data, lastSync, onSuccess) + } + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikWeb.kt new file mode 100644 index 00000000..f5adfc5d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/EdudziennikWeb.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date + +open class EdudziennikWeb(open val data: DataEdudziennik, open val lastSync: Long?) { + companion object { + private const val TAG = "EdudziennikWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun webGet(tag: String, endpoint: String, xhr: Boolean = false, semester: Int? = null, onSuccess: (text: String) -> Unit) { + val url = "https://dziennikel.appspot.com/" + when (endpoint.endsWith('/') || endpoint.contains('?') || endpoint.isEmpty()) { + true -> endpoint + else -> "$endpoint/" + } + (semester?.let { "?semester=" + if(it == -1) "all" else it } ?: "") + + d(tag, "Request: Edudziennik/Web - $url") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null || response == null) { + data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + if (semester == null && url.contains("start")) { + profile?.also { profile -> + val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com") + val semesterCookie = cookies["semester"]?.toIntOrNull() + + semesterCookie?.let { data.currentSemester = it } + + if (semesterCookie == 2 && profile.dateSemester2Start > Date.getToday()) + profile.dateSemester2Start = Date.getToday().stepForward(0, 0, -1) + } + } + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_WEB_REQUEST) + .withThrowable(e) + .withResponse(response) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + val error = when (response?.code()) { + 402 -> ERROR_EDUDZIENNIK_WEB_LIMITED_ACCESS + 403 -> ERROR_EDUDZIENNIK_WEB_SESSION_EXPIRED + else -> ERROR_REQUEST_FAILURE + } + data.error(ApiError(tag, error) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("dziennikel.appspot.com", "sessionid", data.webSessionId) + + Request.builder() + .url(url) + .userAgent(EDUDZIENNIK_USER_AGENT) + .apply { + if (xhr) header("X-Requested-With", "XMLHttpRequest") + } + .get() + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt new file mode 100644 index 00000000..11f048b5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAnnouncements.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-26 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ANNOUNCEMENT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebAnnouncements(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebAnnouncements" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.schoolClassEndpoint + "Announcements") { text -> + val doc = Jsoup.parse(text) + + if (doc.getElementsByClass("message").text().trim() != "Brak ogłoszeń.") { + doc.select("table.list tbody tr").forEach { announcementElement -> + val titleElement = announcementElement.child(0).child(0) + + val longId = EDUDZIENNIK_ANNOUNCEMENT_ID.find(titleElement.attr("href"))?.get(1) + ?: return@forEach + val id = longId.crc32() + val subject = titleElement.text() + + val teacherName = announcementElement.child(1).text() + val teacher = data.getTeacherByFirstLast(teacherName) + + val dateString = announcementElement.getElementsByClass("datetime").first().text() + val startDate = Date.fromY_m_d(dateString) + val addedDate = Date.fromIsoHm(dateString) + + val announcementObject = Announcement( + profileId, + id, + subject, + null, + startDate, + null, + teacher.id, + longId + ) + + data.announcementIgnoreList.add(announcementObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + id, + profile.empty, + profile.empty, + addedDate + )) + } + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ANNOUNCEMENTS) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt new file mode 100644 index 00000000..92545117 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebAttendance.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_ENTRIES +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPE +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ATTENDANCE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class EdudziennikWebAttendance(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebAttendance" + } + + private var requestSemester: Int? = null + + init { + if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1 + getAttendances() + } + + private fun getAttendances() { data.profile?.also { profile -> + webGet(TAG, data.studentEndpoint + "Presence", semester = requestSemester) { text -> + + val attendanceTypes = EDUDZIENNIK_ATTENDANCE_TYPES.find(text)?.get(1)?.split(',')?.map { + val type = EDUDZIENNIK_ATTENDANCE_TYPE.find(it.trim()) + val symbol = type?.get(1)?.trim() + val name = type?.get(2)?.trim() + return@map Triple( + symbol, + name, + when (name?.toLowerCase(Locale.ROOT)) { + "obecność" -> Attendance.TYPE_PRESENT + "nieobecność" -> Attendance.TYPE_ABSENT + "spóźnienie" -> Attendance.TYPE_BELATED + "nieobecność usprawiedliwiona" -> Attendance.TYPE_ABSENT_EXCUSED + "dzień wolny" -> Attendance.TYPE_DAY_FREE + "brak zajęć" -> Attendance.TYPE_DAY_FREE + "oddelegowany" -> Attendance.TYPE_RELEASED + else -> Attendance.TYPE_CUSTOM + } + ) + } ?: emptyList() + + EDUDZIENNIK_ATTENDANCE_ENTRIES.findAll(text).forEach { attendanceElement -> + val date = Date.fromY_m_d(attendanceElement[1]) + val lessonNumber = attendanceElement[2].toInt() + val attendanceSymbol = attendanceElement[3] + + val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val lesson = lessons.firstOrNull { it.lessonNumber == lessonNumber } + + val id = "${date.stringY_m_d}:$lessonNumber:$attendanceSymbol".crc32() + + val (_, name, type) = attendanceTypes.firstOrNull { (symbol, _, _) -> symbol == attendanceSymbol } + ?: return@forEach + + val startTime = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNumber }?.startTime + ?: return@forEach + + val attendanceObject = Attendance( + profileId, + id, + lesson?.displayTeacherId ?: -1, + lesson?.displaySubjectId ?: -1, + profile.currentSemester, + name, + date, + lesson?.displayStartTime ?: startTime, + type + ) + + data.attendanceList.add(attendanceObject) + if(type != Attendance.TYPE_PRESENT) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + } + + if (profile.empty && requestSemester == 1 && data.currentSemester == 2) { + requestSemester = null + getAttendances() + } else { + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE) + } + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_ATTENDANCE) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt new file mode 100644 index 00000000..52833b9a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebEvents.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-1 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_EVENT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EVENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebEvents(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebEvents" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.studentAndClassesEndpoint + "KlassEvent", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + doc.getElementsByTag("tr").forEach { eventElement -> + val date = Date.fromY_m_d(eventElement.child(1).text()) + + val titleElement = eventElement.child(2).child(0) + val title = titleElement.text().trim() + + val id = EDUDZIENNIK_EVENT_ID.find(titleElement.attr("href"))?.get(1)?.crc32() + ?: return@forEach + + val eventObject = Event( + profileId = profileId, + id = id, + date = date, + time = null, + topic = title, + color = null, + type = Event.TYPE_CLASS_EVENT, + teacherId = -1, + subjectId = -1, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_CLASS_EVENT)) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_EVENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_EVENTS) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_EVENTS) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt new file mode 100644 index 00000000..8e7836fd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebExams.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_EVENT_TYPE_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_EXAM_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_EXAMS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebExams(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebExams" + } + + init { profile?.also { profile -> + webGet(TAG, data.studentAndClassEndpoint + "Evaluations", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + doc.select("tr").forEach { examElement -> + val id = EDUDZIENNIK_EXAM_ID.find(examElement.child(0).child(0).attr("href")) + ?.get(1)?.crc32() ?: return@forEach + val topic = examElement.child(0).text().trim() + + val subjectElement = examElement.child(1).child(0) + val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) + ?: return@forEach + val subjectName = subjectElement.text().trim() + val subject = data.getSubject(subjectId, subjectName) + + val dateString = examElement.child(2).text().trim() + if (dateString.isBlank()) return@forEach + val date = Date.fromY_m_d(dateString) + + val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val startTime = lessons.firstOrNull { it.displaySubjectId == subject.id }?.displayStartTime + + val eventTypeElement = examElement.child(3).child(0) + val eventTypeId = EDUDZIENNIK_EVENT_TYPE_ID.find(eventTypeElement.attr("href"))?.get(1) + ?: return@forEach + val eventTypeName = eventTypeElement.text() + val eventType = data.getEventType(eventTypeId, eventTypeName) + + val eventObject = Event( + profileId = profileId, + id = id, + date = date, + time = startTime, + topic = topic, + color = null, + type = eventType.id, + teacherId = -1, + subjectId = subject.id, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureExceptTypes(listOf( + Event.TYPE_HOMEWORK, + Event.TYPE_CLASS_EVENT + ))) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_EXAMS, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_EXAMS) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetAnnouncement.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetAnnouncement.kt new file mode 100644 index 00000000..5d915e64 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGetAnnouncement.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-26 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.get + +class EdudziennikWebGetAnnouncement(override val data: DataEdudziennik, + private val announcement: AnnouncementFull, + val onSuccess: () -> Unit +) : EdudziennikWeb(data, null) { + companion object { + const val TAG = "EdudziennikWebGetAnnouncement" + } + + init { + webGet(TAG, "Announcement/${announcement.idString}") { text -> + val description = Regexes.EDUDZIENNIK_ANNOUNCEMENT_DESCRIPTION.find(text)?.get(1)?.trim() ?: "" + + announcement.text = description + + EventBus.getDefault().postSticky(AnnouncementGetEvent(announcement)) + + data.announcementList.add(announcement) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt new file mode 100644 index 00000000..50557333 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebGrades.kt @@ -0,0 +1,232 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import android.graphics.Color +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.colorFromCssName +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebGrades(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebGrades" + } + + private var requestSemester: Int? = null + + init { + if (profile?.empty == true && data.currentSemester == 2) requestSemester = 1 + getGrades() + } + + private fun getGrades() { data.profile?.also { profile -> + webGet(TAG, data.studentEndpoint + "start", semester = requestSemester) { text -> + val semester = requestSemester ?: data.currentSemester + + val doc = Jsoup.parse(text) + val subjects = doc.select("#student_grades tbody").firstOrNull()?.children() + + subjects?.forEach { subjectElement -> + if (subjectElement.id().isBlank()) return@forEach + + val subjectId = subjectElement.id().trim() + val subjectName = subjectElement.child(0).text().trim() + val subject = data.getSubject(subjectId, subjectName) + + val gradeType = when { + subjectElement.select("#sum").text().isNotBlank() -> TYPE_POINT_SUM + else -> TYPE_NORMAL + } + + val gradeCountToAverage = subjectElement.select("#avg").text().isNotBlank() + + val grades = subjectElement.select(".grade[data-edited]") + val gradesInfo = subjectElement.select(".grade-tip") + + val gradeValues = if (grades.isNotEmpty()) { + subjects.select(".avg-$subjectId .grade-tip > p").first() + .text().split('+').map { + val split = it.split('*') + val value = split[1].trim().toFloatOrNull() + val weight = value?.let { split[0].trim().toFloatOrNull() } ?: 0f + + Pair(value ?: 0f, weight) + } + } else emptyList() + + grades.forEachIndexed { index, gradeElement -> + val id = Regexes.EDUDZIENNIK_GRADE_ID.find(gradeElement.attr("href"))?.get(1)?.crc32() + ?: return@forEachIndexed + val (value, weight) = gradeValues[index] + val name = gradeElement.text().trim().let { + if (it.contains(',') || it.contains('.')) { + val replaced = it.replace(',', '.') + val float = replaced.toFloatOrNull() + + if (float != null && float % 1 == 0f) float.toInt().toString() + else it + } else it + } + + val info = gradesInfo[index] + val fullName = info.child(0).text().trim() + val columnName = info.child(4).text().trim() + val comment = info.ownText() + + val description = columnName + if (comment.isNotBlank()) " - $comment" else null + + val teacherName = info.child(1).text() + val teacher = data.getTeacherByLastFirst(teacherName) + + val addedDate = info.child(2).text().split(' ').let { + val day = it[0].toInt() + val month = Utils.monthFromName(it[1]) + val year = it[2].toInt() + + Date(year, month, day).inMillis + } + + val color = Regexes.STYLE_CSS_COLOR.find(gradeElement.attr("style"))?.get(1)?.let { + if (it.startsWith('#')) Color.parseColor(it) + else colorFromCssName(it) + } ?: -1 + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = gradeType, + value = value, + weight = if (gradeCountToAverage) weight else 0f, + color = color, + category = fullName, + description = description, + comment = null, + semester = semester, + teacherId = teacher.id, + subjectId = subject.id + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + val proposed = subjectElement.select(".proposal").firstOrNull()?.text()?.trim() + + if (proposed != null && proposed.isNotBlank()) { + val proposedGradeObject = Grade( + profileId = profileId, + id = (-1 * subject.id) - 1, + name = proposed, + type = when (semester) { + 1 -> TYPE_SEMESTER1_PROPOSED + else -> TYPE_SEMESTER2_PROPOSED + }, + value = proposed.toFloatOrNull() ?: 0f, + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = semester, + teacherId = -1, + subjectId = subject.id + ) + + data.gradeList.add(proposedGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + proposedGradeObject.id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + + val final = subjectElement.select(".final").firstOrNull()?.text()?.trim() + + if (final != null && final.isNotBlank()) { + val finalGradeObject = Grade( + profileId = profileId, + id = (-1 * subject.id) - 2, + name = final, + type = when (semester) { + 1 -> TYPE_SEMESTER1_FINAL + else -> TYPE_SEMESTER2_FINAL + }, + value = final.toFloatOrNull() ?: 0f, + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = semester, + teacherId = -1, + subjectId = subject.id + ) + + data.gradeList.add(finalGradeObject) + data.metadataList.add(Metadata( + data.profileId, + Metadata.TYPE_GRADE, + finalGradeObject.id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + } + + if (!subjects.isNullOrEmpty()) { + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_POINT_SUM, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL + ).map { + DataRemoveModel.Grades.semesterWithType(semester, it) + }) + } + + if (profile.empty && requestSemester == 1 && data.currentSemester == 2) { + requestSemester = null + getGrades() + } else { + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_GRADES) + } + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt new file mode 100644 index 00000000..1a9ea5ae --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebHomework.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_HOMEWORK_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebHomework(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebHomework" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.courseStudentEndpoint + "Homework", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + if (doc.getElementsByClass("message").text().trim() != "Brak prac domowych") { + doc.getElementsByTag("tr").forEach { homeworkElement -> + val dateElement = homeworkElement.getElementsByClass("date").first().child(0) + val id = EDUDZIENNIK_HOMEWORK_ID.find(dateElement.attr("href"))?.get(1)?.crc32() + ?: return@forEach + val date = Date.fromY_m_d(dateElement.text()) + + val subjectElement = homeworkElement.child(1).child(0) + val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) + ?: return@forEach + val subjectName = subjectElement.text() + val subject = data.getSubject(subjectId, subjectName) + + val lessons = data.app.db.timetableDao().getForDateNow(profileId, date) + val startTime = lessons.firstOrNull { it.subjectId == subject.id }?.displayStartTime + + val teacherName = homeworkElement.child(2).text() + val teacher = data.getTeacherByFirstLast(teacherName) + + val topic = homeworkElement.child(4).text() + + val eventObject = Event( + profileId = profileId, + id = id, + date = date, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacher.id, + subjectId = subject.id, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_HOMEWORK) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt new file mode 100644 index 00000000..70c6849a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebLuckyNumber.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebLuckyNumber(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebLuckyNumber" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.schoolEndpoint + "Lucky", xhr = true) { text -> + text.toIntOrNull()?.also { luckyNumber -> + val luckyNumberObject = LuckyNumber( + profileId, + Date.getToday(), + luckyNumber + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile.empty, + System.currentTimeMillis() + )) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_LUCKY_NUMBER) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt new file mode 100644 index 00000000..a111ef79 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebNotes.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-1 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_NOTE_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_NOTES +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.models.Date + +class EdudziennikWebNotes(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + const val TAG = "EdudziennikWebNotes" + } + + init { data.profile?.also { profile -> + webGet(TAG, data.classStudentEndpoint + "RegistryNotesStudent", xhr = true) { text -> + val doc = Jsoup.parseBodyFragment("" + text.trim() + "
    ") + + doc.getElementsByTag("tr").forEach { noteElement -> + val dateElement = noteElement.getElementsByClass("date").first().child(0) + val addedDate = Date.fromY_m_d(dateElement.text()).inMillis + + val id = EDUDZIENNIK_NOTE_ID.find(dateElement.attr("href"))?.get(0)?.crc32() + ?: return@forEach + + val teacherName = noteElement.child(1).text() + val teacher = data.getTeacherByFirstLast(teacherName) + + val description = noteElement.child(3).text() + + val noticeObject = Notice( + profileId, + id, + description, + profile.currentSemester, + Notice.TYPE_NEUTRAL, + teacher.id + ) + + data.noticeList.add(noticeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_NOTICE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_NOTES, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_NOTES) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_NOTES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt new file mode 100644 index 00000000..66cb7a59 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebStart.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.MONTH +import pl.szczodrzynski.edziennik.crc32 +import pl.szczodrzynski.edziennik.data.api.ERROR_EDUDZIENNIK_WEB_TEAM_MISSING +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECTS_START +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_START +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.firstLettersName +import pl.szczodrzynski.edziennik.get + +class EdudziennikWebStart(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebStart" + } + + init { + webGet(TAG, data.studentEndpoint + "start") { text -> + getSchoolAndTeam(text) + getSubjects(text) + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_START, MONTH) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_START) + } + } + + private fun getSchoolAndTeam(text: String) { + val schoolId = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_ID.find(text)?.get(1)?.trim() + val schoolLongName = Regexes.EDUDZIENNIK_SCHOOL_DETAIL_NAME.find(text)?.get(1)?.trim() + data.schoolId = schoolId + + val classId = Regexes.EDUDZIENNIK_CLASS_DETAIL_ID.find(text)?.get(1)?.trim() + val className = Regexes.EDUDZIENNIK_CLASS_DETAIL_NAME.find(text)?.get(1)?.trim() + data.classId = classId + + if (classId == null || className == null || schoolId == null || schoolLongName == null) { + data.error(ApiError(TAG, ERROR_EDUDZIENNIK_WEB_TEAM_MISSING) + .withApiResponse(text)) + return + } + + val schoolName = schoolId.crc32().toString() + schoolLongName.firstLettersName + "_edu" + data.schoolName = schoolName + + val teamId = classId.crc32() + val teamCode = "$schoolName:$className" + + val teamObject = Team( + data.profileId, + teamId, + className, + Team.TYPE_CLASS, + teamCode, + -1 + ) + + data.teamClass = teamObject + data.teamList.put(teamObject.id, teamObject) + } + + private fun getSubjects(text: String) { + EDUDZIENNIK_SUBJECTS_START.findAll(text).forEach { + val id = it[1].trim() + val name = it[2].trim() + data.getSubject(id, name) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTeachers.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTeachers.kt new file mode 100644 index 00000000..d2f3ba15 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTeachers.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import pl.szczodrzynski.edziennik.MONTH +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHERS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_TEACHERS +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.get + +class EdudziennikWebTeachers(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebTeachers" + } + + init { + webGet(TAG, data.studentAndTeacherClassEndpoint + "grid") { text -> + EDUDZIENNIK_TEACHERS.findAll(text).forEach { + val lastName = it[1].trim() + val firstName = it[2].trim() + data.getTeacher(firstName, lastName) + } + + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_TEACHERS, MONTH) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_TEACHERS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt new file mode 100644 index 00000000..3a31e208 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/data/web/EdudziennikWebTimetable.kt @@ -0,0 +1,150 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.web + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_SUBJECT_ID +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_TEACHER_ID +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class EdudziennikWebTimetable(override val data: DataEdudziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : EdudziennikWeb(data, lastSync) { + companion object { + private const val TAG = "EdudziennikWebTimetable" + } + + init { data.profile?.also { profile -> + + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + + 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) + + webGet(TAG, data.timetableEndpoint + "print?date=$getDate") { text -> + val doc = Jsoup.parse(text) + + val dataDays = mutableListOf() + val dataStart = weekStart.clone() + while (dataStart <= weekEnd) { + dataDays += dataStart.value + dataStart.stepForward(0, 0, 1) + } + + val table = doc.select("#Schedule tbody").first() + + if (!table.text().contains("Brak planu lekcji.")) { + table.children().forEach { row -> + val rowElements = row.children() + + val lessonNumber = rowElements[0].text().toInt() + + val times = rowElements[1].text().split('-') + val startTime = Time.fromH_m(times[0].trim()) + val endTime = Time.fromH_m(times[1].trim()) + + data.lessonRanges.singleOrNull { + it.lessonNumber == lessonNumber && it.startTime == startTime && it.endTime == endTime + } ?: run { + data.lessonRanges.put(lessonNumber, LessonRange(profileId, lessonNumber, startTime, endTime)) + } + + rowElements.subList(2, rowElements.size).forEachIndexed { index, lesson -> + val course = lesson.select(".course").firstOrNull() ?: return@forEachIndexed + val info = course.select("span > span") + + if (info.isEmpty()) return@forEachIndexed + + val type = when (course.hasClass("substitute")) { + true -> Lesson.TYPE_CHANGE + else -> Lesson.TYPE_NORMAL + } + + /* Getting subject */ + + val subjectElement = info[0].child(0) + val subjectId = EDUDZIENNIK_SUBJECT_ID.find(subjectElement.attr("href"))?.get(1) + ?: return@forEachIndexed + val subjectName = subjectElement.text().trim() + val subject = data.getSubject(subjectId, subjectName) + + /* Getting teacher */ + + val teacherId = if (info.size >= 2) { + val teacherElement = info[1].child(0) + val teacherLongId = EDUDZIENNIK_TEACHER_ID.find(teacherElement.attr("href"))?.get(1) + val teacherName = teacherElement.text().trim() + data.getTeacherByLastFirst(teacherName, teacherLongId).id + } else null + + val lessonObject = Lesson(profileId, -1).also { + it.type = type + it.date = weekStart.clone().stepForward(0, 0, index) + it.lessonNumber = lessonNumber + it.startTime = startTime + it.endTime = endTime + it.subjectId = subject.id + it.teacherId = teacherId + it.teamId = data.teamClass?.id + + it.id = it.buildId() + } + + data.lessonList.add(lessonObject) + dataDays.remove(lessonObject.date!!.value) + + if (type != Lesson.TYPE_NORMAL) { + val seen = profile.empty || lessonObject.date!! < Date.getToday() + + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_LESSON_CHANGE, + lessonObject.id, + seen, + seen, + System.currentTimeMillis() + )) + } + } + } + } + + for (day in dataDays) { + val lessonDate = Date.fromValue(day) + data.lessonList += Lesson(profileId, lessonDate.value.toLong()).apply { + type = Lesson.TYPE_NO_LESSONS + date = lessonDate + } + } + + d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") + + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + data.setSyncNext(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE) + } + } ?: onSuccess(ENDPOINT_EDUDZIENNIK_WEB_TIMETABLE) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt new file mode 100644 index 00000000..e47c7842 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/firstlogin/EdudziennikFirstLogin.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_EDUDZIENNIK +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_ACCOUNT_NAME_START +import pl.szczodrzynski.edziennik.data.api.Regexes.EDUDZIENNIK_STUDENTS_START +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.data.EdudziennikWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login.EdudziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getShortName +import pl.szczodrzynski.edziennik.set + +class EdudziennikFirstLogin(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikFirstLogin" + } + + private val web = EdudziennikWeb(data, null) + private val profileList = mutableListOf() + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_EDUDZIENNIK + var firstProfileId = loginStoreId + + EdudziennikLoginWeb(data) { + web.webGet(TAG, "") { text -> + val accountNameLong = EDUDZIENNIK_ACCOUNT_NAME_START.find(text)?.get(1)?.fixName() + + EDUDZIENNIK_STUDENTS_START.findAll(text).forEach { + val studentId = it[1] + val studentNameLong = it[2].fixName() + + if (studentId.isBlank() || studentNameLong.isBlank()) return@forEach + + val studentNameShort = studentNameLong.getShortName() + val accountName = if (accountNameLong == studentNameLong) null else accountNameLong + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + data.loginEmail, + studentNameLong, + studentNameShort, + accountName + ).apply { + studentData["studentId"] = studentId + } + profileList.add(profile) + } + + EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLogin.kt new file mode 100644 index 00000000..1cb2fd07 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLogin.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_EDUDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.utils.Utils + +class EdudziennikLogin(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + data.progress(data.progressStep) + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_EDUDZIENNIK_WEB -> { + data.startProgress(R.string.edziennik_progress_login_edudziennik_web) + EdudziennikLoginWeb(data) { onSuccess(loginMethodId) } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLoginWeb.kt new file mode 100644 index 00000000..6b1615a2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/edudziennik/login/EdudziennikLoginWeb.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.login + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.edudziennik.DataEdudziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.utils.Utils.d + +class EdudziennikLoginWeb(val data: DataEdudziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "EdudziennikLoginWeb" + } + + init { run { + if (data.isWebLoginValid()) { + onSuccess() + } + else { + data.app.cookieJar.clear("dziennikel.appspot.com") + if (data.loginEmail.isNotNullNorEmpty() && data.loginPassword.isNotNullNorEmpty()) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + private fun loginWithCredentials() { + d(TAG, "Request: Edudziennik/Login/Web - https://dziennikel.appspot.com/login/?next=/") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null || response == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + val url = response.raw().request().url().toString() + + if (!url.contains("Student")) { + when { + text.contains("Wprowadzono nieprawidłową nazwę użytkownika lub hasło.") -> ERROR_LOGIN_EDUDZIENNIK_WEB_INVALID_LOGIN + else -> ERROR_LOGIN_EDUDZIENNIK_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + val cookies = data.app.cookieJar.getAll("dziennikel.appspot.com") + val sessionId = cookies["sessionid"] + + if (sessionId == null) { + data.error(ApiError(TAG, ERROR_LOGIN_EDUDZIENNIK_WEB_NO_SESSION_ID) + .withResponse(response) + .withApiResponse(text)) + return + } + + data.webSessionId = sessionId + data.webSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45 min */ + onSuccess() + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("https://dziennikel.appspot.com/login/?next=/") + .userAgent(EDUDZIENNIK_USER_AGENT) + .contentType("application/x-www-form-urlencoded") + .addParameter("email", data.loginEmail) + .addParameter("password", data.loginPassword) + .addParameter("auth_method", "password") + .addParameter("next", "/") + .post() + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/DataIdziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/DataIdziennik.kt new file mode 100644 index 00000000..3e974539 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/DataIdziennik.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik + +import androidx.core.util.set +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Subject +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class DataIdziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isWebLoginValid() = loginExpiryTime-30 > currentTimeUnix() && webSessionId.isNotNullNorEmpty() && webAuth.isNotNullNorEmpty() + fun isApiLoginValid() = apiExpiryTime-30 > currentTimeUnix() && apiBearer.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isWebLoginValid()) { + loginMethods += LOGIN_METHOD_IDZIENNIK_WEB + app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", webSessionId) + app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", webAuth) + } + if (isApiLoginValid()) + loginMethods += LOGIN_METHOD_IDZIENNIK_API + } + + override fun generateUserCode() = "$webSchoolName:$webUsername:$registerId" + + private var mLoginExpiryTime: Long? = null + var loginExpiryTime: Long + get() { mLoginExpiryTime = mLoginExpiryTime ?: loginStore.getLoginData("loginExpiryTime", 0L); return mLoginExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("loginExpiryTime", value); mLoginExpiryTime = value } + + private var mApiExpiryTime: Long? = null + var apiExpiryTime: Long + get() { mApiExpiryTime = mApiExpiryTime ?: loginStore.getLoginData("apiExpiryTime", 0L); return mApiExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("apiExpiryTime", value); mApiExpiryTime = value } + + /* __ __ _ + \ \ / / | | + \ \ /\ / /__| |__ + \ \/ \/ / _ \ '_ \ + \ /\ / __/ |_) | + \/ \/ \___|_._*/ + private var mWebSchoolName: String? = null + var webSchoolName: String? + get() { mWebSchoolName = mWebSchoolName ?: loginStore.getLoginData("schoolName", null); return mWebSchoolName } + set(value) { loginStore.putLoginData("schoolName", value); mWebSchoolName = value } + private var mWebUsername: String? = null + var webUsername: String? + get() { mWebUsername = mWebUsername ?: loginStore.getLoginData("username", null); return mWebUsername } + set(value) { loginStore.putLoginData("username", value); mWebUsername = value } + private var mWebPassword: String? = null + var webPassword: String? + get() { mWebPassword = mWebPassword ?: loginStore.getLoginData("password", null); return mWebPassword } + set(value) { loginStore.putLoginData("password", value); mWebPassword = value } + + private var mWebSessionId: String? = null + var webSessionId: String? + get() { mWebSessionId = mWebSessionId ?: loginStore.getLoginData("webSessionId", null); return mWebSessionId } + set(value) { loginStore.putLoginData("webSessionId", value); mWebSessionId = value } + private var mWebAuth: String? = null + var webAuth: String? + get() { mWebAuth = mWebAuth ?: loginStore.getLoginData("webAuth", null); return mWebAuth } + set(value) { loginStore.putLoginData("webAuth", value); mWebAuth = value } + + private var mWebSelectedRegister: Int? = null + var webSelectedRegister: Int + get() { mWebSelectedRegister = mWebSelectedRegister ?: loginStore.getLoginData("webSelectedRegister", 0); return mWebSelectedRegister ?: 0 } + set(value) { loginStore.putLoginData("webSelectedRegister", value); mWebSelectedRegister = value } + + /* _ + /\ (_) + / \ _ __ _ + / /\ \ | '_ \| | + / ____ \| |_) | | + /_/ \_\ .__/|_| + | | + |*/ + private var mApiBearer: String? = null + var apiBearer: String? + get() { mApiBearer = mApiBearer ?: loginStore.getLoginData("apiBearer", null); return mApiBearer } + set(value) { loginStore.putLoginData("apiBearer", value); mApiBearer = value } + + /* ____ _ _ + / __ \| | | | + | | | | |_| |__ ___ _ __ + | | | | __| '_ \ / _ \ '__| + | |__| | |_| | | | __/ | + \____/ \__|_| |_|\___|*/ + private var mStudentId: String? = null + var studentId: String? + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", null); return mStudentId } + set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + + private var mRegisterId: Int? = null + var registerId: Int + get() { mRegisterId = mRegisterId ?: profile?.getStudentData("registerId", 0); return mRegisterId ?: 0 } + set(value) { profile?.putStudentData("registerId", value) ?: return; mRegisterId = value } + + private var mSchoolYearId: Int? = null + var schoolYearId: Int + get() { mSchoolYearId = mSchoolYearId ?: profile?.getStudentData("schoolYearId", 0); return mSchoolYearId ?: 0 } + set(value) { profile?.putStudentData("schoolYearId", value) ?: return; mSchoolYearId = value } + + + + /* _ _ _ _ _ + | | | | | (_) | + | | | | |_ _| |___ + | | | | __| | / __| + | |__| | |_| | \__ \ + \____/ \__|_|_|__*/ + fun getSubject(name: String, id: Long?, shortName: String): Subject { + var subject = if (id == null) + subjectList.singleOrNull { it.longName == name } + else + subjectList.singleOrNull { it.id == id } + + if (subject == null) { + subject = Subject(profileId, id + ?: name.crc16().toLong(), name, shortName) + subjectList[subject.id] = subject + } + return subject + } + + fun getTeacher(firstName: String, lastName: String): Teacher { + val teacher = teacherList.singleOrNull { it.fullName == "$firstName $lastName" } + return validateTeacher(teacher, firstName, lastName) + } + + fun getTeacher(firstNameChar: Char, lastName: String): Teacher { + val teacher = teacherList.singleOrNull { it.shortName == "$firstNameChar.$lastName" } + return validateTeacher(teacher, firstNameChar.toString(), lastName) + } + + fun getTeacherByLastFirst(nameLastFirst: String): Teacher { + val nameParts = nameLastFirst.split(" ") + return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[1], nameParts[0]) + } + + fun getTeacherByFirstLast(nameFirstLast: String): Teacher { + val nameParts = nameFirstLast.split(" ") + return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0], nameParts[1]) + } + + fun getTeacherByFDotLast(nameFDotLast: String): Teacher { + val nameParts = nameFDotLast.split(".") + return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1]) + } + + fun getTeacherByFDotSpaceLast(nameFDotSpaceLast: String): Teacher { + val nameParts = nameFDotSpaceLast.split(".") + return if (nameParts.size == 1) getTeacher(nameParts[0], "") else getTeacher(nameParts[0][0], nameParts[1]) + } + + private fun validateTeacher(teacher: Teacher?, firstName: String, lastName: String): Teacher { + (teacher ?: Teacher(profileId, -1, firstName, lastName).apply { + id = shortName.crc16().toLong() + teacherList[id] = this + }).apply { + if (firstName.length > 1) + name = firstName + surname = lastName + return this + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt new file mode 100644 index 00000000..be0d0d1c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/Idziennik.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikData +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebGetAttachment +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebGetMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebGetRecipientList +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebSendMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.firstlogin.IdziennikFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLogin +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Idziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Idziennik" + } + + val internalErrorList = mutableListOf() + val data: DataIdziennik + private var afterLogin: (() -> Unit)? = null + + init { + data = DataIdziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Idziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(idziennikLoginMethods, IdziennikFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(idziennikLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + IdziennikLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: IdziennikData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + IdziennikWebGetMessage(data, message) { + completed() + } + } + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + login(LOGIN_METHOD_IDZIENNIK_API) { + IdziennikWebSendMessage(data, recipients, subject, text) { + completed() + } + } + } + + override fun markAllAnnouncementsAsRead() {} + override fun getAnnouncement(announcement: AnnouncementFull) {} + + override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + IdziennikWebGetAttachment(data, message, attachmentId, attachmentName) { + completed() + } + } + } + + override fun getRecipientList() { + login(LOGIN_METHOD_IDZIENNIK_WEB) { + IdziennikWebGetRecipientList(data) { + completed() + } + } + } + + override fun firstLogin() { IdziennikFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION, + ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH, + ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER, + ERROR_IDZIENNIK_WEB_ACCESS_DENIED, + ERROR_IDZIENNIK_API_ACCESS_DENIED -> { + data.loginMethods.remove(LOGIN_METHOD_IDZIENNIK_WEB) + data.prepareFor(idziennikLoginMethods, LOGIN_METHOD_IDZIENNIK_WEB) + data.loginExpiryTime = 0 + login() + } + ERROR_IDZIENNIK_API_NO_REGISTER -> { + data() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/IdziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/IdziennikFeatures.kt new file mode 100644 index 00000000..02aaabb2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/IdziennikFeatures.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik + +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature + +const val ENDPOINT_IDZIENNIK_WEB_TIMETABLE = 1030 +const val ENDPOINT_IDZIENNIK_WEB_GRADES = 1040 +const val ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES = 1050 +const val ENDPOINT_IDZIENNIK_WEB_EXAMS = 1060 +const val ENDPOINT_IDZIENNIK_WEB_HOMEWORK = 1061 +const val ENDPOINT_IDZIENNIK_WEB_NOTICES = 1070 +const val ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS = 1080 +const val ENDPOINT_IDZIENNIK_WEB_ATTENDANCE = 1090 +const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX = 1110 +const val ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT = 1120 +const val ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER = 2010 +const val ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX = 2110 +const val ENDPOINT_IDZIENNIK_API_MESSAGES_SENT = 2120 + +val IdziennikFeatures = listOf( + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_TIMETABLE, listOf( + ENDPOINT_IDZIENNIK_WEB_TIMETABLE to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_GRADES, listOf( + ENDPOINT_IDZIENNIK_WEB_GRADES to LOGIN_METHOD_IDZIENNIK_WEB, + ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_AGENDA, listOf( + ENDPOINT_IDZIENNIK_WEB_EXAMS to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_HOMEWORK, listOf( + ENDPOINT_IDZIENNIK_WEB_HOMEWORK to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_BEHAVIOUR, listOf( + ENDPOINT_IDZIENNIK_WEB_NOTICES to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ATTENDANCE, listOf( + ENDPOINT_IDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_ANNOUNCEMENTS, listOf( + ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)), + + /*Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf( + ENDPOINT_IDZIENNIK_WEB_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2), + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf( + ENDPOINT_IDZIENNIK_WEB_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_WEB + ), listOf(LOGIN_METHOD_IDZIENNIK_WEB)).withPriority(2),*/ + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_INBOX, listOf( + ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX to LOGIN_METHOD_IDZIENNIK_API + ), listOf(LOGIN_METHOD_IDZIENNIK_API)), + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_MESSAGES_SENT, listOf( + ENDPOINT_IDZIENNIK_API_MESSAGES_SENT to LOGIN_METHOD_IDZIENNIK_API + ), listOf(LOGIN_METHOD_IDZIENNIK_API)), + + Feature(LOGIN_TYPE_IDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER to LOGIN_METHOD_IDZIENNIK_API + ), listOf(LOGIN_METHOD_IDZIENNIK_API)) +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikApi.kt new file mode 100644 index 00000000..8da75603 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikApi.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-29. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils +import java.net.HttpURLConnection + +open class IdziennikApi(open val data: DataIdziennik, open val lastSync: Long?) { + companion object { + const val TAG = "IdziennikApi" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun apiGet(tag: String, endpointTemplate: String, method: Int = GET, parameters: Map = emptyMap(), onSuccess: (json: JsonElement) -> Unit) { + val endpoint = endpointTemplate.replace("\$STUDENT_ID", data.studentId ?: "") + Utils.d(tag, "Request: Idziennik/API - $IDZIENNIK_API_URL/$endpoint") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + val json = try { + JsonParser().parse(text) + } catch (_: Exception) { null } + + var error: String? = null + if (json == null) { + error = text + } + else if (json is JsonObject) { + error = if (response?.code() == 200) null else + json.getString("message") ?: json.toString() + } + error?.let { code -> + when (code) { + "Uczeń nie posiada aktywnej pozycji w dzienniku" -> ERROR_IDZIENNIK_API_NO_REGISTER + "Authorization has been denied for this request." -> ERROR_IDZIENNIK_API_ACCESS_DENIED + else -> ERROR_IDZIENNIK_API_OTHER + }.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + try { + onSuccess(json!!) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_IDZIENNIK_API_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_API_URL/$endpoint") + .userAgent(IDZIENNIK_API_USER_AGENT) + .addHeader("Authorization", "Bearer ${data.apiBearer}") + .apply { + when (method) { + GET -> get() + POST -> { + postJson() + val json = JsonObject() + parameters.map { (name, value) -> + when (value) { + is JsonObject -> json.add(name, value) + is JsonArray -> json.add(name, value) + is String -> json.addProperty(name, value) + is Int -> json.addProperty(name, value) + is Long -> json.addProperty(name, value) + is Float -> json.addProperty(name, value) + is Char -> json.addProperty(name, value) + } + } + setJsonBody(json) + } + } + } + .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) + .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .allowErrorCode(HttpURLConnection.HTTP_INTERNAL_ERROR) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikData.kt new file mode 100644 index 00000000..8acb04cb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikData.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiCurrentRegister +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesInbox +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesSent +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.* +import pl.szczodrzynski.edziennik.utils.Utils + +class IdziennikData(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_IDZIENNIK_WEB_TIMETABLE -> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + IdziennikWebTimetable(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + IdziennikWebGrades(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_proposed_grades) + IdziennikWebProposedGrades(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_EXAMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_exams) + IdziennikWebExams(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + IdziennikWebHomework(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_NOTICES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notices) + IdziennikWebNotices(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_announcements) + IdziennikWebAnnouncements(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_WEB_ATTENDANCE -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + IdziennikWebAttendance(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + IdziennikApiCurrentRegister(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + IdziennikApiMessagesInbox(data, lastSync, onSuccess) + } + ENDPOINT_IDZIENNIK_API_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + IdziennikApiMessagesSent(data, lastSync, onSuccess) + } + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikWeb.kt new file mode 100644 index 00000000..907e1f50 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/IdziennikWeb.kt @@ -0,0 +1,237 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler +import im.wangchao.mhttp.callback.JsonCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web.IdziennikWebSwitchRegister +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.io.File +import java.net.HttpURLConnection.HTTP_INTERNAL_ERROR +import java.net.HttpURLConnection.HTTP_UNAUTHORIZED + +open class IdziennikWeb(open val data: DataIdziennik, open val lastSync: Long?) { + companion object { + const val TAG = "IdziennikWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun webApiGet(tag: String, endpoint: String, parameters: Map = emptyMap(), onSuccess: (json: JsonObject) -> Unit) { + d(tag, "Request: Idziennik/Web/API - $IDZIENNIK_WEB_URL/$endpoint") + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null && response?.parserErrorBody == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GET_RECIPIENT_LIST) { + data.error(ApiError(tag, ERROR_IDZIENNIK_WEB_RECIPIENT_LIST_NO_PERMISSION) + .withResponse(response) + .withApiResponse(json)) + return + } + + if (response?.code() == HTTP_INTERNAL_ERROR && endpoint == IDZIENNIK_WEB_GRADES) { + // special override for accounts where displaying grades + // for another student requires switching it manually + if (data.registerId != data.webSelectedRegister) { + IdziennikWebSwitchRegister(data, data.registerId) { + webApiGet(tag, endpoint, parameters, onSuccess) + } + return + } + } + + when { + response?.code() == HTTP_UNAUTHORIZED -> ERROR_IDZIENNIK_WEB_ACCESS_DENIED + response?.code() == HTTP_INTERNAL_ERROR -> ERROR_IDZIENNIK_WEB_SERVER_ERROR + response?.parserErrorBody != null -> when { + response.parserErrorBody.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR + response.parserErrorBody.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED + response.parserErrorBody.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE + else -> ERROR_IDZIENNIK_WEB_OTHER + } + else -> null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json?.toString() ?: response?.parserErrorBody) + .withResponse(response)) + return + } + + if (json == null) { + data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + try { + onSuccess(json) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_API_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$endpoint") + .userAgent(IDZIENNIK_USER_AGENT) + .postJson() + .apply { + val json = JsonObject() + parameters.map { (name, value) -> + when (value) { + is JsonObject -> json.add(name, value) + is JsonArray -> json.add(name, value) + is String -> json.addProperty(name, value) + is Int -> json.addProperty(name, value) + is Long -> json.addProperty(name, value) + is Float -> json.addProperty(name, value) + is Char -> json.addProperty(name, value) + is Boolean -> json.addProperty(name, value) + } + } + setJsonBody(json) + } + .allowErrorCode(HTTP_UNAUTHORIZED) + .allowErrorCode(HTTP_INTERNAL_ERROR) + .callback(callback) + .build() + .enqueue() + } + + fun webGet(tag: String, endpoint: String, parameters: Map = emptyMap(), onSuccess: (text: String) -> Unit) { + d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + if (!text.contains("czyWyswietlicDostepMobilny")) { + when { + text.contains("Identyfikator zgłoszenia") -> ERROR_IDZIENNIK_WEB_SERVER_ERROR + text.contains("Hasło dostępu do systemu wygasło") -> ERROR_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED + text.contains("Trwają prace konserwacyjne") -> ERROR_IDZIENNIK_WEB_MAINTENANCE + else -> ERROR_IDZIENNIK_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_IDZIENNIK_WEB_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$endpoint") + .userAgent(IDZIENNIK_USER_AGENT) + .apply { + if (parameters.isEmpty()) get() + else post() + + parameters.map { (name, value) -> + addParameter(name, value) + } + } + .callback(callback) + .build() + .enqueue() + } + + fun webGetFile(tag: String, endpoint: String, targetFile: File, parameters: Map, + onSuccess: (file: File) -> Unit, onProgress: (written: Long, total: Long) -> Unit) { + + d(tag, "Request: Idziennik/Web - $IDZIENNIK_WEB_URL/$endpoint") + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_FILE_REQUEST) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_EDUDZIENNIK_FILE_REQUEST) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$endpoint") + .userAgent(IDZIENNIK_USER_AGENT) + .apply { + parameters.forEach { (k, v) -> addParameter(k, v) } + } + .post() + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt new file mode 100644 index 00000000..0e000fb1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiCurrentRegister.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-29. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_CURRENT_REGISTER +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class IdziennikApiCurrentRegister(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikApi(data, lastSync) { + companion object { + private const val TAG = "IdziennikApiCurrentRegister" + } + + init { + apiGet(TAG, IDZIENNIK_API_CURRENT_REGISTER) { json -> + if (json !is JsonObject) { + onSuccess(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER) + return@apiGet + } + + var nextSync = System.currentTimeMillis() + 14*DAY*1000 + + val settings = json.getJsonObject("ustawienia")?.apply { + getString("poczatekSemestru1")?.let { profile?.dateSemester1Start = Date.fromY_m_d(it) } + getString("koniecSemestru1")?.let { profile?.dateSemester2Start = Date.fromY_m_d(it).stepForward(0, 0, 1) } + getString("koniecSemestru2")?.let { profile?.dateYearEnd = Date.fromY_m_d(it) } + } + + json.getInt("szczesliwyNumerek")?.let { luckyNumber -> + val luckyNumberDate = Date.getToday() + settings.getString("godzinaPublikacjiSzczesliwegoLosu") + ?.let { Time.fromH_m(it) } + ?.let { publishTime -> + val now = Time.getNow() + if (publishTime.value < 150000 && now.value < publishTime.value) { + nextSync = luckyNumberDate.combineWith(publishTime) + luckyNumberDate.stepForward(0, 0, -1) // the lucky number is still for yesterday + } + else if (publishTime.value >= 150000 && now.value > publishTime.value) { + luckyNumberDate.stepForward(0, 0, 1) // the lucky number is already for tomorrow + nextSync = luckyNumberDate.combineWith(publishTime) + } + else if (publishTime.value < 150000) { + nextSync = luckyNumberDate + .clone() + .stepForward(0, 0, 1) + .combineWith(publishTime) + } + else { + nextSync = luckyNumberDate.combineWith(publishTime) + } + } + + + val luckyNumberObject = LuckyNumber( + data.profileId, + luckyNumberDate, + luckyNumber + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + data.profile?.empty ?: false, + System.currentTimeMillis() + )) + } + + + data.setSyncNext(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER, syncAt = nextSync) + onSuccess(ENDPOINT_IDZIENNIK_API_CURRENT_REGISTER) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt new file mode 100644 index 00000000..c8d756fe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesInbox.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api + +import com.google.gson.JsonArray +import pl.szczodrzynski.edziennik.asJsonObjectList +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_MESSAGES_INBOX +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_DELETED +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.getBoolean +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils.crc32 +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikApiMessagesInbox(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikApi(data, lastSync) { + companion object { + private const val TAG = "IdziennikApiMessagesInbox" + } + + init { + apiGet(TAG, IDZIENNIK_API_MESSAGES_INBOX) { json -> + if (json !is JsonArray) { + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX) + return@apiGet + } + + json.asJsonObjectList()?.forEach { jMessage -> + val subject = jMessage.getString("tytul") + if (subject?.contains("(") == true && subject.startsWith("iDziennik - ")) + return@forEach + if (subject?.startsWith("Uwaga dla ucznia (klasa:") == true) + return@forEach + + val messageIdStr = jMessage.getString("id") + val messageId = crc32((messageIdStr + "0").toByteArray()) + + var body = "[META:$messageIdStr;-1]" + body += jMessage.getString("tresc")?.replace("\n".toRegex(), "
    ") + + val readDate = if (jMessage.getBoolean("odczytana") == true) Date.fromIso(jMessage.getString("wersjaRekordu")) else 0 + val sentDate = Date.fromIso(jMessage.getString("dataWyslania")) + + val sender = jMessage.getAsJsonObject("nadawca") + var firstName = sender.getString("imie") + var lastName = sender.getString("nazwisko") + if (firstName.isNullOrEmpty() || lastName.isNullOrEmpty()) { + firstName = "usunięty" + lastName = "użytkownik" + } + val rTeacher = data.getTeacher( + firstName, + lastName + ) + rTeacher.loginId = /*sender.getString("id") + ":" + */sender.getString("usr") + rTeacher.setTeacherType(Teacher.TYPE_OTHER) + + val message = Message( + profileId, + messageId, + subject, + body, + if (jMessage.getBoolean("rekordUsuniety") == true) TYPE_DELETED else TYPE_RECEIVED, + rTeacher.id, + -1 + ) + + val messageRecipient = MessageRecipient( + profileId, + -1 /* me */, + -1, + readDate, + /*messageId*/ messageId + ) + + data.messageIgnoreList.add(message) + data.messageRecipientList.add(messageRecipient) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + message.id, + readDate > 0, + readDate > 0 || profile?.empty ?: false, + sentDate + )) + } + + data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_INBOX) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt new file mode 100644 index 00000000..b0b17689 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/api/IdziennikApiMessagesSent.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api + +import com.google.gson.JsonArray +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES +import pl.szczodrzynski.edziennik.asJsonObjectList +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_API_MESSAGES_SENT +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_API_MESSAGES_SENT +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikApi +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.MessageRecipient +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.utils.Utils.crc32 +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikApiMessagesSent(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikApi(data, lastSync) { + companion object { + private const val TAG = "IdziennikApiMessagesSent" + } + + init { + apiGet(TAG, IDZIENNIK_API_MESSAGES_SENT) { json -> + if (json !is JsonArray) { + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT) + return@apiGet + } + + json.asJsonObjectList()?.forEach { jMessage -> + val messageIdStr = jMessage.get("id").asString + val messageId = crc32((messageIdStr + "1").toByteArray()) + + val subject = jMessage.get("tytul").asString + + var body = "[META:$messageIdStr;-1]" + body += jMessage.get("tresc").asString.replace("\n".toRegex(), "
    ") + + val sentDate = Date.fromIso(jMessage.get("dataWyslania").asString) + + val message = Message( + profileId, + messageId, + subject, + body, + TYPE_SENT, + -1, + -1 + ) + + for (recipientEl in jMessage.getAsJsonArray("odbiorcy")) { + val recipient = recipientEl.asJsonObject + var firstName = recipient.get("imie").asString + var lastName = recipient.get("nazwisko").asString + if (firstName.isEmpty() || lastName.isEmpty()) { + firstName = "usunięty" + lastName = "użytkownik" + } + val rTeacher = data.getTeacher(firstName, lastName) + rTeacher.loginId = /*recipient.get("id").asString + ":" + */recipient.get("usr").asString + + val messageRecipient = MessageRecipient( + profileId, + rTeacher.id, + -1, + -1, + /*messageId*/ messageId + ) + data.messageRecipientIgnoreList.add(messageRecipient) + } + + data.messageIgnoreList.add(message) + data.metadataList.add(Metadata(profileId, Metadata.TYPE_MESSAGE, message.id, true, true, sentDate)) + } + + data.setSyncNext(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) + onSuccess(ENDPOINT_IDZIENNIK_API_MESSAGES_SENT) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt new file mode 100644 index 00000000..f8744efa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAnnouncements.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebAnnouncements(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebAnnouncements" + } + + init { + val param = JsonObject() + param.add("parametryFiltrow", JsonArray()) + + webApiGet(TAG, IDZIENNIK_WEB_ANNOUNCEMENTS, mapOf( + "uczenId" to (data.studentId ?: ""), + "param" to param + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + for (jAnnouncementEl in json.getAsJsonArray("ListK")) { + val jAnnouncement = jAnnouncementEl.asJsonObject + // jAnnouncement + val announcementId = jAnnouncement.getLong("Id") ?: -1 + + val rTeacher = data.getTeacherByFirstLast(jAnnouncement.getString("Autor") ?: "") + val addedDate = jAnnouncement.getString("DataDodania")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull() ?: System.currentTimeMillis() + val startDate = jAnnouncement.getString("DataWydarzenia")?.replace("[^\\d]".toRegex(), "")?.toLongOrNull()?.let { Date.fromMillis(it) } + + val announcementObject = Announcement( + profileId, + announcementId, + jAnnouncement.get("Temat").asString, + jAnnouncement.get("Tresc").asString, + startDate, + null, + rTeacher.id, + null + ) + data.announcementList.add(announcementObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + announcementObject.id, + profile?.empty ?: false, + profile?.empty ?: false, + addedDate + )) + } + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_ANNOUNCEMENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt new file mode 100644 index 00000000..aa0a9998 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebAttendance.kt @@ -0,0 +1,146 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import pl.szczodrzynski.edziennik.crc16 +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_ATTENDANCE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_ATTENDANCE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class IdziennikWebAttendance(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebAttendance" + } + + private var attendanceYear = Date.getToday().year + private var attendanceMonth = Date.getToday().month + private var attendancePrevMonthChecked = false + + init { + getAttendance() + } + + private fun getAttendance() { + webApiGet(TAG, IDZIENNIK_WEB_ATTENDANCE, mapOf( + "idPozDziennika" to data.registerId, + "mc" to attendanceMonth, + "rok" to attendanceYear, + "dataTygodnia" to "" + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + for (jAttendanceEl in json.getAsJsonArray("Obecnosci")) { + val jAttendance = jAttendanceEl.asJsonObject + // jAttendance + val attendanceTypeIdziennik = jAttendance.get("TypObecnosci").asInt + if (attendanceTypeIdziennik == 5 || attendanceTypeIdziennik == 7) + continue + val attendanceDate = Date.fromY_m_d(jAttendance.get("Data").asString) + val attendanceTime = Time.fromH_m(jAttendance.get("OdDoGodziny").asString) + if (attendanceDate.combineWith(attendanceTime) > System.currentTimeMillis()) + continue + + val attendanceId = jAttendance.get("IdLesson").asString.crc16().toLong() + val rSubject = data.getSubject(jAttendance.get("Przedmiot").asString, jAttendance.get("IdPrzedmiot").asLong, "") + val rTeacher = data.getTeacherByFDotSpaceLast(jAttendance.get("PrzedmiotNauczyciel").asString) + + var attendanceName = "obecność" + var attendanceType = Attendance.TYPE_CUSTOM + + when (attendanceTypeIdziennik) { + 1 /* nieobecność usprawiedliwiona */ -> { + attendanceName = "nieobecność usprawiedliwiona" + attendanceType = TYPE_ABSENT_EXCUSED + } + 2 /* spóźnienie */ -> { + attendanceName = "spóźnienie" + attendanceType = TYPE_BELATED + } + 3 /* nieobecność nieusprawiedliwiona */ -> { + attendanceName = "nieobecność nieusprawiedliwiona" + attendanceType = TYPE_ABSENT + } + 4 /* zwolnienie */, 9 /* zwolniony / obecny */ -> { + attendanceType = TYPE_RELEASED + if (attendanceTypeIdziennik == 4) + attendanceName = "zwolnienie" + if (attendanceTypeIdziennik == 9) + attendanceName = "zwolnienie / obecność" + } + 0 /* obecny */, 8 /* Wycieczka */ -> { + attendanceType = TYPE_PRESENT + if (attendanceTypeIdziennik == 8) + attendanceName = "wycieczka" + } + } + + val semester = profile?.dateToSemester(attendanceDate) ?: 1 + + val attendanceObject = Attendance( + profileId, + attendanceId, + rTeacher.id, + rSubject.id, + semester, + attendanceName, + attendanceDate, + attendanceTime, + attendanceType + ) + + data.attendanceList.add(attendanceObject) + if (attendanceObject.type != TYPE_PRESENT) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + attendanceObject.id, + profile?.empty ?: false, + profile?.empty ?: false, + System.currentTimeMillis() + )) + } + } + + val attendanceDateValue = attendanceYear * 10000 + attendanceMonth * 100 + if (profile?.empty == true && attendanceDateValue > profile?.getSemesterStart(1)?.value ?: 99999999) { + attendancePrevMonthChecked = true // do not need to check prev month later + attendanceMonth-- + if (attendanceMonth < 1) { + attendanceMonth = 12 + attendanceYear-- + } + getAttendance() + } else if (!attendancePrevMonthChecked /* get also the previous month */) { + attendanceMonth-- + if (attendanceMonth < 1) { + attendanceMonth = 12 + attendanceYear-- + } + attendancePrevMonthChecked = true + getAttendance() + } else { + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_ATTENDANCE) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt new file mode 100644 index 00000000..3a3b9d3b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebExams.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_EXAMS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_EXAMS +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class IdziennikWebExams(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebExams" + } + + private var examsYear = Date.getToday().year + private var examsMonth = Date.getToday().month + private var examsMonthsChecked = 0 + private var examsNextMonthChecked = false // TO DO temporary // no more // idk + + init { + getExams() + } + + private fun getExams() { + val param = JsonObject().apply { + addProperty("strona", 1) + addProperty("iloscNaStrone", "99") + addProperty("iloscRekordow", -1) + addProperty("kolumnaSort", "ss.Nazwa,sp.Data_sprawdzianu") + addProperty("kierunekSort", 0) + addProperty("maxIloscZaznaczonych", 0) + addProperty("panelFiltrow", 0) + } + + webApiGet(TAG, IDZIENNIK_WEB_EXAMS, mapOf( + "idP" to data.registerId, + "rok" to examsYear, + "miesiac" to examsMonth, + "param" to param + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { exam -> + val id = exam.getLong("_recordId") ?: return@forEach + val examDate = Date.fromY_m_d(exam.getString("data") ?: return@forEach) + val subjectName = exam.getString("przedmiot") ?: return@forEach + val subjectId = data.getSubject(subjectName, null, subjectName).id + val teacherName = exam.getString("wpisal") ?: return@forEach + val teacherId = data.getTeacherByLastFirst(teacherName).id + val topic = exam.getString("zakres") ?: "" + + val lessonList = data.db.timetableDao().getForDateNow(profileId, examDate) + val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.startTime + + val eventType = when (exam.getString("rodzaj")?.toLowerCase(Locale.getDefault())) { + "sprawdzian/praca klasowa", + "sprawdzian", + "praca klasowa" -> Event.TYPE_EXAM + "kartkówka" -> Event.TYPE_SHORT_QUIZ + else -> Event.TYPE_EXAM + } + + val eventObject = Event( + profileId = profileId, + id = id, + date = examDate, + time = startTime, + topic = topic, + color = null, + type = eventType, + teacherId = teacherId, + subjectId = subjectId, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_EVENT, + eventObject.id, + profile?.empty ?: false, + profile?.empty ?: false, + System.currentTimeMillis() + )) + } + + if (profile?.empty == true && examsMonthsChecked < 3 /* how many months backwards to check? */) { + examsMonthsChecked++ + examsMonth-- + if (examsMonth < 1) { + examsMonth = 12 + examsYear-- + } + getExams() + } else if (!examsNextMonthChecked /* get also one month forward */) { + val showDate = Date.getToday().stepForward(0, 1, 0) + examsYear = showDate.year + examsMonth = showDate.month + examsNextMonthChecked = true + getExams() + } else { + data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_EXAMS, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_EXAMS) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetAttachment.kt new file mode 100644 index 00000000..562f527c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetAttachment.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-28 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_ATTACHMENT +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File + +class IdziennikWebGetAttachment(override val data: DataIdziennik, + val message: Message, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + const val TAG = "IdziennikWebGetAttachment" + } + + init { + val messageId = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex().find(message.body ?: "")?.get(2) ?: -1 + val targetFile = File(Utils.getStorageDir(), attachmentName) + + webGetFile(TAG, IDZIENNIK_WEB_GET_ATTACHMENT, targetFile, mapOf( + "id" to messageId, + "fileName" to attachmentName + ), { file -> + val event = AttachmentGetEvent( + profileId, + message.id, + attachmentId, + AttachmentGetEvent.TYPE_FINISHED, + file.absolutePath + ) + + val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.messageId}_${event.attachmentId}") + Utils.writeStringToFile(attachmentDataFile, event.fileName) + + EventBus.getDefault().post(event) + + onSuccess() + + }) { written, _ -> + val event = AttachmentGetEvent( + profileId, + message.id, + attachmentId, + AttachmentGetEvent.TYPE_PROGRESS, + bytesWritten = written + ) + + EventBus.getDefault().post(event) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt new file mode 100644 index 00000000..dfe9fba3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetMessage.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-28 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_MESSAGE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebGetMessage(override val data: DataIdziennik, + private val message: MessageFull, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + const val TAG = "IdziennikWebGetMessage" + } + + init { data.profile?.also { profile -> + val metaPattern = "\\[META:([A-z0-9]+);([0-9-]+)]".toRegex() + val meta = metaPattern.find(message.body!!) + val messageIdString = meta?.get(1) ?: "" + + webApiGet(TAG, IDZIENNIK_WEB_GET_MESSAGE, parameters = mapOf( + "idWiadomosci" to messageIdString, + "typWiadomosci" to if (message.type == TYPE_SENT) 1 else 0 + )) { json -> + json.getJsonObject("d")?.getJsonObject("Wiadomosc")?.also { + val id = it.getLong("_recordId") + message.body = message.body?.replace(metaPattern, "[META:$messageIdString;$id]") + + message.clearAttachments() + it.getJsonArray("ListaZal")?.asJsonObjectList()?.forEach { attachment -> + message.addAttachment( + attachment.getLong("Id") ?: return@forEach, + attachment.getString("Nazwa") ?: return@forEach, + -1 + ) + } + + message.recipients?.clear() + when (message.type) { + TYPE_RECEIVED -> { + val recipientObject = MessageRecipientFull(profileId, -1, message.id) + + val readDateString = it.getString("DataOdczytania") + recipientObject.readDate = if (readDateString.isNullOrBlank()) System.currentTimeMillis() + else Date.fromIso(readDateString) + + recipientObject.fullName = profile.accountName ?: profile.studentNameLong + + data.messageRecipientList.add(recipientObject) + message.addRecipient(recipientObject) + } + + TYPE_SENT -> { + it.getJsonArray("ListaOdbiorcow")?.asJsonObjectList()?.forEach { recipient -> + val recipientName = recipient.getString("NazwaOdbiorcy") ?: return@forEach + val teacher = data.getTeacherByLastFirst(recipientName) + + val recipientObject = MessageRecipientFull(profileId, teacher.id, message.id) + + recipientObject.readDate = recipient.getLong("Status") ?: return@forEach + recipientObject.fullName = teacher.fullName + + data.messageRecipientList.add(recipientObject) + message.addRecipient(recipientObject) + } + } + } + + if (!message.seen) { + message.seen = true + + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + message.id, + message.seen, + message.notified, + message.addedDate + )) + } + + EventBus.getDefault().postSticky(MessageGetEvent(message)) + + data.messageList.add(message) + onSuccess() + } + } + } ?: onSuccess() } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetRecipientList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetRecipientList.kt new file mode 100644 index 00000000..a14efc24 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGetRecipientList.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import androidx.room.OnConflictStrategy +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GET_RECIPIENT_LIST +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class IdziennikWebGetRecipientList(override val data: DataIdziennik, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebGetRecipientList" + } + + init { + webApiGet(TAG, IDZIENNIK_WEB_GET_RECIPIENT_LIST, mapOf( + "idP" to data.registerId + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("ListK_Pracownicy")?.asJsonObjectList()?.forEach { recipient -> + val name = recipient.getString("ImieNazwisko") ?: ": " + val (fullName, subject) = name.split(": ").let { + Pair(it.getOrNull(0), it.getOrNull(1)) + } + val guid = recipient.getString("Id") ?: "" + // get teacher by ID or create it + val teacher = data.getTeacherByFirstLast(fullName ?: " ") + teacher.loginId = guid + teacher.setTeacherType(Teacher.TYPE_TEACHER) + // unset OTHER that is automatically set in IdziennikApiMessages* + teacher.unsetTeacherType(Teacher.TYPE_OTHER) + teacher.typeDescription = subject + } + + json.getJsonArray("ListK_Opiekunowie")?.asJsonObjectList()?.forEach { recipient -> + val name = recipient.getString("ImieNazwisko") ?: ": " + val (fullName, parentOf) = Regexes.IDZIENNIK_MESSAGES_RECIPIENT_PARENT.find(name)?.let { + Pair(it.groupValues.getOrNull(1), it.groupValues.getOrNull(2)) + } ?: Pair(null, null) + val guid = recipient.getString("Id") ?: "" + // get teacher by ID or create it + val teacher = data.getTeacherByFirstLast(fullName ?: " ") + teacher.loginId = guid + teacher.setTeacherType(Teacher.TYPE_PARENT) + // unset OTHER that is automatically set in IdziennikApiMessages* + teacher.unsetTeacherType(Teacher.TYPE_OTHER) + teacher.typeDescription = parentOf + } + + val event = RecipientListGetEvent( + data.profileId, + data.teacherList.filter { it.loginId != null } + ) + + profile?.lastReceiversSync = System.currentTimeMillis() + + data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE + EventBus.getDefault().postSticky(event) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt new file mode 100644 index 00000000..b3237c82 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebGrades.kt @@ -0,0 +1,176 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebGrades(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebGrades" + } + + init { data.profile?.also { profile -> + webApiGet(TAG, IDZIENNIK_WEB_GRADES, mapOf( + "idPozDziennika" to data.registerId + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("Przedmioty")?.asJsonObjectList()?.onEach { subjectJson -> + val subject = data.getSubject( + subjectJson.getString("Przedmiot") ?: return@onEach, + subjectJson.getLong("IdPrzedmiotu") ?: return@onEach, + subjectJson.getString("Przedmiot") ?: return@onEach + ) + subjectJson.getJsonArray("Oceny")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("idK") ?: return@forEach + val category = grade.getString("Kategoria") ?: "" + val name = grade.getString("Ocena") ?: "?" + val semester = grade.getInt("Semestr") ?: 1 + val teacher = data.getTeacherByLastFirst(grade.getString("Wystawil") ?: return@forEach) + + val countToAverage = grade.getBoolean("DoSredniej") ?: true + var value = grade.getFloat("WartoscDoSred") ?: 0.0f + val weight = if (countToAverage) + grade.getFloat("Waga") ?: 0.0f + else + 0.0f + + val gradeColor = grade.getString("Kolor") ?: "" + var colorInt = 0xff2196f3.toInt() + if (gradeColor.isNotEmpty()) { + colorInt = Color.parseColor("#$gradeColor") + } + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_NORMAL, + value = value, + weight = weight, + color = colorInt, + category = category, + description = null, + comment = null, + semester = semester, + teacherId = teacher.id, + subjectId = subject.id) + + when (grade.getInt("Typ")) { + 0 -> { + val history = grade.getJsonArray("Historia")?.asJsonObjectList() + if (history?.isNotEmpty() == true) { + var sum = gradeObject.value * gradeObject.weight + var count = gradeObject.weight + for (historyItem in history) { + val countToTheAverage = historyItem.getBoolean("DoSredniej") ?: false + value = historyItem.get("WartoscDoSred").asFloat + val weight = historyItem.get("Waga").asFloat + + if (value > 0 && countToTheAverage) { + sum += value * weight + count += weight + } + + val historyColor = historyItem.getString("Kolor") ?: "" + colorInt = 0xff2196f3.toInt() + if (historyColor.isNotEmpty()) { + colorInt = Color.parseColor("#$historyColor") + } + + val historyObject = Grade( + profileId = profileId, + id = gradeObject.id * -1, + name = historyItem.getString("Ocena") ?: "", + type = TYPE_NORMAL, + value = value, + weight = if (value > 0f && countToTheAverage) weight * -1f else 0f, + color = colorInt, + category = historyItem.getString("Kategoria"), + description = historyItem.getString("Uzasadnienie"), + comment = null, + semester = historyItem.getInt("Semestr") ?: 1, + teacherId = teacher.id, + subjectId = subject.id) + historyObject.parentId = gradeObject.id + + val addedDate = historyItem.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + + data.gradeList.add(historyObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + historyObject.id, + true, + true, + addedDate + )) + } + // update the current grade's value with an average of all historical grades and itself + if (sum > 0 && count > 0) { + gradeObject.value = sum / count + } + gradeObject.isImprovement = true // gradeObject is the improved grade. Originals are historyObjects + } + } + 1 -> { + gradeObject.type = Grade.TYPE_SEMESTER1_FINAL + gradeObject.name = value.toInt().toString() + gradeObject.weight = 0f + } + 2 -> { + gradeObject.type = Grade.TYPE_YEAR_FINAL + gradeObject.name = value.toInt().toString() + gradeObject.weight = 0f + } + } + + val addedDate = grade.getString("Data_wystaw")?.let { Date.fromY_m_d(it).inMillis } ?: System.currentTimeMillis() + + data.gradeList.add(gradeObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + data.profile.empty, + data.profile.empty, + addedDate + )) + } + } + + data.toRemove.addAll(listOf( + Grade.TYPE_NORMAL, + Grade.TYPE_SEMESTER1_FINAL, + Grade.TYPE_YEAR_FINAL + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_GRADES) + } + } ?: onSuccess(ENDPOINT_IDZIENNIK_WEB_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt new file mode 100644 index 00000000..71ea7ea9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebHomework.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebHomework(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebHomework" + } + + init { + val param = JsonObject().apply { + addProperty("strona", 1) + addProperty("iloscNaStrone", 997) + addProperty("iloscRekordow", -1) + addProperty("kolumnaSort", "DataZadania") + addProperty("kierunekSort", 0) + addProperty("maxIloscZaznaczonych", 0) + addProperty("panelFiltrow", 0) + } + + webApiGet(TAG, IDZIENNIK_WEB_HOMEWORK, mapOf( + "idP" to data.registerId, + "data" to Date.getToday().stringY_m_d, + "wszystkie" to true, + "param" to param + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("ListK")?.asJsonObjectList()?.forEach { homework -> + val id = homework.getLong("_recordId") ?: return@forEach + val eventDate = Date.fromY_m_d(homework.getString("dataO") ?: return@forEach) + val subjectName = homework.getString("przed") ?: return@forEach + val subjectId = data.getSubject(subjectName, null, subjectName).id + val teacherName = homework.getString("usr") ?: return@forEach + val teacherId = data.getTeacherByLastFirst(teacherName).id + val lessonList = data.db.timetableDao().getForDateNow(profileId, eventDate) + val startTime = lessonList.firstOrNull { it.subjectId == subjectId }?.displayStartTime + val topic = homework.getString("tytul") ?: "" + + val seen = when (profile?.empty) { + true -> true + else -> eventDate < Date.getToday() + } + + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacherId, + subjectId = subjectId, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + eventObject.id, + seen, + seen, + System.currentTimeMillis() + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_HOMEWORK, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_HOMEWORK) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt new file mode 100644 index 00000000..dcc560d5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebNotices.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import pl.szczodrzynski.edziennik.crc16 +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.Notice.* +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikWebNotices(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebNotices" + } + + init { + webApiGet(TAG, IDZIENNIK_WEB_NOTICES, mapOf( + "idPozDziennika" to data.registerId + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + for (jNoticeEl in json.getAsJsonArray("SUwaga")) { + val jNotice = jNoticeEl.asJsonObject + // jNotice + val noticeId = jNotice.get("id").asString.crc16().toLong() + + val rTeacher = data.getTeacherByLastFirst(jNotice.get("Nauczyciel").asString) + val addedDate = Date.fromY_m_d(jNotice.get("Data").asString) + + var nType = TYPE_NEUTRAL + val jType = jNotice.get("Typ").asString + if (jType == "n") { + nType = TYPE_NEGATIVE + } else if (jType == "p") { + nType = TYPE_POSITIVE + } + + val noticeObject = Notice( + profileId, + noticeId, + jNotice.get("Tresc").asString, + jNotice.get("Semestr").asInt, + nType, + rTeacher.id) + data.noticeList.add(noticeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_NOTICE, + noticeObject.id, + profile?.empty ?: false, + profile?.empty ?: false, + addedDate.inMillis + )) + } + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_NOTICES, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_NOTICES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt new file mode 100644 index 00000000..fe5a0156 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebProposedGrades.kt @@ -0,0 +1,124 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-28. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import pl.szczodrzynski.edziennik.asJsonObjectList +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_MISSING_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getJsonArray +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils.getWordGradeValue + +class IdziennikWebProposedGrades(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebProposedGrades" + } + + init { data.profile?.also { profile -> + webApiGet(TAG, IDZIENNIK_WEB_MISSING_GRADES, mapOf( + "idPozDziennika" to data.registerId + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { subject -> + val subjectName = subject.getString("Przedmiot") ?: return@forEach + val subjectObject = data.getSubject(subjectName, null, subjectName) + + val semester1Proposed = subject.getString("OcenaSem1") ?: "" + val semester1Value = getWordGradeValue(semester1Proposed) + val semester1Id = subjectObject.id * (-100) - 1 + + val semester2Proposed = subject.getString("OcenaSem2") ?: "" + val semester2Value = getWordGradeValue(semester2Proposed) + val semester2Id = subjectObject.id * (-100) - 2 + + if (semester1Proposed != "") { + val gradeObject = Grade( + profileId = profileId, + id = semester1Id, + name = semester1Value.toString(), + type = TYPE_SEMESTER1_PROPOSED, + value = semester1Value.toFloat(), + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = 1, + teacherId = -1, + subjectId = subjectObject.id + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + gradeObject.id, + profile.empty, + profile.empty, + System.currentTimeMillis() + )) + } + + if (semester2Proposed != "") { + val gradeObject = Grade( + profileId = profileId, + id = semester2Id, + name = semester2Value.toString(), + type = TYPE_YEAR_PROPOSED, + value = semester2Value.toFloat(), + weight = 0f, + color = -1, + category = null, + description = null, + comment = null, + semester = 2, + teacherId = -1, + subjectId = subjectObject.id + ) + + val addedDate = if (data.profile.empty) + data.profile.dateSemester1Start.inMillis + else + System.currentTimeMillis() + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + gradeObject.id, + profile.empty, + profile.empty, + addedDate + )) + } + } + + data.toRemove.addAll(listOf(TYPE_SEMESTER1_PROPOSED, TYPE_YEAR_PROPOSED).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES) + } + } ?: onSuccess(ENDPOINT_IDZIENNIK_WEB_PROPOSED_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt new file mode 100644 index 00000000..9a997a32 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSendMessage.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-30. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_SEND_MESSAGE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.api.IdziennikApiMessagesSent +import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import java.util.* + +class IdziennikWebSendMessage(override val data: DataIdziennik, + val recipients: List, + val subject: String, + val text: String, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebSendMessage" + } + + init { + val recipientsArray = JsonArray() + for (teacher in recipients) { + teacher.loginId?.let { + recipientsArray += it + } + } + + webApiGet(TAG, IDZIENNIK_WEB_SEND_MESSAGE, mapOf( + "Wiadomosc" to JsonObject( + "Tytul" to subject, + "Tresc" to text, + "Confirmation" to false, + "GuidMessage" to UUID.randomUUID().toString().toUpperCase(Locale.ROOT), + "Odbiorcy" to recipientsArray + ) + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + if (json.getBoolean("CzyJestBlad") != false) { + // TODO error + return@webApiGet + } + + IdziennikApiMessagesSent(data, null) { + val message = data.messageIgnoreList.firstOrNull { it.type == Message.TYPE_SENT && it.subject == subject } + val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } + val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + + EventBus.getDefault().postSticky(event) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSwitchRegister.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSwitchRegister.kt new file mode 100644 index 00000000..f225f177 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebSwitchRegister.kt @@ -0,0 +1,36 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_HOME +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString + +class IdziennikWebSwitchRegister(override val data: DataIdziennik, + val registerId: Int, + val onSuccess: () -> Unit +) : IdziennikWeb(data, null) { + companion object { + private const val TAG = "IdziennikWebSwitchRegister" + } + + init { + val hiddenFields = data.loginStore.getLoginData("hiddenFields", JsonObject()) + // TODO error checking + + webGet(TAG, IDZIENNIK_WEB_HOME, mapOf( + "__VIEWSTATE" to hiddenFields.getString("__VIEWSTATE", ""), + "__VIEWSTATEGENERATOR" to hiddenFields.getString("__VIEWSTATEGENERATOR", ""), + "__EVENTVALIDATION" to hiddenFields.getString("__EVENTVALIDATION", ""), + "ctl00\$dxComboUczniowie" to registerId + )) { text -> + Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let { + val registerId = it[1].toIntOrNull() ?: return@let + data.webSelectedRegister = registerId + } + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt new file mode 100644 index 00000000..066346cd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/data/web/IdziennikWebTimetable.kt @@ -0,0 +1,195 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-22 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.web + +import androidx.core.util.set +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.ENDPOINT_IDZIENNIK_WEB_TIMETABLE +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class IdziennikWebTimetable(override val data: DataIdziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : IdziennikWeb(data, lastSync) { + companion object { + private const val TAG = "IdziennikWebTimetable" + } + + init { data.profile?.also { profile -> + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + + 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) + + webApiGet(TAG, IDZIENNIK_WEB_TIMETABLE, mapOf( + "idPozDziennika" to data.registerId, + "pidRokSzkolny" to data.schoolYearId, + "data" to "${weekStart.stringY_m_d}T10:00:00.000Z" + )) { result -> + val json = result.getJsonObject("d") ?: run { + data.error(ApiError(TAG, ERROR_IDZIENNIK_WEB_REQUEST_NO_DATA) + .withApiResponse(result)) + return@webApiGet + } + + json.getJsonArray("GodzinyLekcyjne")?.asJsonObjectList()?.forEachIndexed { index, range -> + val lessonRange = LessonRange( + profileId, + index + 1, + range.getString("Poczatek")?.let { Time.fromH_m(it) } + ?: return@forEachIndexed, + range.getString("Koniec")?.let { Time.fromH_m(it) } ?: return@forEachIndexed + ) + data.lessonRanges[lessonRange.lessonNumber] = lessonRange + } + + val dates = mutableSetOf() + val lessons = mutableListOf() + + json.getJsonArray("Przedmioty")?.asJsonObjectList()?.forEach { lesson -> + val subject = data.getSubject( + lesson.getString("Nazwa") ?: return@forEach, + lesson.getLong("Id"), + lesson.getString("Skrot") ?: "" + ) + val teacher = data.getTeacherByFDotLast(lesson.getString("Nauczyciel") + ?: return@forEach) + + val newSubjectName = lesson.getString("PrzedmiotZastepujacy") + val newSubject = when (newSubjectName.isNullOrBlank()) { + true -> null + else -> data.getSubject(newSubjectName, null, newSubjectName) + } + + val newTeacherName = lesson.getString("NauZastepujacy") + val newTeacher = when (newTeacherName.isNullOrBlank()) { + true -> null + else -> data.getTeacherByFDotLast(newTeacherName) + } + + val weekDay = lesson.getInt("DzienTygodnia")?.minus(1) ?: return@forEach + val lessonRange = data.lessonRanges[lesson.getInt("Godzina")?.plus(1) + ?: return@forEach] + val lessonDate = weekStart.clone().stepForward(0, 0, weekDay) + val classroom = lesson.getString("NazwaSali") + + val type = lesson.getInt("TypZastepstwa") ?: -1 + + val lessonObject = Lesson(profileId, -1) + + when (type) { + 1, 2, 3, 4, 5 -> { + lessonObject.apply { + this.type = Lesson.TYPE_CHANGE + + this.date = lessonDate + this.lessonNumber = lessonRange.lessonNumber + this.startTime = lessonRange.startTime + this.endTime = lessonRange.endTime + this.subjectId = newSubject?.id + this.teacherId = newTeacher?.id + this.teamId = data.teamClass?.id + this.classroom = classroom + + this.oldDate = lessonDate + this.oldLessonNumber = lessonRange.lessonNumber + this.oldStartTime = lessonRange.startTime + this.oldEndTime = lessonRange.endTime + this.oldSubjectId = subject.id + this.oldTeacherId = teacher.id + this.oldTeamId = data.teamClass?.id + this.oldClassroom = classroom + } + } + 0 -> { + lessonObject.apply { + this.type = Lesson.TYPE_CANCELLED + + this.oldDate = lessonDate + this.oldLessonNumber = lessonRange.lessonNumber + this.oldStartTime = lessonRange.startTime + this.oldEndTime = lessonRange.endTime + this.oldSubjectId = subject.id + this.oldTeacherId = teacher.id + this.oldTeamId = data.teamClass?.id + this.oldClassroom = classroom + } + } + else -> { + lessonObject.apply { + this.type = Lesson.TYPE_NORMAL + + this.date = lessonDate + this.lessonNumber = lessonRange.lessonNumber + this.startTime = lessonRange.startTime + this.endTime = lessonRange.endTime + this.subjectId = subject.id + this.teacherId = teacher.id + this.teamId = data.teamClass?.id + this.classroom = classroom + } + } + } + + lessonObject.id = lessonObject.buildId() + + dates.add(lessonDate.value) + lessons.add(lessonObject) + + val seen = profile.empty || lessonDate < Date.getToday() + + if (lessonObject.type != Lesson.TYPE_NORMAL && lessonDate >= Date.getToday()) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_LESSON_CHANGE, + lessonObject.id, + seen, + seen, + System.currentTimeMillis() + )) + } + } + + 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.lessonList.addAll(lessons) + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + + data.setSyncNext(ENDPOINT_IDZIENNIK_WEB_TIMETABLE, SYNC_ALWAYS) + onSuccess(ENDPOINT_IDZIENNIK_WEB_TIMETABLE) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt new file mode 100644 index 00000000..892c12c8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/firstlogin/IdziennikFirstLogin.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-27. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR +import pl.szczodrzynski.edziennik.data.api.IDZIENNIK_WEB_SETTINGS +import pl.szczodrzynski.edziennik.data.api.LOGIN_TYPE_IDZIENNIK +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.data.IdziennikWeb +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login.IdziennikLoginWeb +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.set +import pl.szczodrzynski.edziennik.swapFirstLastName + +class IdziennikFirstLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikFirstLogin" + } + + private val web = IdziennikWeb(data, null) + private val profileList = mutableListOf() + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_IDZIENNIK + var firstProfileId = loginStoreId + + IdziennikLoginWeb(data) { + web.webGet(TAG, IDZIENNIK_WEB_SETTINGS) { text -> + //val accounts = json.getJsonArray("accounts") + + val isParent = Regexes.IDZIENNIK_LOGIN_FIRST_IS_PARENT.find(text)?.get(1) != "0" + val accountNameLong = if (isParent) + Regexes.IDZIENNIK_LOGIN_FIRST_ACCOUNT_NAME.find(text)?.get(1)?.swapFirstLastName()?.fixName() + else null + + var schoolYearStart: Int? = null + var schoolYearEnd: Int? = null + var schoolYearName: String? = null + val schoolYearId = Regexes.IDZIENNIK_LOGIN_FIRST_SCHOOL_YEAR.find(text)?.let { + schoolYearName = it[2]+"/"+it[3] + schoolYearStart = it[2].toIntOrNull() + schoolYearEnd = it[3].toIntOrNull() + it[1].toIntOrNull() + } ?: run { + data.error(ApiError(TAG, ERROR_LOGIN_IDZIENNIK_FIRST_NO_SCHOOL_YEAR) + .withApiResponse(text)) + return@webGet + } + + Regexes.IDZIENNIK_LOGIN_FIRST_STUDENT.findAll(text) + .toMutableList() + .reversed() + .forEach { match -> + val registerId = match[1].toIntOrNull() ?: return@forEach + val studentId = match[2] + val firstName = match[3] + val lastName = match[4] + val className = match[5] + " " + match[6] + + val studentNameLong = "$firstName $lastName".fixName() + val studentNameShort = "$firstName ${lastName[0]}.".fixName() + val accountName = if (accountNameLong == studentNameLong) null else accountNameLong + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + data.webUsername, + studentNameLong, + studentNameShort, + accountName + ).apply { + schoolYearStart?.let { studentSchoolYearStart = it } + studentClassName = className + studentData["studentId"] = studentId + studentData["registerId"] = registerId + studentData["schoolYearId"] = schoolYearId + } + profileList.add(profile) + } + + EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLogin.kt new file mode 100644 index 00000000..e1cef5c1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLogin.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-25. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_IDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.utils.Utils + +class IdziennikLogin(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + data.progress(data.progressStep) + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_IDZIENNIK_WEB -> { + data.startProgress(R.string.edziennik_progress_login_idziennik_web) + IdziennikLoginWeb(data) { onSuccess(loginMethodId) } + } + LOGIN_METHOD_IDZIENNIK_API -> { + data.startProgress(R.string.edziennik_progress_login_idziennik_api) + IdziennikLoginApi(data) { onSuccess(loginMethodId) } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginApi.kt new file mode 100644 index 00000000..2afe481d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginApi.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-27. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login + +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik + +class IdziennikLoginApi(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikLoginApi" + } + + init { run { + if (data.isApiLoginValid()) { + onSuccess() + } + else { + onSuccess() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt new file mode 100644 index 00000000..e15f9410 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/idziennik/login/IdziennikLoginWeb.kt @@ -0,0 +1,171 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-26. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.login + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.idziennik.DataIdziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class IdziennikLoginWeb(val data: DataIdziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "IdziennikLoginWeb" + } + + init { run { + if (data.isWebLoginValid()) { + data.app.cookieJar.set("iuczniowie.progman.pl", "ASP.NET_SessionId_iDziennik", data.webSessionId) + data.app.cookieJar.set("iuczniowie.progman.pl", ".ASPXAUTH", data.webAuth) + onSuccess() + } + else { + data.app.cookieJar.clear("iuczniowie.progman.pl") + if (data.webSchoolName != null && data.webUsername != null && data.webPassword != null) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + private fun loginWithCredentials() { + Utils.d(TAG, "Request: Idziennik/Login/Web - $IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + + val loginCallback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + // login succeeded: there is a start page + if (text.contains("czyWyswietlicDostepMobilny")) { + val cookies = data.app.cookieJar.getAll("iuczniowie.progman.pl") + run { + data.webSessionId = cookies["ASP.NET_SessionId_iDziennik"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_SESSION + data.webAuth = cookies[".ASPXAUTH"] ?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_AUTH + data.apiBearer = cookies["Bearer"]?: return@run ERROR_LOGIN_IDZIENNIK_WEB_NO_BEARER + data.loginExpiryTime = response.getUnixDate() + 30 * MINUTE /* after about 40 minutes the login didn't work already */ + data.apiExpiryTime = response.getUnixDate() + 12 * HOUR /* actually it expires after 24 hours but I'm not sure when does the token refresh. */ + + val hiddenFields = JsonObject() + Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text).forEach { + hiddenFields[it[1]] = it[2] + } + data.loginStore.putLoginData("hiddenFields", hiddenFields) + + Regexes.IDZIENNIK_WEB_SELECTED_REGISTER.find(text)?.let { + val registerId = it[1].toIntOrNull() ?: return@let + data.webSelectedRegister = registerId + } + + data.profile?.let { profile -> + Regexes.IDZIENNIK_WEB_LUCKY_NUMBER.find(text)?.also { + val number = it[1].toIntOrNull() ?: return@also + val luckyNumberObject = LuckyNumber( + data.profileId, + Date.getToday(), + number + ) + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profile.id, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile.empty, + System.currentTimeMillis() + )) + } + } + + return@run null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + + onSuccess() + return + } + + val errorText = Regexes.IDZIENNIK_LOGIN_ERROR.find(text)?.get(1) + when { + errorText?.contains("nieprawidłową nazwę szkoły") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_SCHOOL_NAME + errorText?.contains("nieprawidłowy login lub hasło") == true -> ERROR_LOGIN_IDZIENNIK_WEB_INVALID_LOGIN + text.contains("Identyfikator zgłoszenia") -> ERROR_LOGIN_IDZIENNIK_WEB_SERVER_ERROR + text.contains("Hasło dostępu do systemu wygasło") -> ERROR_LOGIN_IDZIENNIK_WEB_PASSWORD_CHANGE_NEEDED + text.contains("Trwają prace konserwacyjne") -> ERROR_LOGIN_IDZIENNIK_WEB_MAINTENANCE + else -> ERROR_LOGIN_IDZIENNIK_WEB_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + val getCallback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + Request.builder() + .url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + .userAgent(IDZIENNIK_USER_AGENT) + .addHeader("Origin", "https://iuczniowie.progman.pl") + .addHeader("Referer", "$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + .apply { + Regexes.IDZIENNIK_LOGIN_HIDDEN_FIELDS.findAll(text ?: return@apply).forEach { + addParameter(it[1], it[2]) + } + } + .addParameter("ctl00\$ContentPlaceHolder\$nazwaPrzegladarki", IDZIENNIK_USER_AGENT) + .addParameter("ctl00\$ContentPlaceHolder\$NazwaSzkoly", data.webSchoolName) + .addParameter("ctl00\$ContentPlaceHolder\$UserName", data.webUsername) + .addParameter("ctl00\$ContentPlaceHolder\$Password", data.webPassword) + .addParameter("ctl00\$ContentPlaceHolder\$captcha", "") + .addParameter("ctl00\$ContentPlaceHolder\$Logowanie", "Zaloguj") + .post() + .allowErrorCode(502) + .callback(loginCallback) + .build() + .enqueue() + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$IDZIENNIK_WEB_URL/$IDZIENNIK_WEB_LOGIN") + .userAgent(IDZIENNIK_USER_AGENT) + .get() + .allowErrorCode(502) + .callback(getCallback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt new file mode 100644 index 00000000..80a85a9f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/DataLibrus.kt @@ -0,0 +1,280 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-21. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus + +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.isNotNullNorEmpty + +class DataLibrus(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isPortalLoginValid() = portalTokenExpiryTime-30 > currentTimeUnix() && portalRefreshToken.isNotNullNorEmpty() && portalAccessToken.isNotNullNorEmpty() + fun isApiLoginValid() = apiTokenExpiryTime-30 > currentTimeUnix() && apiAccessToken.isNotNullNorEmpty() + fun isSynergiaLoginValid() = synergiaSessionIdExpiryTime-30 > currentTimeUnix() && synergiaSessionId.isNotNullNorEmpty() + fun isMessagesLoginValid() = messagesSessionIdExpiryTime-30 > currentTimeUnix() && messagesSessionId.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isPortalLoginValid()) + loginMethods += LOGIN_METHOD_LIBRUS_PORTAL + if (isApiLoginValid()) + loginMethods += LOGIN_METHOD_LIBRUS_API + if (isSynergiaLoginValid()) { + loginMethods += LOGIN_METHOD_LIBRUS_SYNERGIA + app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", synergiaSessionId) + } + if (isMessagesLoginValid()) { + loginMethods += LOGIN_METHOD_LIBRUS_MESSAGES + app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", messagesSessionId) + } + } + + override fun generateUserCode() = "$schoolName:$apiLogin" + + fun getColor(id: Int?): Int { + return when (id) { + 1 -> 0xFFF0E68C + 2 -> 0xFF87CEFA + 3 -> 0xFFB0C4DE + 4 -> 0xFFF0F8FF + 5 -> 0xFFF0FFFF + 6 -> 0xFFF5F5DC + 7 -> 0xFFFFEBCD + 8 -> 0xFFFFF8DC + 9 -> 0xFFA9A9A9 + 10 -> 0xFFBDB76B + 11 -> 0xFF8FBC8F + 12 -> 0xFFDCDCDC + 13 -> 0xFFDAA520 + 14 -> 0xFFE6E6FA + 15 -> 0xFFFFA07A + 16 -> 0xFF32CD32 + 17 -> 0xFF66CDAA + 18 -> 0xFF66CDAA + 19 -> 0xFFC0C0C0 + 20 -> 0xFFD2B48C + 21 -> 0xFF3333FF + 22 -> 0xFF7B68EE + 23 -> 0xFFBA55D3 + 24 -> 0xFFFFB6C1 + 25 -> 0xFFFF1493 + 26 -> 0xFFDC143C + 27 -> 0xFFFF0000 + 28 -> 0xFFFF8C00 + 29 -> 0xFFFFD700 + 30 -> 0xFFADFF2F + 31 -> 0xFF7CFC00 + else -> 0xff2196f3 + }.toInt() + } + + /* _____ _ _ + | __ \ | | | | + | |__) |__ _ __| |_ __ _| | + | ___/ _ \| '__| __/ _` | | + | | | (_) | | | || (_| | | + |_| \___/|_| \__\__,_|*/ + private var mPortalEmail: String? = null + var portalEmail: String? + get() { mPortalEmail = mPortalEmail ?: loginStore.getLoginData("email", null); return mPortalEmail } + set(value) { loginStore.putLoginData("email", value); mPortalEmail = value } + private var mPortalPassword: String? = null + var portalPassword: String? + get() { mPortalPassword = mPortalPassword ?: loginStore.getLoginData("password", null); return mPortalPassword } + set(value) { loginStore.putLoginData("password", value); mPortalPassword = value } + + private var mPortalAccessToken: String? = null + var portalAccessToken: String? + get() { mPortalAccessToken = mPortalAccessToken ?: loginStore.getLoginData("accessToken", null); return mPortalAccessToken } + set(value) { loginStore.putLoginData("accessToken", value); mPortalAccessToken = value } + private var mPortalRefreshToken: String? = null + var portalRefreshToken: String? + get() { mPortalRefreshToken = mPortalRefreshToken ?: loginStore.getLoginData("refreshToken", null); return mPortalRefreshToken } + set(value) { loginStore.putLoginData("refreshToken", value); mPortalRefreshToken = value } + private var mPortalTokenExpiryTime: Long? = null + var portalTokenExpiryTime: Long + get() { mPortalTokenExpiryTime = mPortalTokenExpiryTime ?: loginStore.getLoginData("tokenExpiryTime", 0L); return mPortalTokenExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("tokenExpiryTime", value); mPortalTokenExpiryTime = value } + + /* _____ _____ + /\ | __ \_ _| + / \ | |__) || | + / /\ \ | ___/ | | + / ____ \| | _| |_ + /_/ \_\_| |____*/ + /** + * A Synergia login, like 1234567u. + * Used: for login (API Login Method) in Synergia mode. + * Used: for login (Synergia Login Method) in Synergia mode. + * And also in various places in [pl.szczodrzynski.edziennik.api.v2.models.Feature]s + */ + private var mApiLogin: String? = null + var apiLogin: String? + get() { mApiLogin = mApiLogin ?: profile?.getStudentData("accountLogin", null); return mApiLogin } + set(value) { profile?.putStudentData("accountLogin", value) ?: return; mApiLogin = value } + /** + * A Synergia password. + * Used: for login (API Login Method) in Synergia mode. + * Used: for login (Synergia Login Method) in Synergia mode. + */ + private var mApiPassword: String? = null + var apiPassword: String? + get() { mApiPassword = mApiPassword ?: profile?.getStudentData("accountPassword", null); return mApiPassword } + set(value) { profile?.putStudentData("accountPassword", value) ?: return; mApiPassword = value } + + /** + * A JST login Code. + * Used only during first login in JST mode. + */ + private var mApiCode: String? = null + var apiCode: String? + get() { mApiCode = mApiCode ?: loginStore.getLoginData("accountCode", null); return mApiCode } + set(value) { + loginStore.putLoginData("accountCode", value); mApiCode = value } + /** + * A JST login PIN. + * Used only during first login in JST mode. + */ + private var mApiPin: String? = null + var apiPin: String? + get() { mApiPin = mApiPin ?: loginStore.getLoginData("accountPin", null); return mApiPin } + set(value) { + loginStore.putLoginData("accountPin", value); mApiPin = value } + + /** + * A Synergia API access token. + * Used in all Api Endpoints. + * Created in Login Method Api. + * Applicable for all login modes. + */ + private var mApiAccessToken: String? = null + var apiAccessToken: String? + get() { mApiAccessToken = mApiAccessToken ?: profile?.getStudentData("accountToken", null); return mApiAccessToken } + set(value) { mApiAccessToken = value; profile?.putStudentData("accountToken", value) ?: return; } + /** + * A Synergia API refresh token. + * Used when refreshing the [apiAccessToken] in JST, Synergia modes. + */ + private var mApiRefreshToken: String? = null + var apiRefreshToken: String? + get() { mApiRefreshToken = mApiRefreshToken ?: profile?.getStudentData("accountRefreshToken", null); return mApiRefreshToken } + set(value) { mApiRefreshToken = value; profile?.putStudentData("accountRefreshToken", value) ?: return; } + /** + * The expiry time for [apiAccessToken], as a UNIX timestamp. + * Used when refreshing the [apiAccessToken] in JST, Synergia modes. + * Used when refreshing the [apiAccessToken] in Portal mode ([pl.szczodrzynski.edziennik.api.v2.librus.login.SynergiaTokenExtractor]) + */ + private var mApiTokenExpiryTime: Long? = null + var apiTokenExpiryTime: Long + get() { mApiTokenExpiryTime = mApiTokenExpiryTime ?: profile?.getStudentData("accountTokenTime", 0L); return mApiTokenExpiryTime ?: 0L } + set(value) { mApiTokenExpiryTime = value; profile?.putStudentData("accountTokenTime", value) ?: return; } + + /** + * A push device ID, generated by Librus when registering + * a FCM token. I don't really know if this has any use, + * but it may be worthy to save that ID. + */ + private var mPushDeviceId: Int? = null + var pushDeviceId: Int + get() { mPushDeviceId = mPushDeviceId ?: profile?.getStudentData("pushDeviceId", 0); return mPushDeviceId ?: 0 } + set(value) { mPushDeviceId = value; profile?.putStudentData("pushDeviceId", value) ?: return; } + + /* _____ _ + / ____| (_) + | (___ _ _ _ __ ___ _ __ __ _ _ __ _ + \___ \| | | | '_ \ / _ \ '__/ _` | |/ _` | + ____) | |_| | | | | __/ | | (_| | | (_| | + |_____/ \__, |_| |_|\___|_| \__, |_|\__,_| + __/ | __/ | + |___/ |__*/ + /** + * A Synergia web Session ID (DZIENNIKSID). + * Used in endpoints with Synergia login method. + */ + private var mSynergiaSessionId: String? = null + var synergiaSessionId: String? + get() { mSynergiaSessionId = mSynergiaSessionId ?: profile?.getStudentData("accountSID", null); return mSynergiaSessionId } + set(value) { profile?.putStudentData("accountSID", value) ?: return; mSynergiaSessionId = value } + /** + * The expiry time for [synergiaSessionId], as a UNIX timestamp. + * Used in endpoints with Synergia login method. + * TODO verify how long is the session ID valid. + */ + private var mSynergiaSessionIdExpiryTime: Long? = null + var synergiaSessionIdExpiryTime: Long + get() { mSynergiaSessionIdExpiryTime = mSynergiaSessionIdExpiryTime ?: profile?.getStudentData("accountSIDTime", 0L); return mSynergiaSessionIdExpiryTime ?: 0L } + set(value) { profile?.putStudentData("accountSIDTime", value) ?: return; mSynergiaSessionIdExpiryTime = value } + + + /** + * A Messages web Session ID (DZIENNIKSID). + * Used in endpoints with Messages login method. + */ + private var mMessagesSessionId: String? = null + var messagesSessionId: String? + get() { mMessagesSessionId = mMessagesSessionId ?: profile?.getStudentData("messagesSID", null); return mMessagesSessionId } + set(value) { profile?.putStudentData("messagesSID", value) ?: return; mMessagesSessionId = value } + /** + * The expiry time for [messagesSessionId], as a UNIX timestamp. + * Used in endpoints with Messages login method. + * TODO verify how long is the session ID valid. + */ + private var mMessagesSessionIdExpiryTime: Long? = null + var messagesSessionIdExpiryTime: Long + get() { mMessagesSessionIdExpiryTime = mMessagesSessionIdExpiryTime ?: profile?.getStudentData("messagesSIDTime", 0L); return mMessagesSessionIdExpiryTime ?: 0L } + set(value) { profile?.putStudentData("messagesSIDTime", value) ?: return; mMessagesSessionIdExpiryTime = value } + + /* ____ _ _ + / __ \| | | | + | | | | |_| |__ ___ _ __ + | | | | __| '_ \ / _ \ '__| + | |__| | |_| | | | __/ | + \____/ \__|_| |_|\___|*/ + var isPremium + get() = profile?.getStudentData("isPremium", false) ?: false + set(value) { profile?.putStudentData("isPremium", value) } + + private var mSchoolName: String? = null + var schoolName: String? + get() { mSchoolName = mSchoolName ?: profile?.getStudentData("schoolName", null); return mSchoolName } + set(value) { profile?.putStudentData("schoolName", value) ?: return; mSchoolName = value } + + private var mUnitId: Long? = null + var unitId: Long + get() { mUnitId = mUnitId ?: profile?.getStudentData("unitId", 0L); return mUnitId ?: 0L } + set(value) { profile?.putStudentData("unitId", value) ?: return; mUnitId = value } + + private var mStartPointsSemester1: Int? = null + var startPointsSemester1: Int + get() { mStartPointsSemester1 = mStartPointsSemester1 ?: profile?.getStudentData("startPointsSemester1", 0); return mStartPointsSemester1 ?: 0 } + set(value) { profile?.putStudentData("startPointsSemester1", value) ?: return; mStartPointsSemester1 = value } + + private var mStartPointsSemester2: Int? = null + var startPointsSemester2: Int + get() { mStartPointsSemester2 = mStartPointsSemester2 ?: profile?.getStudentData("startPointsSemester2", 0); return mStartPointsSemester2 ?: 0 } + set(value) { profile?.putStudentData("startPointsSemester2", value) ?: return; mStartPointsSemester2 = value } + + private var mEnablePointGrades: Boolean? = null + var enablePointGrades: Boolean + get() { mEnablePointGrades = mEnablePointGrades ?: profile?.getStudentData("enablePointGrades", true); return mEnablePointGrades ?: true } + set(value) { profile?.putStudentData("enablePointGrades", value) ?: return; mEnablePointGrades = value } + + private var mEnableDescriptiveGrades: Boolean? = null + var enableDescriptiveGrades: Boolean + get() { mEnableDescriptiveGrades = mEnableDescriptiveGrades ?: profile?.getStudentData("enableDescriptiveGrades", true); return mEnableDescriptiveGrades ?: true } + set(value) { profile?.putStudentData("enableDescriptiveGrades", value) ?: return; mEnableDescriptiveGrades = value } + + private var mTimetableNotPublic: Boolean? = null + var timetableNotPublic: Boolean + get() { mTimetableNotPublic = mTimetableNotPublic ?: profile?.getStudentData("timetableNotPublic", false); return mTimetableNotPublic ?: false } + set(value) { profile?.putStudentData("timetableNotPublic", value) ?: return; mTimetableNotPublic = value } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt new file mode 100644 index 00000000..0a1ed593 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/Librus.kt @@ -0,0 +1,220 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-21. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusData +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.LibrusApiAnnouncementMarkAsRead +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetAttachment +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetRecipientList +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesSendMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaMarkAllAnnouncementsAsRead +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin.LibrusFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLogin +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Librus(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Librus" + } + + val internalErrorList = mutableListOf() + val data: DataLibrus + private var afterLogin: (() -> Unit)? = null + + init { + data = DataLibrus(app, profile, loginStore).apply { + callback = wrapCallback(this@Librus.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(librusLoginMethods, LibrusFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(librusLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + LibrusLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: LibrusData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) { + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + LibrusMessagesGetMessage(data, message) { + completed() + } + } + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + LibrusMessagesSendMessage(data, recipients, subject, text) { + completed() + } + } + } + + override fun markAllAnnouncementsAsRead() { + login(LOGIN_METHOD_LIBRUS_SYNERGIA) { + LibrusSynergiaMarkAllAnnouncementsAsRead(data) { + completed() + } + } + } + + override fun getAnnouncement(announcement: AnnouncementFull) { + login(LOGIN_METHOD_LIBRUS_API) { + LibrusApiAnnouncementMarkAsRead(data, announcement) { + completed() + } + } + } + + override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) { + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + LibrusMessagesGetAttachment(data, message, attachmentId, attachmentName) { + completed() + } + } + } + + override fun getRecipientList() { + login(LOGIN_METHOD_LIBRUS_MESSAGES) { + LibrusMessagesGetRecipientList(data) { + completed() + } + } + } + + override fun firstLogin() { LibrusFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_LIBRUS_PORTAL_ACCESS_DENIED -> { + data.loginMethods.remove(LOGIN_METHOD_LIBRUS_PORTAL) + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_PORTAL) + data.portalTokenExpiryTime = 0 + login() + } + ERROR_LIBRUS_API_ACCESS_DENIED, + ERROR_LIBRUS_API_TOKEN_EXPIRED -> { + data.loginMethods.remove(LOGIN_METHOD_LIBRUS_API) + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_API) + data.apiTokenExpiryTime = 0 + login() + } + ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED -> { + data.loginMethods.remove(LOGIN_METHOD_LIBRUS_SYNERGIA) + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_SYNERGIA) + data.synergiaSessionIdExpiryTime = 0 + login() + } + ERROR_LIBRUS_MESSAGES_ACCESS_DENIED -> { + data.loginMethods.remove(LOGIN_METHOD_LIBRUS_MESSAGES) + data.prepareFor(librusLoginMethods, LOGIN_METHOD_LIBRUS_MESSAGES) + data.messagesSessionIdExpiryTime = 0 + login() + } + ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE, + ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING, + ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED, + ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED, + ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED -> { + login() + } + ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH, + ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED, + ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID -> { + data.portalRefreshToken = null + login() + } + ERROR_LOGIN_LIBRUS_SYNERGIA_TOKEN_INVALID, + ERROR_LOGIN_LIBRUS_SYNERGIA_NO_TOKEN, + ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID -> { + login() + } + ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID -> { + login() + } + ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC -> { + data.timetableNotPublic = true + data() + } + ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE, + ERROR_LIBRUS_API_NOTES_NOT_ACTIVE -> { + data() + } + ERROR_LIBRUS_API_DEVICE_REGISTERED -> { + data.app.config.sync.tokenLibrusList = + data.app.config.sync.tokenLibrusList + data.profileId + data() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt new file mode 100644 index 00000000..98caab4e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/LibrusFeatures.kt @@ -0,0 +1,246 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus + +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature + +const val ENDPOINT_LIBRUS_API_ME = 1001 +const val ENDPOINT_LIBRUS_API_SCHOOLS = 1002 +const val ENDPOINT_LIBRUS_API_CLASSES = 1003 +const val ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES = 1004 +const val ENDPOINT_LIBRUS_API_UNITS = 1005 +const val ENDPOINT_LIBRUS_API_USERS = 1006 +const val ENDPOINT_LIBRUS_API_SUBJECTS = 1007 +const val ENDPOINT_LIBRUS_API_CLASSROOMS = 1008 +const val ENDPOINT_LIBRUS_API_LESSONS = 1009 +const val ENDPOINT_LIBRUS_API_PUSH_CONFIG = 1010 +const val ENDPOINT_LIBRUS_API_TIMETABLES = 1015 +const val ENDPOINT_LIBRUS_API_SUBSTITUTIONS = 1016 +const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES = 1021 +const val ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES = 1022 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES = 1023 +const val ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES = 1024 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADE_CATEGORIES = 1025 +const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES = 1026 +const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS = 1027 +const val ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS = 1030 +const val ENDPOINT_LIBRUS_API_NORMAL_GRADES = 1031 +const val ENDPOINT_LIBRUS_API_POINT_GRADES = 1032 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES = 1033 +const val ENDPOINT_LIBRUS_API_TEXT_GRADES = 1034 +const val ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES = 1035 +const val ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES = 1036 +const val ENDPOINT_LIBRUS_API_EVENT_TYPES = 1040 +const val ENDPOINT_LIBRUS_API_EVENTS = 1041 +const val ENDPOINT_LIBRUS_API_HOMEWORK = 1050 +const val ENDPOINT_LIBRUS_API_LUCKY_NUMBER = 1060 +const val ENDPOINT_LIBRUS_API_NOTICE_TYPES = 1070 +const val ENDPOINT_LIBRUS_API_NOTICES = 1071 +const val ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES = 1080 +const val ENDPOINT_LIBRUS_API_ATTENDANCES = 1081 +const val ENDPOINT_LIBRUS_API_ANNOUNCEMENTS = 1090 +const val ENDPOINT_LIBRUS_API_PT_MEETINGS = 1100 +const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES = 1109 +const val ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS = 1110 +const val ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS = 1120 +const val ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS = 1130 +const val ENDPOINT_LIBRUS_SYNERGIA_INFO = 2010 +const val ENDPOINT_LIBRUS_SYNERGIA_GRADES = 2020 +const val ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK = 2030 +const val ENDPOINT_LIBRUS_MESSAGES_RECEIVED = 3010 +const val ENDPOINT_LIBRUS_MESSAGES_SENT = 3020 +const val ENDPOINT_LIBRUS_MESSAGES_TRASH = 3030 + +val LibrusFeatures = listOf( + + Feature(LOGIN_TYPE_LIBRUS, FEATURE_ALWAYS_NEEDED, listOf( + ENDPOINT_LIBRUS_API_LESSONS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + + // push config + Feature(LOGIN_TYPE_LIBRUS, FEATURE_PUSH_CONFIG, listOf( + ENDPOINT_LIBRUS_API_PUSH_CONFIG to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> + (data as DataLibrus).isPremium && !data.app.config.sync.tokenLibrusList.contains(data.profileId) + }, + + + + + + /** + * Timetable - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_TIMETABLE, listOf( + ENDPOINT_LIBRUS_API_TIMETABLES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_SUBSTITUTIONS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Agenda - using API. + * Events, Parent-teacher meetings, free days (teacher/school/class). + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_AGENDA, listOf( + ENDPOINT_LIBRUS_API_EVENTS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_EVENT_TYPES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_PT_MEETINGS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_SCHOOL_FREE_DAYS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_CLASS_FREE_DAYS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Grades - using API. + * All grades + categories. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( + ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + // Commented out, because TextGrades/Categories is the same as Grades/Categories + /* ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, */ + ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS 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_DESCRIPTIVE_GRADES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_TEXT_GRADES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_DESCRIPTIVE_TEXT_GRADES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Homework - using API. + * Sync only if account has premium access. + */ + /*Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( + ENDPOINT_LIBRUS_API_HOMEWORK to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> + (data as DataLibrus).isPremium + },*/ + /** + * Behaviour - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_BEHAVIOUR, listOf( + ENDPOINT_LIBRUS_API_NOTICES to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Attendance - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_ATTENDANCE, listOf( + ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_ATTENDANCES to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Announcements - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_ANNOUNCEMENTS, listOf( + ENDPOINT_LIBRUS_API_ANNOUNCEMENTS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + + + + + + /** + * Student info - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf( + ENDPOINT_LIBRUS_API_ME to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * School info - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_SCHOOL_INFO, listOf( + ENDPOINT_LIBRUS_API_SCHOOLS to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_UNITS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Class info - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_CLASS_INFO, listOf( + ENDPOINT_LIBRUS_API_CLASSES to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Team info - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_TEAM_INFO, listOf( + ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Lucky number - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_LIBRUS_API_LUCKY_NUMBER to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)).withShouldSync { data -> data.shouldSyncLuckyNumber() }, + /** + * Teacher list - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_TEACHERS, listOf( + ENDPOINT_LIBRUS_API_USERS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Subject list - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_SUBJECTS, listOf( + ENDPOINT_LIBRUS_API_SUBJECTS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + /** + * Classroom list - using API. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_CLASSROOMS, listOf( + ENDPOINT_LIBRUS_API_CLASSROOMS to LOGIN_METHOD_LIBRUS_API + ), listOf(LOGIN_METHOD_LIBRUS_API)), + + /** + * Student info - using synergia scrapper. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_INFO, listOf( + ENDPOINT_LIBRUS_SYNERGIA_INFO to LOGIN_METHOD_LIBRUS_SYNERGIA + ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)), + /** + * Student number - using synergia scrapper. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_STUDENT_NUMBER, listOf( + ENDPOINT_LIBRUS_SYNERGIA_INFO to LOGIN_METHOD_LIBRUS_SYNERGIA + ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)), + + + /** + * Grades - using API + synergia scrapper. + */ + /*Feature(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( + ENDPOINT_LIBRUS_API_NORMAL_GC to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_API_NORMAL_GRADES to LOGIN_METHOD_LIBRUS_API, + ENDPOINT_LIBRUS_SYNERGIA_GRADES to LOGIN_METHOD_LIBRUS_SYNERGIA + ), listOf(LOGIN_METHOD_LIBRUS_API, LOGIN_METHOD_LIBRUS_SYNERGIA)),*/ + /*Endpoint(LOGIN_TYPE_LIBRUS, FEATURE_GRADES, listOf( + ENDPOINT_LIBRUS_SYNERGIA_GRADES to LOGIN_METHOD_LIBRUS_SYNERGIA + ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA)),*/ + + /** + * Homework - using scrapper. + * Sync only if account has not premium access. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_HOMEWORK, listOf( + ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK to LOGIN_METHOD_LIBRUS_SYNERGIA + ), listOf(LOGIN_METHOD_LIBRUS_SYNERGIA))/*.withShouldSync { data -> + !(data as DataLibrus).isPremium + }*/, + + /** + * Messages inbox - using messages website. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_INBOX, listOf( + ENDPOINT_LIBRUS_MESSAGES_RECEIVED to LOGIN_METHOD_LIBRUS_MESSAGES + ), listOf(LOGIN_METHOD_LIBRUS_MESSAGES)), + /** + * Messages sent - using messages website. + */ + Feature(LOGIN_TYPE_LIBRUS, FEATURE_MESSAGES_SENT, listOf( + ENDPOINT_LIBRUS_MESSAGES_SENT to LOGIN_METHOD_LIBRUS_MESSAGES + ), listOf(LOGIN_METHOD_LIBRUS_MESSAGES)) +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusApi.kt new file mode 100644 index 00000000..d06cc219 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusApi.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-21. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.JsonCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection.* + +open class LibrusApi(open val data: DataLibrus, open val lastSync: Long?) { + companion object { + private const val TAG = "LibrusApi" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun apiGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, ignoreErrors: List = emptyList(), onSuccess: (json: JsonObject) -> Unit) { + + d(tag, "Request: Librus/Api - ${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint") + + val callback = object : JsonCallbackHandler() { + 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) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + /* +{"Status":"Error","Code":"DeviceRegistered","Message":"This device is alerdy registered.","Resources":{"..":{"Url":"https:\/\/api.librus.pl\/2.0\/Root"}},"Url":"https:\/\/api.librus.pl\/2.0\/ChangeRegister"}*/ + val error = if (response?.code() == 200) null else + json.getString("Code") ?: + json.getString("Message") ?: + json.getString("Status") ?: + response?.parserErrorBody + error?.let { code -> + when (code) { + "TokenIsExpired" -> ERROR_LIBRUS_API_TOKEN_EXPIRED + "Insufficient scopes" -> ERROR_LIBRUS_API_INSUFFICIENT_SCOPES + "Request is denied" -> ERROR_LIBRUS_API_ACCESS_DENIED + "Resource not found" -> ERROR_LIBRUS_API_RESOURCE_NOT_FOUND + "NotFound" -> ERROR_LIBRUS_API_DATA_NOT_FOUND + "AccessDeny" -> when (json.getString("Message")) { + "Student timetable is not public" -> ERROR_LIBRUS_API_TIMETABLE_NOT_PUBLIC + else -> ERROR_LIBRUS_API_RESOURCE_ACCESS_DENIED + } + "LuckyNumberIsNotActive" -> ERROR_LIBRUS_API_LUCKY_NUMBER_NOT_ACTIVE + "NotesIsNotActive" -> ERROR_LIBRUS_API_NOTES_NOT_ACTIVE + "InvalidRequest" -> ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS + "Nieprawidłowy węzeł." -> ERROR_LIBRUS_API_INCORRECT_ENDPOINT + "NoticeboardProblem" -> ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM + "DeviceRegistered" -> ERROR_LIBRUS_API_DEVICE_REGISTERED + "Maintenance" -> ERROR_LIBRUS_API_MAINTENANCE + else -> ERROR_LIBRUS_API_OTHER + }.let { errorCode -> + if (errorCode !in ignoreErrors) { + data.error(ApiError(tag, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } + } + } + + if (json == null) { + data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + try { + onSuccess(json) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_API_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + // TODO add hotfix for Classrooms 500 + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("${if (data.fakeLogin) FAKE_LIBRUS_API else LIBRUS_API_URL}/$endpoint") + .userAgent(LIBRUS_USER_AGENT) + .addHeader("Authorization", "Bearer ${data.apiAccessToken}") + .apply { + when (method) { + GET -> get() + POST -> post() + } + if (payload != null) + setJsonBody(payload) + } + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_FORBIDDEN) + .allowErrorCode(HTTP_UNAUTHORIZED) + .allowErrorCode(HTTP_UNAVAILABLE) + .allowErrorCode(HTTP_NOT_FOUND) + .allowErrorCode(503) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt new file mode 100644 index 00000000..bf1fdab4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusData.kt @@ -0,0 +1,220 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages.LibrusMessagesGetList +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaHomework +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia.LibrusSynergiaInfo +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.utils.Utils + +class LibrusData(val data: DataLibrus, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "LibrusEndpoints" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + /** + * API + */ + ENDPOINT_LIBRUS_API_ME -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + LibrusApiMe(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_SCHOOLS -> { + data.startProgress(R.string.edziennik_progress_endpoint_school_info) + LibrusApiSchools(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_CLASSES -> { + data.startProgress(R.string.edziennik_progress_endpoint_classes) + LibrusApiClasses(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES -> { + data.startProgress(R.string.edziennik_progress_endpoint_teams) + LibrusApiVirtualClasses(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_UNITS -> { + data.startProgress(R.string.edziennik_progress_endpoint_units) + LibrusApiUnits(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_USERS -> { + data.startProgress(R.string.edziennik_progress_endpoint_teachers) + LibrusApiUsers(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_SUBJECTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_subjects) + LibrusApiSubjects(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_CLASSROOMS -> { + data.startProgress(R.string.edziennik_progress_endpoint_classrooms) + LibrusApiClassrooms(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_LESSONS -> { + data.startProgress(R.string.edziennik_progress_endpoint_lessons) + LibrusApiLessons(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_PUSH_CONFIG -> { + data.startProgress(R.string.edziennik_progress_endpoint_push_config) + LibrusApiPushConfig(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TIMETABLES -> { + data.startProgress(R.string.edziennik_progress_endpoint_timetable) + LibrusApiTimetables(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiBehaviourGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiDescriptiveGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiTextGradeCategories(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_categories) + LibrusApiPointGradeCategories(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_comments) + LibrusApiGradeComments(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_grade_comments) + LibrusApiBehaviourGradeComments(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_NORMAL_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + LibrusApiGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_behaviour_grades) + LibrusApiBehaviourGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_descriptive_grades) + LibrusApiDescriptiveGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEXT_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_descriptive_grades) + LibrusApiTextGrades(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_POINT_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_point_grades) + LibrusApiPointGrades(data, lastSync, onSuccess) + } + + ENDPOINT_LIBRUS_API_EVENT_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_event_types) + LibrusApiEventTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_EVENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_events) + LibrusApiEvents(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + LibrusApiHomework(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_LUCKY_NUMBER -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + LibrusApiLuckyNumber(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_NOTICE_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notice_types) + LibrusApiNoticeTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_NOTICES -> { + data.startProgress(R.string.edziennik_progress_endpoint_notices) + LibrusApiNotices(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance_types) + LibrusApiAttendanceTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_ATTENDANCES -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + LibrusApiAttendances(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_ANNOUNCEMENTS -> { + data.startProgress(R.string.edziennik_progress_endpoint_announcements) + LibrusApiAnnouncements(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_PT_MEETINGS -> { + data.startProgress(R.string.edziennik_progress_endpoint_pt_meetings) + LibrusApiPtMeetings(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES -> { + data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_day_types) + LibrusApiTeacherFreeDayTypes(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS -> { + data.startProgress(R.string.edziennik_progress_endpoint_teacher_free_days) + LibrusApiTeacherFreeDays(data, lastSync, onSuccess) + } + + /** + * SYNERGIA + */ + ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK -> { + data.startProgress(R.string.edziennik_progress_endpoint_homework) + LibrusSynergiaHomework(data, lastSync, onSuccess) + } + ENDPOINT_LIBRUS_SYNERGIA_INFO -> { + data.startProgress(R.string.edziennik_progress_endpoint_student_info) + LibrusSynergiaInfo(data, lastSync, onSuccess) + } + + /** + * MESSAGES + */ + ENDPOINT_LIBRUS_MESSAGES_RECEIVED -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + LibrusMessagesGetList(data, type = Message.TYPE_RECEIVED, lastSync = lastSync, onSuccess = onSuccess) + } + ENDPOINT_LIBRUS_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = lastSync, onSuccess = onSuccess) + } + + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt new file mode 100644 index 00000000..dc8d8c47 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusMessages.kt @@ -0,0 +1,307 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.body.MediaTypeUtils +import im.wangchao.mhttp.callback.FileCallbackHandler +import im.wangchao.mhttp.callback.JsonCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import org.json.JSONObject +import org.json.XML +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.parser.Parser +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.io.File +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 + +open class LibrusMessages(open val data: DataLibrus, open val lastSync: Long?) { + companion object { + private const val TAG = "LibrusMessages" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun messagesGet(tag: String, module: String, method: Int = POST, + parameters: Map? = null, onSuccess: (doc: Document) -> Unit) { + + d(tag, "Request: Librus/Messages - $LIBRUS_MESSAGES_URL/$module") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + when { + text.contains("Niepoprawny login i/lub hasło.") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN + text.contains("Nie odnaleziono wiadomości.") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND + text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED + text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE + text.contains("error") -> ERROR_LIBRUS_MESSAGES_ERROR + text.contains("eVarWhitThisNameNotExists") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("") -> ERROR_LIBRUS_MESSAGES_OTHER + else -> null + }?.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + + try { + val doc = Jsoup.parse(text, "", Parser.xmlParser()) + onSuccess(doc) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId) + + val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val doc = docBuilder.newDocument() + val serviceElement = doc.createElement("service") + val headerElement = doc.createElement("header") + val dataElement = doc.createElement("data") + for ((key, value) in parameters.orEmpty()) { + val element = doc.createElement(key) + element.appendChild(doc.createTextNode(value.toString())) + dataElement.appendChild(element) + } + 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/$module") + .userAgent(SYNERGIA_USER_AGENT) + .setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML) + .apply { + when (method) { + GET -> get() + POST -> post() + } + } + .callback(callback) + .build() + .enqueue() + } + + fun messagesGetJson(tag: String, module: String, method: Int = POST, + parameters: Map? = null, onSuccess: (json: JsonObject?) -> Unit) { + + d(tag, "Request: Librus/Messages - $LIBRUS_MESSAGES_URL/$module") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + when { + text.contains("Niepoprawny login i/lub hasło.") -> ERROR_LOGIN_LIBRUS_MESSAGES_INVALID_LOGIN + text.contains("Nie odnaleziono wiadomości.") -> ERROR_LIBRUS_MESSAGES_NOT_FOUND + text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED + text.contains("eAccessDeny") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("OffLine") -> ERROR_LIBRUS_MESSAGES_MAINTENANCE + text.contains("error") -> ERROR_LIBRUS_MESSAGES_ERROR + text.contains("eVarWhitThisNameNotExists") -> ERROR_LIBRUS_MESSAGES_ACCESS_DENIED + text.contains("") -> ERROR_LIBRUS_MESSAGES_OTHER + else -> null + }?.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(text) + .withResponse(response)) + return + } + + try { + val json: JSONObject? = XML.toJSONObject(text) + onSuccess(JsonParser().parse(json?.toString() ?: "{}")?.asJsonObject) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId) + + val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val doc = docBuilder.newDocument() + val serviceElement = doc.createElement("service") + val headerElement = doc.createElement("header") + val dataElement = doc.createElement("data") + for ((key, value) in parameters.orEmpty()) { + val element = doc.createElement(key) + element.appendChild(doc.createTextNode(value.toString())) + dataElement.appendChild(element) + } + 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/$module") + .userAgent(SYNERGIA_USER_AGENT) + .setTextBody(requestXml, MediaTypeUtils.APPLICATION_XML) + .apply { + when (method) { + GET -> get() + POST -> post() + } + } + .callback(callback) + .build() + .enqueue() + } + + fun sandboxGet(tag: String, action: String, parameters: Map? = null, + onSuccess: (json: JsonObject) -> Unit) { + + d(tag, "Request: Librus/Messages - $LIBRUS_SANDBOX_URL$action") + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + try { + onSuccess(json) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url("$LIBRUS_SANDBOX_URL$action") + .userAgent(SYNERGIA_USER_AGENT) + .apply { + parameters?.forEach { (k, v) -> + addParameter(k, v) + } + } + .post() + .callback(callback) + .build() + .enqueue() + } + + fun sandboxGetFile(tag: String, url: String, targetFile: File, onSuccess: (file: File) -> Unit, + method: Int = GET, + onProgress: (written: Long, total: Long) -> Unit) { + + d(tag, "Request: Librus/Messages - $url") + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_MESSAGES_FILE_REQUEST) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url(url) + .userAgent(SYNERGIA_USER_AGENT) + .also { + when (method) { + POST -> it.post() + else -> it.get() + } + } + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusPortal.kt new file mode 100644 index 00000000..4a5e0af6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusPortal.kt @@ -0,0 +1,106 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.JsonCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection + +open class LibrusPortal(open val data: DataLibrus) { + companion object { + private const val TAG = "LibrusPortal" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun portalGet(tag: String, endpoint: String, method: Int = GET, payload: JsonObject? = null, onSuccess: (json: JsonObject, response: Response?) -> Unit) { + + d(tag, "Request: Librus/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL}$endpoint") + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null) { + data.error(ApiError(tag, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + val error = if (response?.code() == 200) null else + json.getString("reason") ?: + json.getString("message") ?: + json.getString("hint") ?: + json.getString("Code") + error?.let { code -> + when (code) { + "requires_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED + "Access token is invalid" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED + "ApiDisabled" -> ERROR_LIBRUS_PORTAL_API_DISABLED + "Account not found" -> ERROR_LIBRUS_PORTAL_SYNERGIA_NOT_FOUND + "Unable to refresh the account" -> ERROR_LIBRUS_PORTAL_MAINTENANCE + else -> when (json.getString("hint")) { + "Error while decoding to JSON" -> ERROR_LIBRUS_PORTAL_ACCESS_DENIED + else -> ERROR_LIBRUS_PORTAL_OTHER + } + }.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } + } + if (response?.code() == HttpURLConnection.HTTP_OK) { + try { + onSuccess(json, response) + } catch (e: NullPointerException) { + e.printStackTrace() + data.error(ApiError(tag, EXCEPTION_LIBRUS_PORTAL_SYNERGIA_TOKEN) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + + } else { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + Request.builder() + .url((if (data.fakeLogin) FAKE_LIBRUS_PORTAL else LIBRUS_PORTAL_URL) + endpoint) + .userAgent(LIBRUS_USER_AGENT) + .addHeader("Authorization", "Bearer ${data.portalAccessToken}") + .apply { + when (method) { + GET -> get() + POST -> post() + } + if (payload != null) + setJsonBody(payload) + } + .allowErrorCode(HttpURLConnection.HTTP_NOT_FOUND) + .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN) + .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) + .allowErrorCode(HttpURLConnection.HTTP_GONE) + .allowErrorCode(424) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt new file mode 100644 index 00000000..e201ca8f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/LibrusSynergia.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-21. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d + +open class LibrusSynergia(open val data: DataLibrus, open val lastSync: Long?) { + companion object { + private const val TAG = "LibrusSynergia" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + fun synergiaGet(tag: String, endpoint: String, method: Int = GET, + parameters: Map = emptyMap(), onSuccess: (text: String) -> Unit) { + d(tag, "Request: Librus/Synergia - $LIBRUS_SYNERGIA_URL/$endpoint") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + + if (!text.contains("jesteś zalogowany")) { + when { + text.contains("stop.png") -> ERROR_LIBRUS_SYNERGIA_ACCESS_DENIED + text.contains("Przerwa techniczna") -> ERROR_LIBRUS_SYNERGIA_MAINTENANCE + else -> ERROR_LIBRUS_SYNERGIA_OTHER + }.let { errorCode -> + data.error(ApiError(tag, errorCode) + .withResponse(response) + .withApiResponse(text)) + return + } + } + + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_LIBRUS_SYNERGIA_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(tag, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + /*data.app.cookieJar.saveFromResponse(null, listOf( + Cookie.Builder() + .name("DZIENNIKSID") + .value(data.synergiaSessionId!!) + .domain("synergia.librus.pl") + .secure().httpOnly().build() + ))*/ + + Request.builder() + .url("$LIBRUS_SYNERGIA_URL/$endpoint") + .userAgent(LIBRUS_USER_AGENT) + .apply { + when (method) { + GET -> get() + POST -> post() + } + parameters.map { (name, value) -> + addParameter(name, value) + } + } + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt new file mode 100644 index 00000000..d40c1a9a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncementMarkAsRead.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-27 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS +import pl.szczodrzynski.edziennik.data.api.ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM +import pl.szczodrzynski.edziennik.data.api.POST +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.events.AnnouncementGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull + +class LibrusApiAnnouncementMarkAsRead(override val data: DataLibrus, + private val announcement: AnnouncementFull, + val onSuccess: () -> Unit +) : LibrusApi(data, null) { + companion object { + const val TAG = "LibrusApiAnnouncementMarkAsRead" + } + + init { + apiGet(TAG, "SchoolNotices/MarkAsRead/${announcement.idString}", method = POST, + ignoreErrors = listOf( + ERROR_LIBRUS_API_INVALID_REQUEST_PARAMS, + ERROR_LIBRUS_API_NOTICEBOARD_PROBLEM + )) { + announcement.seen = true + + EventBus.getDefault().postSticky(AnnouncementGetEvent(announcement)) + + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + announcement.id, + announcement.seen, + announcement.notified, + announcement.addedDate + )) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt new file mode 100644 index 00000000..8256b859 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAnnouncements.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ANNOUNCEMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Announcement +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiAnnouncements(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiAnnouncements" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "SchoolNotices") { json -> + val announcements = json.getJsonArray("SchoolNotices")?.asJsonObjectList() + + announcements?.forEach { announcement -> + val longId = announcement.getString("Id") ?: return@forEach + val id = longId.crc32() + val subject = announcement.getString("Subject") ?: "" + val text = announcement.getString("Content") ?: "" + val startDate = Date.fromY_m_d(announcement.getString("StartDate")) + val endDate = Date.fromY_m_d(announcement.getString("EndDate")) + val teacherId = announcement.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val addedDate = announcement.getString("CreationDate")?.let { Date.fromIso(it) } + ?: System.currentTimeMillis() + val read = announcement.getBoolean("WasRead") ?: false + + val announcementObject = Announcement( + profileId, + id, + subject, + text, + startDate, + endDate, + teacherId, + longId + ) + + data.announcementList.add(announcementObject) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_ANNOUNCEMENT, + id, + read, + profile.empty || read, + addedDate + )) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_ANNOUNCEMENTS) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt new file mode 100644 index 00000000..d7cbc00c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendanceTypes.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.AttendanceType + +class LibrusApiAttendanceTypes(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiAttendanceTypes" + } + + init { + apiGet(TAG, "Attendances/Types") { json -> + val attendanceTypes = json.getJsonArray("Types")?.asJsonObjectList() + + attendanceTypes?.forEach { attendanceType -> + val id = attendanceType.getLong("Id") ?: return@forEach + val name = attendanceType.getString("Name") ?: "" + val color = attendanceType.getString("ColorRGB")?.let { Color.parseColor("#$it") } ?: -1 + + val standardId = when (attendanceType.getBoolean("Standard") ?: false) { + true -> id + false -> attendanceType.getJsonObject("StandardType")?.getLong("Id") ?: id + } + val type = when (standardId) { + 1L -> Attendance.TYPE_ABSENT + 2L -> Attendance.TYPE_BELATED + 3L -> Attendance.TYPE_ABSENT_EXCUSED + 4L -> Attendance.TYPE_RELEASED + /*100*/else -> Attendance.TYPE_PRESENT + } + + data.attendanceTypes.put(id, AttendanceType(profileId, id, name, type, color)) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_ATTENDANCE_TYPES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt new file mode 100644 index 00000000..3727788d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiAttendances.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-13 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ATTENDANCES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class LibrusApiAttendances(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiAttendances" + } + + init { + if (data.attendanceTypes.isEmpty()) { + data.db.attendanceTypeDao().getAllNow(profileId).toSparseArray(data.attendanceTypes) { it.id } + } + if (data.librusLessons.isEmpty()) { + data.db.librusLessonDao().getAllNow(profileId).toSparseArray(data.librusLessons) { it.lessonId } + } + + apiGet(TAG, "Attendances") { json -> + val attendances = json.getJsonArray("Attendances")?.asJsonObjectList() + + attendances?.forEach { attendance -> + val id = ((attendance.getString("Id") ?: return@forEach) + .replace("[^\\d.]".toRegex(), "")).toLong() + val lessonId = attendance.getJsonObject("Lesson")?.getLong("Id") ?: -1 + val lessonNo = attendance.getInt("LessonNo") ?: return@forEach + val lessonDate = Date.fromY_m_d(attendance.getString("Date")) + val teacherId = attendance.getJsonObject("AddedBy")?.getLong("Id") + val semester = attendance.getInt("Semester") ?: return@forEach + val type = attendance.getJsonObject("Type")?.getLong("Id") ?: return@forEach + val typeObject = data.attendanceTypes[type] ?: null + val topic = typeObject?.name ?: "" + + val startTime = data.lessonRanges.get(lessonNo)?.startTime + + val lesson = if (lessonId != -1L) + data.librusLessons.singleOrNull { it.lessonId == lessonId } + else null + + val attendanceObject = Attendance( + profileId, + id, + teacherId ?: lesson?.teacherId ?: -1, + lesson?.subjectId ?: -1, + semester, + topic, + lessonDate, + startTime ?: Time(0, 0, 0), + typeObject?.type ?: Attendance.TYPE_CUSTOM + ) + + val addedDate = Date.fromIso(attendance.getString("AddDate") ?: return@forEach) + + data.attendanceList.add(attendanceObject) + if(typeObject?.type != Attendance.TYPE_PRESENT) { + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_ATTENDANCE, + id, + profile?.empty ?: false, + profile?.empty ?: false, + addedDate + )) + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_ATTENDANCES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_ATTENDANCES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeCategories.kt new file mode 100644 index 00000000..650dfbb5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeCategories.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-3 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiBehaviourGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiBehaviourGradeCategories" + } + + init { + apiGet(TAG, "BehaviourGrades/Points/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val valueFrom = category.getFloat("ValueFrom") ?: 0f + val valueTo = category.getFloat("ValueTo") ?: 0f + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + Color.BLUE, + name + ).apply { + type = GradeCategory.TYPE_BEHAVIOUR + setValueRange(valueFrom, valueTo) + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES, 1 * WEEK) + onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeComments.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeComments.kt new file mode 100644 index 00000000..eedb1d1d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGradeComments.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-7 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS + +class LibrusApiBehaviourGradeComments(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiBehaviourGradeComments" + } + + init { + apiGet(TAG, "BehaviourGrades/Points/Comments") { json -> + + json.getJsonArray("Comments")?.asJsonObjectList()?.forEach { comment -> + val id = comment.getLong("Id") ?: return@forEach + val text = comment.getString("Text")?.fixWhiteSpaces() ?: return@forEach + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + -1, + text + ).apply { + type = GradeCategory.TYPE_BEHAVIOUR_COMMENT + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADE_COMMENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt new file mode 100644 index 00000000..5a374ec9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiBehaviourGrades.kt @@ -0,0 +1,179 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-3 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_SUM +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date +import java.text.DecimalFormat + +class LibrusApiBehaviourGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiBehaviourGrades" + } + + private val nameFormat by lazy { DecimalFormat("#.##") } + + private val types by lazy { + mapOf( + 1 to ("wz" to "wzorowe"), + 2 to ("bdb" to "bardzo dobre"), + 3 to ("db" to "dobre"), + 4 to ("popr" to "poprawne"), + 5 to ("ndp" to "nieodpowiednie"), + 6 to ("ng" to "naganne") + ) + } + + init { data.profile?.also { profile -> + apiGet(TAG, "BehaviourGrades/Points") { json -> + + if (data.startPointsSemester1 > 0) { + val semester1StartGradeObject = Grade( + profileId = profileId, + id = -101, + name = nameFormat.format(data.startPointsSemester1), + type = TYPE_POINT_SUM, + value = data.startPointsSemester1.toFloat(), + weight = 0f, + color = 0xffbdbdbd.toInt(), + category = data.app.getString(R.string.grade_start_points), + description = data.app.getString(R.string.grade_start_points_format, 1), + comment = null, + semester = 1, + teacherId = -1, + subjectId = 1 + ) + + data.gradeList.add(semester1StartGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + semester1StartGradeObject.id, + true, + true, + profile.getSemesterStart(1).inMillis + )) + } + + if (data.startPointsSemester2 > 0) { + val semester2StartGradeObject = Grade( + profileId = profileId, + id = -102, + name = nameFormat.format(data.startPointsSemester2), + type = TYPE_POINT_SUM, + value = data.startPointsSemester2.toFloat(), + weight = -1f, + color = 0xffbdbdbd.toInt(), + category = data.app.getString(R.string.grade_start_points), + description = data.app.getString(R.string.grade_start_points_format, 2), + comment = null, + semester = 2, + teacherId = -1, + subjectId = 1 + ) + + data.gradeList.add(semester2StartGradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + semester2StartGradeObject.id, + true, + true, + profile.getSemesterStart(2).inMillis + )) + } + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val value = grade.getFloat("Value") + val shortName = grade.getString("ShortName") + val semester = grade.getInt("Semester") ?: profile.currentSemester + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val addedDate = grade.getString("AddDate")?.let { Date.fromIso(it) } + ?: System.currentTimeMillis() + + val text = grade.getString("Text") + val type = grade.getJsonObject("BehaviourGrade")?.getInt("Id")?.let { types[it] } + + val name = when { + type != null -> type.first + value != null -> (if (value > 0) "+" else "") + nameFormat.format(value) + shortName != null -> shortName + else -> return@forEach + } + + val color = data.getColor(when { + value == null || value == 0f -> 12 + value > 0 -> 16 + value < 0 -> 26 + else -> 12 + }) + + val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: -1 + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_BEHAVIOUR + } + + val categoryName = category?.text ?: "" + + val comments = grade.getJsonArray("Comments") + ?.asJsonObjectList() + ?.mapNotNull { comment -> + val cId = comment.getLong("Id") ?: return@mapNotNull null + data.gradeCategories[cId]?.text + } ?: listOf() + + val description = listOfNotNull(type?.second) + comments + + val valueFrom = value ?: category?.valueFrom ?: 0f + val valueTo = category?.valueTo ?: 0f + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_POINT_SUM, + value = valueFrom, + weight = -1f, + color = color, + category = categoryName, + description = text ?: description.join(" - "), + comment = if (text != null) description.join(" - ") else null, + semester = semester, + teacherId = teacherId, + subjectId = 1 + ).apply { + valueMax = valueTo + } + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, Grade.TYPE_POINT_SUM)) + data.setSyncNext(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_BEHAVIOUR_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClasses.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClasses.kt new file mode 100644 index 00000000..171e80e1 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClasses.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-14 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_CLASSES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiClasses(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiClasses" + } + + init { + apiGet(TAG, "Classes") { json -> + json.getJsonObject("Class")?.also { studentClass -> + val id = studentClass.getLong("Id") ?: return@also + val name = studentClass.getString("Number") + + studentClass.getString("Symbol") + val code = data.schoolName + ":" + name + val teacherId = studentClass.getJsonObject("ClassTutor")?.getLong("Id") ?: -1 + + val teamObject = Team( + profileId, + id, + name, + 1, + code, + teacherId + ) + + data.profile?.studentClassName = name + + data.teamList.put(id, teamObject) + + data.unitId = studentClass.getJsonObject("Unit").getLong("Id") ?: 0L + + profile?.apply { + dateSemester1Start = Date.fromY_m_d(studentClass.getString("BeginSchoolYear") + ?: return@apply) + dateSemester2Start = Date.fromY_m_d(studentClass.getString("EndFirstSemester") + ?: return@apply) + dateYearEnd = Date.fromY_m_d(studentClass.getString("EndSchoolYear") + ?: return@apply) + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSES, 4 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_CLASSES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClassrooms.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClassrooms.kt new file mode 100644 index 00000000..80a11105 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiClassrooms.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-24. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_CLASSROOMS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Classroom +import java.util.* + +class LibrusApiClassrooms(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiClassrooms" + } + + init { + apiGet(TAG, "Classrooms") { json -> + val classrooms = json.getJsonArray("Classrooms")?.asJsonObjectList() + + classrooms?.forEach { classroom -> + val id = classroom.getLong("Id") ?: return@forEach + val name = classroom.getString("Name")?.toLowerCase(Locale.getDefault()) ?: "" + val symbol = classroom.getString("Symbol")?.toLowerCase(Locale.getDefault()) ?: "" + val nameShort = name.fixWhiteSpaces().split(" ").onEach { it[0] }.joinToString() + val symbolParts = symbol.fixWhiteSpaces().split(" ") + + val friendlyName = if (name != symbol && !name.contains(symbol) && !name.containsAll(symbolParts) && !nameShort.contains(symbol)) { + classroom.getString("Symbol") + " " + classroom.getString("Name") + } + else { + classroom.getString("Name") ?: "" + } + + data.classrooms.put(id, Classroom(profileId, id, friendlyName)) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_CLASSROOMS, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_CLASSROOMS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGradeCategories.kt new file mode 100644 index 00000000..94ce6972 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGradeCategories.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiDescriptiveGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiDescriptiveGradeCategories" + } + + init { + apiGet(TAG, "DescriptiveTextGrades/Skills") { json -> + json.getJsonArray("Skills")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + color, + name + ).apply { + type = GradeCategory.TYPE_DESCRIPTIVE + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES, 1 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt new file mode 100644 index 00000000..42e318e0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiDescriptiveGrades.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE_TEXT +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_TEXT +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiDescriptiveGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiDescriptiveGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "BaseTextGrades") { json -> + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: return@forEach + val semester = grade.getInt("Semester") ?: return@forEach + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + val description = grade.getString("Grade") + + val categoryId = grade.getJsonObject("Skill")?.getLong("Id") + ?: grade.getJsonObject("Category")?.getLong("Id") + ?: return@forEach + val type = when (grade.getJsonObject("Category")) { + null -> TYPE_DESCRIPTIVE_TEXT + else -> TYPE_TEXT + } + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == when (type) { + TYPE_DESCRIPTIVE_TEXT -> GradeCategory.TYPE_DESCRIPTIVE + else -> GradeCategory.TYPE_NORMAL + } + } + + val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach) + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = " ", + type = type, + value = 0f, + weight = 0f, + color = category?.color ?: -1, + category = category?.text, + description = description, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + data.toRemove.addAll(listOf( + TYPE_DESCRIPTIVE_TEXT, + TYPE_TEXT + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + + data.setSyncNext(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_DESCRIPTIVE_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEventTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEventTypes.kt new file mode 100644 index 00000000..331b8249 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEventTypes.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-24. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENT_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.EventType + +class LibrusApiEventTypes(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiEventTypes" + } + + init { + apiGet(TAG, "HomeWorks/Categories") { json -> + val eventTypes = json.getJsonArray("Categories")?.asJsonObjectList() + + eventTypes?.forEach { eventType -> + val id = eventType.getLong("Id") ?: return@forEach + val name = eventType.getString("Name") ?: "" + val color = data.getColor(eventType.getJsonObject("Color")?.getInt("Id")) + + data.eventTypes.put(id, EventType(profileId, id, name, color)) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_EVENT_TYPES, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_EVENT_TYPES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt new file mode 100644 index 00000000..1dff3e7d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiEvents.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-4. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_EVENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class LibrusApiEvents(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiEvents" + } + + init { + if (data.eventTypes.isEmpty()) { + data.db.eventTypeDao().getAllNow(profileId).toSparseArray(data.eventTypes) { it.id } + } + + apiGet(TAG, "HomeWorks") { json -> + val events = json.getJsonArray("HomeWorks")?.asJsonObjectList() + + events?.forEach { event -> + val id = event.getLong("Id") ?: return@forEach + val eventDate = Date.fromY_m_d(event.getString("Date")) + val topic = event.getString("Content") ?: "" + val type = event.getJsonObject("Category")?.getLong("Id") ?: -1 + val teacherId = event.getJsonObject("CreatedBy")?.getLong("Id") ?: -1 + val subjectId = event.getJsonObject("Subject")?.getLong("Id") ?: -1 + val teamId = event.getJsonObject("Class")?.getLong("Id") ?: -1 + + val lessonNo = event.getInt("LessonNo") + val lessonRange = data.lessonRanges.singleOrNull { it.lessonNumber == lessonNo } + val startTime = lessonRange?.startTime ?: Time.fromH_m(event.getString("TimeFrom")) + val addedDate = Date.fromIso(event.getString("AddDate")) + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = type, + teacherId = teacherId, + subjectId = subjectId, + teamId = teamId + ) + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile?.empty ?: false, + profile?.empty ?: false, + addedDate + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureExceptTypes(listOf( + Event.TYPE_HOMEWORK, + Event.TYPE_PT_MEETING + ))) + + data.setSyncNext(ENDPOINT_LIBRUS_API_EVENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_EVENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeCategories.kt new file mode 100644 index 00000000..518e643f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeCategories.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-5 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS + +class LibrusApiGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiGradeCategories" + } + + init { + apiGet(TAG, "Grades/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name")?.fixWhiteSpaces() ?: "" + val weight = when (category.getBoolean("CountToTheAverage")) { + true -> category.getFloat("Weight") ?: 0f + else -> 0f + } + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + + val gradeCategoryObject = GradeCategory( + profileId, + id, + weight, + color, + name + ) + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt new file mode 100644 index 00000000..971ac35b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGradeComments.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-20 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS + +class LibrusApiGradeComments(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + 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")?.fixWhiteSpaces() ?: return@forEach + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + -1, + text + ).apply { + type = GradeCategory.TYPE_NORMAL_COMMENT + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADE_COMMENTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt new file mode 100644 index 00000000..7ff42fde --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiGrades.kt @@ -0,0 +1,121 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NORMAL_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "Grades") { json -> + val grades = json.getJsonArray("Grades")?.asJsonObjectList() + + grades?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: -1 + val name = grade.getString("Grade") ?: "" + val semester = grade.getInt("Semester") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: -1 + val addedDate = Date.fromIso(grade.getString("AddDate")) + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_NORMAL + } + + val value = Utils.getGradeValue(name) + val weight = if (name == "-" || name == "+" + || name.equals("np", ignoreCase = true) + || name.equals("bz", ignoreCase = true)) 0f + else category?.weight ?: 0f + + val description = grade.getJsonArray("Comments")?.asJsonObjectList()?.let { comments -> + if (comments.isNotEmpty()) { + data.gradeCategories.singleOrNull { + it.type == GradeCategory.TYPE_NORMAL_COMMENT + && it.categoryId == comments[0].asJsonObject.getLong("Id") + }?.text + } else null + } ?: "" + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = when { + grade.getBoolean("IsConstituent") ?: false -> TYPE_NORMAL + grade.getBoolean("IsSemester") ?: false -> if (semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL + grade.getBoolean("IsSemesterProposition") ?: false -> if (semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED + grade.getBoolean("IsFinal") ?: false -> TYPE_YEAR_FINAL + grade.getBoolean("IsFinalProposition") ?: false -> TYPE_YEAR_PROPOSED + else -> TYPE_NORMAL + }, + value = value, + weight = weight, + color = category?.color ?: -1, + category = category?.text ?: "", + description = description, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId + ) + + grade.getJsonObject("Improvement")?.also { + val historicalId = it.getLong("Id") + data.gradeList.firstOrNull { grade -> grade.id == historicalId }?.also { grade -> + grade.parentId = gradeObject.id + if (grade.name == "nb") grade.weight = 0f + } + gradeObject.isImprovement = true + } + + data.gradeList.add(gradeObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_YEAR_FINAL, + TYPE_YEAR_PROPOSED + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + data.setSyncNext(ENDPOINT_LIBRUS_API_NORMAL_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_NORMAL_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt new file mode 100644 index 00000000..d47fbfdf --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiHomework.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-12. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiHomework(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiHomework" + } + + init { + apiGet(TAG, "HomeWorkAssignments") { json -> + val homeworkList = json.getJsonArray("HomeWorkAssignments")?.asJsonObjectList() + + homeworkList?.forEach { homework -> + val id = homework.getLong("Id") ?: return@forEach + val eventDate = Date.fromY_m_d(homework.getString("DueDate")) + val topic = homework.getString("Topic") + "\n" + homework.getString("Text") + val teacherId = homework.getJsonObject("Teacher")?.getLong("Id") ?: -1 + val addedDate = Date.fromY_m_d(homework.getString("Date")) + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = null, + topic = topic, + color = null, + type = -1, + teacherId = teacherId, + subjectId = -1, + teamId = -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + id, + profile?.empty ?: false, + profile?.empty ?: false, + addedDate.inMillis + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_HOMEWORK, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_HOMEWORK) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt new file mode 100644 index 00000000..e32790c9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLessons.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-6. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LESSONS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.LibrusLesson + +class LibrusApiLessons(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiLessons" + } + + init { + apiGet(TAG, "Lessons") { json -> + val lessons = json.getJsonArray("Lessons")?.asJsonObjectList() + + lessons?.forEach { lesson -> + val id = lesson.getLong("Id") ?: return@forEach + val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id") ?: return@forEach + val subjectId = lesson.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + val teamId = lesson.getJsonObject("Class")?.getLong("Id") + + val librusLesson = LibrusLesson( + profileId, + id, + teacherId, + subjectId, + teamId + ) + + data.librusLessons.put(id, librusLesson) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_LESSONS, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_LESSONS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt new file mode 100644 index 00000000..5a52cd69 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiLuckyNumber.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-14 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_LUCKY_NUMBER +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.LuckyNumber +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class LibrusApiLuckyNumber(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiLuckyNumber" + } + + init { + var nextSync = System.currentTimeMillis() + 2*DAY*1000 + + apiGet(TAG, "LuckyNumbers") { json -> + if (json.isJsonNull) { + //profile?.luckyNumberEnabled = false + } else { + json.getJsonObject("LuckyNumber")?.also { luckyNumberEl -> + + val luckyNumberDate = Date.fromY_m_d(luckyNumberEl.getString("LuckyNumberDay")) ?: Date.getToday() + val luckyNumber = luckyNumberEl.getInt("LuckyNumber") ?: -1 + val luckyNumberObject = LuckyNumber( + profileId, + luckyNumberDate, + luckyNumber + ) + + if (luckyNumberDate >= Date.getToday()) + nextSync = luckyNumberDate.combineWith(Time(15, 0, 0)) + else + nextSync = System.currentTimeMillis() + 6*HOUR*1000 + + data.luckyNumberList.add(luckyNumberObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_LUCKY_NUMBER, + luckyNumberObject.date.value.toLong(), + true, + profile?.empty ?: false, + System.currentTimeMillis() + )) + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_LUCKY_NUMBER, syncAt = nextSync) + onSuccess(ENDPOINT_LIBRUS_API_LUCKY_NUMBER) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiMe.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiMe.kt new file mode 100644 index 00000000..9d5ce1d4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiMe.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-3. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_ME +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi + +class LibrusApiMe(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiMe" + } + + init { + apiGet(TAG, "Me") { json -> + val me = json.getJsonObject("Me") + val account = me?.getJsonObject("Account") + val user = me?.getJsonObject("User") + + data.isPremium = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true + + val isParent = account?.getInt("GroupId") == 5 + data.profile?.accountName = + if (isParent) + buildFullName(account?.getString("FirstName"), account?.getString("LastName")) + else null + + data.profile?.studentNameLong = + buildFullName(user?.getString("FirstName"), user?.getString("LastName")) + + data.setSyncNext(ENDPOINT_LIBRUS_API_ME, 2*DAY) + onSuccess(ENDPOINT_LIBRUS_API_ME) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNoticeTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNoticeTypes.kt new file mode 100644 index 00000000..55e753ee --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNoticeTypes.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-24. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NOTICE_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.NoticeType + +class LibrusApiNoticeTypes(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiNoticeTypes" + } + + init { + apiGet(TAG, "Notes/Categories") { json -> + val noticeTypes = json.getJsonArray("Categories")?.asJsonObjectList() + + noticeTypes?.forEach { noticeType -> + val id = noticeType.getLong("Id") ?: return@forEach + val name = noticeType.getString("CategoryName") ?: "" + + data.noticeTypes.put(id, NoticeType(profileId, id, name)) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICE_TYPES, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_NOTICE_TYPES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt new file mode 100644 index 00000000..e05cbfd7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiNotices.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-24. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_NOTICES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiNotices(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiNotices" + } + + init { + if (data.noticeTypes.isEmpty()) { + data.db.noticeTypeDao().getAllNow(profileId).toSparseArray(data.noticeTypes) { it.id } + } + + apiGet(TAG, "Notes") { json -> + val notes = json.getJsonArray("Notes")?.asJsonObjectList() + + notes?.forEach { note -> + val id = note.getLong("Id") ?: return@forEach + val text = note.getString("Text") ?: "" + val categoryId = note.getJsonObject("Category")?.getLong("Id") ?: -1 + val teacherId = note.getJsonObject("AddedBy")?.getLong("Id") ?: -1 + val addedDate = note.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach + + val type = when (note.getInt("Positive")) { + 0 -> Notice.TYPE_NEGATIVE + 1 -> Notice.TYPE_POSITIVE + /*2*/else -> Notice.TYPE_NEUTRAL + } + val categoryText = data.noticeTypes[categoryId]?.name ?: "" + val semester = profile?.dateToSemester(addedDate) ?: 1 + + val noticeObject = Notice( + profileId, + id, + categoryText + "\n" + text, + semester, + type, + teacherId + ) + + data.noticeList.add(noticeObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_NOTICE, + id, + profile?.empty ?: false, + profile?.empty ?: false, + addedDate.inMillis + )) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_NOTICES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_NOTICES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGradeCategories.kt new file mode 100644 index 00000000..bba6ead9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGradeCategories.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiPointGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPointGradeCategories" + } + + init { + apiGet(TAG, "PointGrades/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + val countToAverage = category.getBoolean("CountToTheAverage") ?: true + val weight = if (countToAverage) category.getFloat("Weight") ?: 0f else 0f + val valueFrom = category.getFloat("ValueFrom") ?: 0f + val valueTo = category.getFloat("ValueTo") ?: 0f + + val gradeCategoryObject = GradeCategory( + profileId, + id, + weight, + color, + name + ).apply { + type = GradeCategory.TYPE_POINT + setValueRange(valueFrom, valueTo) + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES, 1 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_POINT_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt new file mode 100644 index 00000000..fae45318 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPointGrades.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_POINT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_POINT_AVG +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiPointGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPointGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "PointGrades") { json -> + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: return@forEach + val semester = grade.getInt("Semester") ?: return@forEach + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + val name = grade.getString("Grade") ?: return@forEach + val value = grade.getFloat("GradeValue") ?: 0f + + val categoryId = grade.getJsonObject("Category")?.getLong("Id") ?: return@forEach + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_POINT + } + + val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach) + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_POINT_AVG, + value = value, + weight = category?.weight ?: 0f, + color = category?.color ?: -1, + category = category?.text ?: "", + description = null, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId + ).apply { + valueMax = category?.valueTo ?: 0f + } + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, TYPE_POINT_AVG)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_POINT_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_POINT_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_POINT_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt new file mode 100644 index 00000000..de696a99 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPtMeetings.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-24. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PT_MEETINGS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class LibrusApiPtMeetings(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPtMeetings" + } + + init { + apiGet(TAG, "ParentTeacherConferences") { json -> + val ptMeetings = json.getJsonArray("ParentTeacherConferences")?.asJsonObjectList() + + ptMeetings?.forEach { meeting -> + val id = meeting.getLong("Id") ?: return@forEach + val topic = meeting.getString("Topic") ?: "" + val teacherId = meeting.getJsonObject("Teacher")?.getLong("Id") ?: -1 + val eventDate = meeting.getString("Date")?.let { Date.fromY_m_d(it) } ?: return@forEach + val startTime = meeting.getString("Time")?.let { + if (it == "00:00:00") + null + else + Time.fromH_m_s(it) + } + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_PT_MEETING, + teacherId = teacherId, + subjectId = -1, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_EVENT, + id, + profile?.empty ?: false, + profile?.empty ?: false, + System.currentTimeMillis() + )) + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_PT_MEETING)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_PT_MEETINGS, 12*HOUR) + onSuccess(ENDPOINT_LIBRUS_API_PT_MEETINGS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPushConfig.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPushConfig.kt new file mode 100644 index 00000000..9073d03f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiPushConfig.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.JsonObject +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_PUSH_CONFIG +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getJsonObject + +class LibrusApiPushConfig(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiPushConfig" + } + + init { data.app.config.sync.tokenLibrus?.also { tokenLibrus -> + if(tokenLibrus.isEmpty()) { + data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS) + data.app.config.sync.tokenLibrusList = + data.app.config.sync.tokenLibrusList + profileId + onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) + return@also + } + + apiGet(TAG, "ChangeRegister", payload = JsonObject( + "provider" to "FCM", + "device" to tokenLibrus, + "sendPush" to "1", + "appVersion" to 4 + )) { json -> + json.getJsonObject("ChangeRegister")?.getInt("Id")?.let { data.pushDeviceId = it } + + // sync always: this endpoint has .shouldSync set + data.setSyncNext(ENDPOINT_LIBRUS_API_PUSH_CONFIG, SYNC_ALWAYS) + data.app.config.sync.tokenLibrusList = + data.app.config.sync.tokenLibrusList + profileId + onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_PUSH_CONFIG) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt new file mode 100644 index 00000000..d405f9bf --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSchools.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-4. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SCHOOLS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.utils.models.Time +import java.util.* + +class LibrusApiSchools(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiSchools" + } + + init { + apiGet(TAG, "Schools") { json -> + val school = json.getJsonObject("School") + val schoolId = school?.getInt("Id") + val schoolNameLong = school?.getString("Name") + + // create the school's short name using first letters of each long name's word + // append the town name and save to student data + val schoolNameShort = schoolNameLong?.firstLettersName + val schoolTown = school?.getString("Town")?.toLowerCase(Locale.getDefault()) + data.schoolName = schoolId.toString() + schoolNameShort + "_" + schoolTown + + school?.getJsonArray("LessonsRange")?.let { ranges -> + data.lessonRanges.clear() + ranges.forEachIndexed { index, rangeEl -> + val range = rangeEl.asJsonObject + val from = range.getString("From") ?: return@forEachIndexed + val to = range.getString("To") ?: return@forEachIndexed + data.lessonRanges.put( + index, + LessonRange( + profileId, + index, + Time.fromH_m(from), + Time.fromH_m(to) + )) + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_SCHOOLS, 4 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_SCHOOLS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt new file mode 100644 index 00000000..cfd9bd31 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiSubjects.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-23. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_SUBJECTS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Subject + +class LibrusApiSubjects(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiSubjects" + } + + init { + apiGet(TAG, "Subjects") { json -> + val subjects = json.getJsonArray("Subjects")?.asJsonObjectList() + + subjects?.forEach { subject -> + val id = subject.getLong("Id") ?: return@forEach + val longName = subject.getString("Name") ?: "" + val shortName = subject.getString("Short") ?: "" + + data.subjectList.put(id, Subject(profileId, id, longName, shortName)) + } + + data.subjectList.put(1, Subject(profileId, 1, "Zachowanie", "zach")) + + data.setSyncNext(ENDPOINT_LIBRUS_API_SUBJECTS, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_SUBJECTS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDayTypes.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDayTypes.kt new file mode 100644 index 00000000..e09504bf --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDayTypes.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-19 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsenceType + +class LibrusApiTeacherFreeDayTypes(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTeacherFreeDayTypes" + } + + init { + apiGet(TAG, "TeacherFreeDays/Types") { json -> + val teacherAbsenceTypes = json.getJsonArray("Types")?.asJsonObjectList() + + teacherAbsenceTypes?.forEach { teacherAbsenceType -> + val id = teacherAbsenceType.getLong("Id") ?: return@forEach + val name = teacherAbsenceType.getString("Name") ?: return@forEach + + val teacherAbsenceTypeObject = TeacherAbsenceType( + profileId, + id, + name + ) + + data.teacherAbsenceTypes.put(id, teacherAbsenceTypeObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES, 7 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAY_TYPES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt new file mode 100644 index 00000000..3d4db1aa --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTeacherFreeDays.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-4. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.TeacherAbsence +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class LibrusApiTeacherFreeDays(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTeacherFreeDays" + } + + init { + if (data.teacherAbsenceTypes.isEmpty()) { + data.db.teacherAbsenceTypeDao().getAllNow(profileId).toSparseArray(data.teacherAbsenceTypes) { it.id } + } + + apiGet(TAG, "TeacherFreeDays") { json -> + val teacherAbsences = json.getJsonArray("TeacherFreeDays")?.asJsonObjectList() + + teacherAbsences?.forEach { teacherAbsence -> + val id = teacherAbsence.getLong("Id") ?: return@forEach + val teacherId = teacherAbsence.getJsonObject("Teacher")?.getLong("Id") + ?: return@forEach + val type = teacherAbsence.getJsonObject("Type").getLong("Id") ?: return@forEach + val name = data.teacherAbsenceTypes.singleOrNull { it.id == type }?.name + val dateFrom = Date.fromY_m_d(teacherAbsence.getString("DateFrom")) + val dateTo = Date.fromY_m_d(teacherAbsence.getString("DateTo")) + val timeFrom = teacherAbsence.getString("TimeFrom")?.let { Time.fromH_m_s(it) } + val timeTo = teacherAbsence.getString("TimeTo")?.let { Time.fromH_m_s(it) } + + val teacherAbsenceObject = TeacherAbsence( + profileId, + id, + teacherId, + type, + name, + dateFrom, + dateTo, + timeFrom, + timeTo + ) + + data.teacherAbsenceList.add(teacherAbsenceObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_TEACHER_ABSENCE, + id, + true, + profile?.empty ?: false, + System.currentTimeMillis() + )) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS, 6*HOUR, DRAWER_ITEM_AGENDA) + onSuccess(ENDPOINT_LIBRUS_API_TEACHER_FREE_DAYS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTemplate.kt new file mode 100644 index 00000000..9fd6afee --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTemplate.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-4. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi + +class LibrusApiTemplate(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApi" + } + + init { + /*apiGet(TAG, "") { json -> + + data.setSyncNext(ENDPOINT_LIBRUS_API_, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_) + }*/ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGradeCategories.kt new file mode 100644 index 00000000..45b6b069 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGradeCategories.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import android.graphics.Color +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class LibrusApiTextGradeCategories(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTextGradeCategories" + } + + init { + apiGet(TAG, "TextGrades/Categories") { json -> + json.getJsonArray("Categories")?.asJsonObjectList()?.forEach { category -> + val id = category.getLong("Id") ?: return@forEach + val name = category.getString("Name") ?: "" + val color = category.getJsonObject("Color")?.getInt("Id") + ?.let { data.getColor(it) } ?: Color.BLUE + + val gradeCategoryObject = GradeCategory( + profileId, + id, + -1f, + color, + name + ).apply { + type = GradeCategory.TYPE_TEXT + } + + data.gradeCategories.put(id, gradeCategoryObject) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES, 1 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_TEXT_GRADE_CATEGORIES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt new file mode 100644 index 00000000..cf020425 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTextGrades.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-12-29 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TEXT_GRADES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_DESCRIPTIVE +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusApiTextGrades(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTextGrades" + } + + init { data.profile?.also { profile -> + apiGet(TAG, "DescriptiveGrades") { json -> + + json.getJsonArray("Grades")?.asJsonObjectList()?.forEach { grade -> + val id = grade.getLong("Id") ?: return@forEach + val teacherId = grade.getJsonObject("AddedBy")?.getLong("Id") ?: return@forEach + val semester = grade.getInt("Semester") ?: return@forEach + val subjectId = grade.getJsonObject("Subject")?.getLong("Id") ?: return@forEach + + val map = grade.getString("Map") + val realValue = grade.getString("RealGradeValue") + + val name = map ?: realValue ?: return@forEach + val description = if (map != null && map != realValue) realValue ?: "" else "" + + val categoryId = grade.getJsonObject("Skill")?.getLong("Id") ?: return@forEach + + val category = data.gradeCategories.singleOrNull { + it.categoryId == categoryId && it.type == GradeCategory.TYPE_DESCRIPTIVE + } + + val addedDate = Date.fromIso(grade.getString("AddDate") ?: return@forEach) + + val gradeObject = Grade( + profileId = profileId, + id = id, + name = name, + type = TYPE_DESCRIPTIVE, + value = 0f, + weight = 0f, + color = category?.color ?: -1, + category = category?.text ?: "", + description = description, + comment = grade.getString("Phrase") /* whatever it is */, + semester = semester, + teacherId = teacherId, + subjectId = subjectId + ) + + data.gradeList.add(gradeObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_GRADE, + id, + profile.empty, + profile.empty, + addedDate + )) + } + + data.toRemove.add(DataRemoveModel.Grades.semesterWithType(profile.currentSemester, TYPE_DESCRIPTIVE)) + + data.setSyncNext(ENDPOINT_LIBRUS_API_TEXT_GRADES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_TEXT_GRADES) + } + } ?: onSuccess(ENDPOINT_LIBRUS_API_TEXT_GRADES) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt new file mode 100644 index 00000000..7c1eec61 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiTimetables.kt @@ -0,0 +1,207 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-10. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import androidx.core.util.isEmpty +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_TIMETABLES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS +import pl.szczodrzynski.edziennik.utils.Utils.d +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week + +class LibrusApiTimetables(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiTimetables" + } + + init { + if (data.classrooms.isEmpty()) { + data.db.classroomDao().getAllNow(profileId).toSparseArray(data.classrooms) { it.id } + } + + val currentWeekStart = Week.getWeekStart() + + if (Date.getToday().weekDay > 4) { + currentWeekStart.stepForward(0, 0, 7) + } + + 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, "Timetables?weekStart=${weekStart.stringY_m_d}") { json -> + val days = json.getJsonObject("Timetable") + + days?.entrySet()?.forEach { (dateString, dayEl) -> + val day = dayEl?.asJsonArray + + val lessonDate = dateString?.let { Date.fromY_m_d(it) } ?: return@forEach + + var lessonsFound = false + day?.forEach { lessonRangeEl -> + val lessonRange = lessonRangeEl?.asJsonArray?.asJsonObjectList() + if (lessonRange?.isNullOrEmpty() == false) + lessonsFound = true + lessonRange?.forEach { lesson -> + parseLesson(lessonDate, lesson) + } + } + + if (day.isNullOrEmpty() || !lessonsFound) { + data.lessonList.add(Lesson(profileId, lessonDate.value.toLong()).apply { + type = Lesson.TYPE_NO_LESSONS + date = lessonDate + }) + } + } + + d(TAG, "Clearing lessons between ${weekStart.stringY_m_d} and ${weekEnd.stringY_m_d} - timetable downloaded for $getDate") + + if (data.timetableNotPublic) data.timetableNotPublic = false + + data.toRemove.add(DataRemoveModel.Timetable.between(weekStart, weekEnd)) + data.setSyncNext(ENDPOINT_LIBRUS_API_TIMETABLES, SYNC_ALWAYS) + onSuccess(ENDPOINT_LIBRUS_API_TIMETABLES) + } + } + + private fun parseLesson(lessonDate: Date, lesson: JsonObject) { data.profile?.also { profile -> + val isSubstitution = lesson.getBoolean("IsSubstitutionClass") ?: false + val isCancelled = lesson.getBoolean("IsCanceled") ?: false + + val lessonNo = lesson.getInt("LessonNo") ?: return + val startTime = lesson.getString("HourFrom")?.let { Time.fromH_m(it) } ?: return + val endTime = lesson.getString("HourTo")?.let { Time.fromH_m(it) } ?: return + val subjectId = lesson.getJsonObject("Subject")?.getLong("Id") + val teacherId = lesson.getJsonObject("Teacher")?.getLong("Id") + val classroomId = lesson.getJsonObject("Classroom")?.getLong("Id") ?: -1 + val virtualClassId = lesson.getJsonObject("VirtualClass")?.getLong("Id") + val teamId = lesson.getJsonObject("Class")?.getLong("Id") ?: virtualClassId + + val lessonObject = Lesson(profileId, -1) + + if (isSubstitution && isCancelled) { + // shifted lesson - source + val newDate = lesson.getString("NewDate")?.let { Date.fromY_m_d(it) } ?: return + val newLessonNo = lesson.getInt("NewLessonNo") ?: return + val newStartTime = lesson.getString("NewHourFrom")?.let { Time.fromH_m(it) } ?: return + val newEndTime = lesson.getString("NewHourTo")?.let { Time.fromH_m(it) } ?: return + val newSubjectId = lesson.getJsonObject("NewSubject")?.getLong("Id") + val newTeacherId = lesson.getJsonObject("NewTeacher")?.getLong("Id") + val newClassroomId = lesson.getJsonObject("NewClassroom")?.getLong("Id") ?: -1 + val newVirtualClassId = lesson.getJsonObject("NewVirtualClass")?.getLong("Id") + val newTeamId = lesson.getJsonObject("NewClass")?.getLong("Id") ?: newVirtualClassId + + lessonObject.let { + it.type = Lesson.TYPE_SHIFTED_SOURCE + it.oldDate = lessonDate + it.oldLessonNumber = lessonNo + it.oldStartTime = startTime + it.oldEndTime = endTime + it.oldSubjectId = subjectId + it.oldTeacherId = teacherId + it.oldTeamId = teamId + it.oldClassroom = data.classrooms[classroomId]?.name + + it.date = newDate + it.lessonNumber = newLessonNo + it.startTime = newStartTime + it.endTime = newEndTime + it.subjectId = newSubjectId + it.teacherId = newTeacherId + it.teamId = newTeamId + it.classroom = data.classrooms[newClassroomId]?.name + } + } + else if (isSubstitution) { + // lesson change OR shifted lesson - target + val oldDate = lesson.getString("OrgDate")?.let { Date.fromY_m_d(it) } ?: return + val oldLessonNo = lesson.getInt("OrgLessonNo") ?: return + val oldStartTime = lesson.getString("OrgHourFrom")?.let { Time.fromH_m(it) } ?: return + val oldEndTime = lesson.getString("OrgHourTo")?.let { Time.fromH_m(it) } ?: return + val oldSubjectId = lesson.getJsonObject("OrgSubject")?.getLong("Id") + val oldTeacherId = lesson.getJsonObject("OrgTeacher")?.getLong("Id") + val oldClassroomId = lesson.getJsonObject("OrgClassroom")?.getLong("Id") ?: -1 + val oldVirtualClassId = lesson.getJsonObject("OrgVirtualClass")?.getLong("Id") + val oldTeamId = lesson.getJsonObject("OrgClass")?.getLong("Id") ?: oldVirtualClassId + + lessonObject.let { + it.type = if (lessonDate == oldDate && lessonNo == oldLessonNo) Lesson.TYPE_CHANGE else Lesson.TYPE_SHIFTED_TARGET + it.oldDate = oldDate + it.oldLessonNumber = oldLessonNo + it.oldStartTime = oldStartTime + it.oldEndTime = oldEndTime + it.oldSubjectId = oldSubjectId + it.oldTeacherId = oldTeacherId + it.oldTeamId = oldTeamId + it.oldClassroom = data.classrooms[oldClassroomId]?.name + + it.date = lessonDate + it.lessonNumber = lessonNo + it.startTime = startTime + it.endTime = endTime + it.subjectId = subjectId + it.teacherId = teacherId + it.teamId = teamId + it.classroom = data.classrooms[classroomId]?.name + } + } + else if (isCancelled) { + lessonObject.let { + it.type = Lesson.TYPE_CANCELLED + it.oldDate = lessonDate + it.oldLessonNumber = lessonNo + it.oldStartTime = startTime + it.oldEndTime = endTime + it.oldSubjectId = subjectId + it.oldTeacherId = teacherId + it.oldTeamId = teamId + it.oldClassroom = data.classrooms[classroomId]?.name + } + } + else { + lessonObject.let { + it.type = Lesson.TYPE_NORMAL + it.date = lessonDate + it.lessonNumber = lessonNo + it.startTime = startTime + it.endTime = endTime + it.subjectId = subjectId + it.teacherId = teacherId + it.teamId = teamId + it.classroom = data.classrooms[classroomId]?.name + } + } + + lessonObject.id = lessonObject.buildId() + + val seen = profile.empty || lessonDate < Date.getToday() + + if (lessonObject.type != Lesson.TYPE_NORMAL) { + data.metadataList.add( + Metadata( + profileId, + Metadata.TYPE_LESSON_CHANGE, + lessonObject.id, + seen, + seen, + System.currentTimeMillis() + )) + } + data.lessonList.add(lessonObject) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUnits.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUnits.kt new file mode 100644 index 00000000..791283ed --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUnits.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-23. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_UNITS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi + +class LibrusApiUnits(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiUnits" + } + + init { run { + if (data.unitId == 0L) { + data.setSyncNext(ENDPOINT_LIBRUS_API_UNITS, 12 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_UNITS) + return@run + } + + apiGet(TAG, "Units") { json -> + val units = json.getJsonArray("Units")?.asJsonObjectList() + + units?.singleOrNull { it.getLong("Id") == data.unitId }?.also { unit -> + val startPoints = unit.getJsonObject("BehaviourGradesSettings")?.getJsonObject("StartPoints") + startPoints?.apply { + data.startPointsSemester1 = getInt("Semester1", defaultValue = 0) + data.startPointsSemester2 = getInt("Semester2", defaultValue = data.startPointsSemester1) + } + unit.getJsonObject("GradesSettings")?.apply { + data.enablePointGrades = getBoolean("PointGradesEnabled", true) + data.enableDescriptiveGrades = getBoolean("DescriptiveGradesEnabled", true) + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_UNITS, 7 * DAY) + onSuccess(ENDPOINT_LIBRUS_API_UNITS) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUsers.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUsers.kt new file mode 100644 index 00000000..0ac9a5ea --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiUsers.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-23. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_USERS +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class LibrusApiUsers(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiUsers" + } + + init { + apiGet(TAG, "Users") { json -> + val users = json.getJsonArray("Users")?.asJsonObjectList() + + users?.forEach { user -> + val id = user.getLong("Id") ?: return@forEach + val firstName = user.getString("FirstName")?.fixName() ?: "" + val lastName = user.getString("LastName")?.fixName() ?: "" + + val teacher = Teacher(profileId, id, firstName, lastName) + + if (user.getBoolean("IsSchoolAdministrator") == true) + teacher.setTeacherType(Teacher.TYPE_SCHOOL_ADMIN) + if (user.getBoolean("IsPedagogue") == true) + teacher.setTeacherType(Teacher.TYPE_PEDAGOGUE) + + data.teacherList.put(id, teacher) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_USERS, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_USERS) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiVirtualClasses.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiVirtualClasses.kt new file mode 100644 index 00000000..9937f808 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/api/LibrusApiVirtualClasses.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-23. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.api + +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.db.entity.Team + +class LibrusApiVirtualClasses(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusApi(data, lastSync) { + companion object { + const val TAG = "LibrusApiVirtualClasses" + } + + init { + apiGet(TAG, "VirtualClasses") { json -> + val virtualClasses = json.getJsonArray("VirtualClasses")?.asJsonObjectList() + + virtualClasses?.forEach { virtualClass -> + val id = virtualClass.getLong("Id") ?: return@forEach + val name = virtualClass.getString("Name") ?: "" + val teacherId = virtualClass.getJsonObject("Teacher")?.getLong("Id") ?: -1 + val code = "${data.schoolName}:$name" + + data.teamList.put(id, Team(profileId, id, name, 2, code, teacherId)) + } + + data.setSyncNext(ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES, 4*DAY) + onSuccess(ENDPOINT_LIBRUS_API_VIRTUAL_CLASSES) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt new file mode 100644 index 00000000..8446cab7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetAttachment.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent.Companion.TYPE_FINISHED +import pl.szczodrzynski.edziennik.data.api.events.AttachmentGetEvent.Companion.TYPE_PROGRESS +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.utils.Utils +import java.io.File +import kotlin.coroutines.CoroutineContext + +class LibrusMessagesGetAttachment(override val data: DataLibrus, + val message: Message, + val attachmentId: Long, + val attachmentName: String, + val onSuccess: () -> Unit +) : LibrusMessages(data, null), CoroutineScope { + companion object { + const val TAG = "LibrusMessagesGetAttachment" + } + + private var job = Job() + + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Default + + private var getAttachmentCheckKeyTries = 0 + + init { + messagesGet(TAG, "GetFileDownloadLink", parameters = mapOf( + "fileId" to attachmentId, + "msgId" to message.id, + "archive" to 0 + )) { doc -> + val downloadLink = doc.select("response GetFileDownloadLink downloadLink").text() + val keyMatcher = Regexes.LIBRUS_ATTACHMENT_KEY.find(downloadLink) + + if (keyMatcher != null) { + getAttachmentCheckKeyTries = 0 + + val attachmentKey = keyMatcher[1] + getAttachmentCheckKey(attachmentKey) { + downloadAttachment("${LIBRUS_SANDBOX_URL}CSDownload&singleUseKey=$attachmentKey", method = POST) + } + } else { + downloadAttachment(downloadLink, method = GET) + } + } + } + + private fun getAttachmentCheckKey(attachmentKey: String, callback: () -> Unit) { + sandboxGet(TAG, "CSCheckKey", + parameters = mapOf("singleUseKey" to attachmentKey)) { json -> + + when (json.getString("status")) { + "not_downloaded_yet" -> { + if (getAttachmentCheckKeyTries++ > 5) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withApiResponse(json)) + return@sandboxGet + } + launch { + delay(2000) + getAttachmentCheckKey(attachmentKey, callback) + } + } + + "ready" -> { + launch { callback() } + } + + else -> { + data.error(ApiError(TAG, EXCEPTION_LIBRUS_MESSAGES_REQUEST) + .withApiResponse(json)) + } + } + } + } + + private fun downloadAttachment(url: String, method: Int = GET) { + val targetFile = File(Utils.getStorageDir(), attachmentName) + + sandboxGetFile(TAG, url, targetFile, { file -> + + val event = AttachmentGetEvent( + profileId, + message.id, + attachmentId, + TYPE_FINISHED, + file.absolutePath + ) + + val attachmentDataFile = File(Utils.getStorageDir(), ".${profileId}_${event.messageId}_${event.attachmentId}") + Utils.writeStringToFile(attachmentDataFile, event.fileName) + + EventBus.getDefault().post(event) + + onSuccess() + + }) { written, _ -> + val event = AttachmentGetEvent( + profileId, + message.id, + attachmentId, + TYPE_PROGRESS, + bytesWritten = written + ) + + EventBus.getDefault().post(event) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt new file mode 100644 index 00000000..5574970f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetList.kt @@ -0,0 +1,140 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-24 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import pl.szczodrzynski.edziennik.DAY +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_MESSAGES +import pl.szczodrzynski.edziennik.data.api.ERROR_NOT_IMPLEMENTED +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_RECEIVED +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_MESSAGES_SENT +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.db.entity.* +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.Utils +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusMessagesGetList(override val data: DataLibrus, + override val lastSync: Long?, + private val type: Int = TYPE_RECEIVED, + archived: Boolean = false, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusMessages(data, lastSync) { + companion object { + const val TAG = "LibrusMessagesGetList" + } + + init { + val endpoint = when (type) { + TYPE_RECEIVED -> "Inbox/action/GetList" + Message.TYPE_SENT -> "Outbox/action/GetList" + else -> null + } + val endpointId = when (type) { + TYPE_RECEIVED -> ENDPOINT_LIBRUS_MESSAGES_RECEIVED + else -> ENDPOINT_LIBRUS_MESSAGES_SENT + } + + if (endpoint != null) { + messagesGet(TAG, endpoint, parameters = mapOf( + "archive" to if (archived) 1 else 0 + )) { doc -> + doc.select("GetList data").firstOrNull()?.children()?.forEach { element -> + val id = element.select("messageId").text().toLong() + val subject = element.select("topic").text().trim() + val readDateText = element.select("readDate").text().trim() + val readDate = when (readDateText.isNotBlank()) { + true -> Date.fromIso(readDateText) + else -> 0 + } + val sentDate = Date.fromIso(element.select("sendDate").text().trim()) + + val recipientFirstName = element.select(when (type) { + TYPE_RECEIVED -> "senderFirstName" + else -> "receiverFirstName" + }).text().fixName() + + val recipientLastName = element.select(when (type) { + TYPE_RECEIVED -> "senderLastName" + else -> "receiverLastName" + }).text().fixName() + + val recipientId = data.teacherList.singleOrNull { + it.name == recipientFirstName && it.surname == recipientLastName + }?.id ?: { + val teacherObject = Teacher( + profileId, + -1 * Utils.crc16("$recipientFirstName $recipientLastName".toByteArray()).toLong(), + recipientFirstName, + recipientLastName + ) + 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) { + Message.TYPE_SENT -> true + else -> readDate > 0 || profile?.empty ?: false + } + + val messageObject = Message( + profileId, + id, + subject, + null, + type, + senderId, + -1 + ) + + val messageRecipientObject = MessageRecipient( + profileId, + receiverId, + -1, + readDate, + id + ) + + element.select("isAnyFileAttached")?.text()?.let { + if (it == "1") + messageObject.overrideHasAttachments = true + } + + data.messageIgnoreList.add(messageObject) + data.messageRecipientList.add(messageRecipientObject) + data.setSeenMetadataList.add(Metadata( + profileId, + Metadata.TYPE_MESSAGE, + id, + notified, + notified, + sentDate + )) + } + + when (type) { + TYPE_RECEIVED -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_RECEIVED, SYNC_ALWAYS) + Message.TYPE_SENT -> data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_SENT, DAY, DRAWER_ITEM_MESSAGES) + } + onSuccess(endpointId) + } + } else { + data.error(TAG, ERROR_NOT_IMPLEMENTED) + onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt new file mode 100644 index 00000000..824fdc5e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetMessage.kt @@ -0,0 +1,168 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-11 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import android.util.Base64 +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.MessageGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_RECEIVED +import pl.szczodrzynski.edziennik.data.db.entity.Message.TYPE_SENT +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.data.db.full.MessageRecipientFull +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.notEmptyOrNull +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date +import java.nio.charset.Charset + +class LibrusMessagesGetMessage(override val data: DataLibrus, + private val messageObject: MessageFull, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + const val TAG = "LibrusMessagesGetMessage" + } + + init { data.profile?.also { profile -> + messagesGet(TAG, "GetMessage", parameters = mapOf( + "messageId" to messageObject.id, + "archive" to 0 + )) { doc -> + val message = doc.select("response GetMessage data").first() + + val body = Base64.decode(message.select("Message").text(), Base64.DEFAULT) + .toString(Charset.defaultCharset()) + .replace("\n", "
    ") + .replace("", "") + + messageObject.apply { + this.body = body + + clearAttachments() + message.select("attachments ArrayItem").forEach { + val attachmentId = it.select("id").text().toLong() + val attachmentName = it.select("filename").text() + addAttachment(attachmentId, attachmentName, -1) + } + } + + val messageRecipientList = mutableListOf() + + when (messageObject.type) { + TYPE_RECEIVED -> { + val senderLoginId = message.select("senderId").text().notEmptyOrNull() + val senderGroupId = message.select("senderGroupId").text().toIntOrNull() + val userClass = message.select("userClass").text().notEmptyOrNull() + data.teacherList.singleOrNull { it.id == messageObject.senderId }?.apply { + loginId = senderLoginId + setTeacherType(when (senderGroupId) { + /* https://api.librus.pl/2.0/Messages/Role */ + 0, 1, 99 -> Teacher.TYPE_SUPER_ADMIN + 2 -> Teacher.TYPE_SCHOOL_ADMIN + 3 -> Teacher.TYPE_PRINCIPAL + 4 -> Teacher.TYPE_TEACHER + 5, 9 -> { + if (typeDescription == null) + typeDescription = userClass + Teacher.TYPE_PARENT + } + 7 -> Teacher.TYPE_SECRETARIAT + 8 -> { + if (typeDescription == null) + typeDescription = userClass + Teacher.TYPE_STUDENT + } + 10 -> Teacher.TYPE_PEDAGOGUE + 11 -> Teacher.TYPE_LIBRARIAN + 12 -> Teacher.TYPE_SPECIALIST + 21 -> { + typeDescription = "Jednostka Nadrzędna" + Teacher.TYPE_OTHER + } + 50 -> { + typeDescription = "Jednostka Samorządu Terytorialnego" + Teacher.TYPE_OTHER + } + else -> Teacher.TYPE_OTHER + }) + } + + val readDateText = message.select("readDate").text() + val readDate = when (readDateText.isNotNullNorEmpty()) { + true -> Date.fromIso(readDateText) + else -> 0 + } + + val messageRecipientObject = MessageRecipientFull( + profileId, + -1, + -1, + readDate, + messageObject.id + ) + + messageRecipientObject.fullName = profile.accountName ?: profile.studentNameLong ?: "" + + messageRecipientList.add(messageRecipientObject) + } + + TYPE_SENT -> { + + message.select("receivers ArrayItem").forEach { receiver -> + val receiverFirstName = receiver.select("firstName").text().fixName() + val receiverLastName = receiver.select("lastName").text().fixName() + val receiverLoginId = receiver.select("receiverId").text() + + val teacher = data.teacherList.singleOrNull { it.name == receiverFirstName && it.surname == receiverLastName } + val receiverId = teacher?.id ?: -1 + teacher?.loginId = receiverLoginId + + val readDateText = message.select("readed").text() + val readDate = when (readDateText.isNotNullNorEmpty()) { + true -> Date.fromIso(readDateText) + else -> 0 + } + + val messageRecipientObject = MessageRecipientFull( + profileId, + receiverId, + -1, + readDate, + messageObject.id + ) + + messageRecipientObject.fullName = "$receiverFirstName $receiverLastName" + + messageRecipientList.add(messageRecipientObject) + } + } + } + + if (!messageObject.seen) { + data.setSeenMetadataList.add(Metadata( + messageObject.profileId, + Metadata.TYPE_MESSAGE, + messageObject.id, + true, + true, + messageObject.addedDate + )) + } + + messageObject.recipients = messageRecipientList + data.messageRecipientList.addAll(messageRecipientList) + data.messageList.add(messageObject) + + EventBus.getDefault().postSticky(MessageGetEvent(messageObject)) + onSuccess() + } + } ?: onSuccess()} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetRecipientList.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetRecipientList.kt new file mode 100644 index 00000000..053258d8 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesGetRecipientList.kt @@ -0,0 +1,175 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-31. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import androidx.core.util.set +import androidx.room.OnConflictStrategy +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.RecipientListGetEvent +import pl.szczodrzynski.edziennik.data.db.entity.Teacher + +class LibrusMessagesGetRecipientList(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + private const val TAG = "LibrusMessagesGetRecipientList" + } + + private val listTypes = mutableListOf>() + + init { + messagesGet(TAG, "Receivers/action/GetTypes", parameters = mapOf( + "includeClass" to 1 + )) { doc -> + doc.select("response GetTypes data list ArrayItem")?.forEach { + val id = it.getElementsByTag("id")?.firstOrNull()?.ownText() ?: return@forEach + val name = it.getElementsByTag("name")?.firstOrNull()?.ownText() ?: return@forEach + listTypes += id to name + } + + getLists() + } + } + + private fun getLists() { + if (listTypes.isEmpty()) { + finish() + return + } + val type = listTypes.removeAt(0) + if (type.first == "contactsGroups") { + getLists() + return + } + messagesGetJson(TAG, "Receivers/action/GetListForType", parameters = mapOf( + "receiverType" to type.first + )) { json -> + val dataEl = json?.getJsonObject("response")?.getJsonObject("GetListForType")?.get("data") + if (dataEl is JsonObject) { + val listEl = dataEl.get("ArrayItem") + if (listEl is JsonArray) { + listEl.asJsonObjectList()?.forEach { item -> + processElement(item, type.first, type.second) + } + } + if (listEl is JsonObject) { + processElement(listEl, type.first, type.second) + } + } + + getLists() + } + } + + private fun processElement(element: JsonObject, typeId: String, typeName: String, listName: String? = null) { + val listEl = element.getJsonObject("list")?.get("ArrayItem") + if (listEl is JsonArray) { + listEl.asJsonObjectList()?.let { list -> + val label = element.getString("label") ?: "" + list.forEach { item -> + processElement(item, typeId, typeName, label) + } + return + } + } + if (listEl is JsonObject) { + val label = element.getString("label") ?: "" + processElement(listEl, typeId, typeName, label) + return + } + processRecipient(element, typeId, typeName, listName) + } + + private fun processRecipient(recipient: JsonObject, typeId: String, typeName: String, listName: String? = null) { + val id = recipient.getLong("id") ?: return + val label = recipient.getString("label") ?: return + + val fullNameLastFirst: String + val description: String? + if (typeId == "parentsCouncil" || typeId == "schoolParentsCouncil") { + val delimiterIndex = label.lastIndexOf(" - ") + if (delimiterIndex == -1) { + fullNameLastFirst = label.fixName() + description = null + } + else { + fullNameLastFirst = label.substring(0, delimiterIndex).fixName() + description = label.substring(delimiterIndex+3) + } + } + else { + fullNameLastFirst = label.fixName() + description = null + } + + var typeDescription: String? = null + val type = when (typeId) { + "tutors" -> Teacher.TYPE_EDUCATOR + "teachers" -> Teacher.TYPE_TEACHER + "classParents" -> Teacher.TYPE_PARENT + "guardians" -> Teacher.TYPE_PARENT + "parentsCouncil" -> { + typeDescription = joinNotNullStrings(": ", listName, description) + Teacher.TYPE_PARENTS_COUNCIL + } + "schoolParentsCouncil" -> { + typeDescription = joinNotNullStrings(": ", listName, description) + Teacher.TYPE_SCHOOL_PARENTS_COUNCIL + } + "pedagogue" -> Teacher.TYPE_PEDAGOGUE + "librarian" -> Teacher.TYPE_LIBRARIAN + "admin" -> Teacher.TYPE_SCHOOL_ADMIN + "secretary" -> Teacher.TYPE_SECRETARIAT + "sadmin" -> Teacher.TYPE_SUPER_ADMIN + else -> { + typeDescription = typeName + Teacher.TYPE_OTHER + } + } + + // get teacher by fullName AND type or create it + val teacher = data.teacherList.singleOrNull { + it.fullNameLastFirst == fullNameLastFirst && ((type != Teacher.TYPE_SCHOOL_ADMIN && type != Teacher.TYPE_PEDAGOGUE) || it.isType(type)) + } ?: Teacher(data.profileId, id).apply { + if (typeId == "sadmin" && id == 2L) { + name = "Pomoc" + surname = "Techniczna LIBRUS" + } + else { + name = fullNameLastFirst + fullNameLastFirst.splitName()?.let { + name = it.second + surname = it.first + } + } + data.teacherList[id] = this + } + + teacher.apply { + this.loginId = id.toString() + this.setTeacherType(type) + if (this.typeDescription.isNullOrBlank()) + this.typeDescription = typeDescription + } + } + + private fun finish() { + val event = RecipientListGetEvent( + data.profileId, + data.teacherList.filter { it.loginId != null } + ) + + profile?.lastReceiversSync = System.currentTimeMillis() + + data.teacherOnConflictStrategy = OnConflictStrategy.REPLACE + EventBus.getDefault().postSticky(event) + onSuccess() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt new file mode 100644 index 00000000..02215dea --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesSendMessage.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-2. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.base64Encode +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages +import pl.szczodrzynski.edziennik.data.api.events.MessageSentEvent +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.getJsonObject +import pl.szczodrzynski.edziennik.getLong +import pl.szczodrzynski.edziennik.getString + +class LibrusMessagesSendMessage(override val data: DataLibrus, + val recipients: List, + val subject: String, + val text: String, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + const val TAG = "LibrusMessages" + } + + init { + val params = mapOf( + "topic" to subject.base64Encode(), + "message" to text.base64Encode(), + "receivers" to recipients + .filter { it.loginId != null } + .joinToString(",") { it.loginId ?: "" }, + "actions" to "".base64Encode() + ) + + messagesGetJson(TAG, "SendMessage", parameters = params) { json -> + + val response = json.getJsonObject("response").getJsonObject("SendMessage") + val id = response.getLong("data") + + if (response.getString("status") != "ok" || id == null) { + val message = response.getString("message") + // TODO error + return@messagesGetJson + } + + LibrusMessagesGetList(data, type = Message.TYPE_SENT, lastSync = null) { + val message = data.messageIgnoreList.firstOrNull { it.type == Message.TYPE_SENT && it.id == id } + val metadata = data.metadataList.firstOrNull { it.thingType == Metadata.TYPE_MESSAGE && it.thingId == message?.id } + val event = MessageSentEvent(data.profileId, message, metadata?.addedDate) + + EventBus.getDefault().postSticky(event) + onSuccess() + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesTemplate.kt new file mode 100644 index 00000000..6115e136 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/messages/LibrusMessagesTemplate.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-25 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.messages + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusMessages + +class LibrusMessagesTemplate(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusMessages(data, null) { + companion object { + const val TAG = "LibrusMessages" + } + + init { + /* messagesGet(TAG, "") { doc -> + + data.setSyncNext(ENDPOINT_LIBRUS_MESSAGES_, SYNC_ALWAYS) + onSuccess() + } */ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt new file mode 100644 index 00000000..20c122d3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaHomework.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-22. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.HOUR +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.POST +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.get +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date + +class LibrusSynergiaHomework(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusSynergia(data, lastSync) { + companion object { + const val TAG = "LibrusSynergiaHomework" + } + + init { data.profile?.also { profile -> + synergiaGet(TAG, "moje_zadania", method = POST, parameters = mapOf( + "dataOd" to + if (profile.empty) + profile.getSemesterStart(1).stringY_m_d + else + Date.getToday().stringY_m_d, + "dataDo" to Date.getToday().stepForward(0, 0, 7).stringY_m_d, + "przedmiot" to -1 + + )) { text -> + val doc = Jsoup.parse(text) + + doc.select("table.myHomeworkTable > tbody").firstOrNull()?.also { homeworkTable -> + val homeworkElements = homeworkTable.children() + + val graphElements = doc.select("table[border].center td[align=left] tbody").first().children() + + homeworkElements.forEachIndexed { i, el -> + val elements = el.children() + + val subjectName = elements[0].text().trim() + val subjectId = data.subjectList.singleOrNull { it.longName == subjectName }?.id + ?: -1 + val teacherName = elements[1].text().trim() + val teacherId = data.teacherList.singleOrNull { teacherName == it.fullName }?.id + ?: -1 + val topic = elements[2].text().trim() + val addedDate = Date.fromY_m_d(elements[4].text().trim()) + val eventDate = Date.fromY_m_d(elements[6].text().trim()) + val id = "/podglad/([0-9]+)'".toRegex().find( + elements[9].select("input").attr("onclick") + )?.get(1)?.toLong() ?: return@forEachIndexed + + val lessons = data.db.timetableDao().getForDateNow(profileId, eventDate) + val startTime = lessons.firstOrNull { it.subjectId == subjectId }?.startTime + + /*val moreInfo = graphElements[2 * i + 1].select("td[title]") + .attr("title").trim()*/ + + var description = "" + + graphElements.forEach { graphEl -> + graphEl.select("td[title]")?.also { + val title = it.attr("title") + val r = "Temat: (.*?)Data udostępnienia: (.*?)Termin wykonania: (.*?)Treść: (.*)" + .toRegex(RegexOption.DOT_MATCHES_ALL).find(title) ?: return@forEach + val gTopic = r[1].trim() + val gAddedDate = Date.fromY_m_d(r[2].trim()) + val gEventDate = Date.fromY_m_d(r[3].trim()) + if (gTopic == topic && gAddedDate == addedDate && gEventDate == eventDate) { + description = r[4].replace("".toRegex(), "\n").trim() + return@forEach + } + } + } + + val seen = when (profile.empty) { + true -> true + else -> eventDate < Date.getToday() + } + + val eventObject = Event( + profileId = profileId, + id = id, + date = eventDate, + time = startTime, + topic = "$topic\n$description", + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacherId, + subjectId = subjectId, + teamId = data.teamClass?.id ?: -1 + ) + + data.eventList.add(eventObject) + data.metadataList.add(Metadata( + profileId, + Metadata.TYPE_HOMEWORK, + id, + seen, + seen, + addedDate.inMillis + )) + } + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + + // because this requires a synergia login (2 more requests!!!) sync this every few hours or if explicit :D + data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK, 5 * HOUR, DRAWER_ITEM_HOMEWORK) + onSuccess(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) + } + } ?: onSuccess(ENDPOINT_LIBRUS_SYNERGIA_HOMEWORK) } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaInfo.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaInfo.kt new file mode 100644 index 00000000..66e11785 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaInfo.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import org.jsoup.Jsoup +import pl.szczodrzynski.edziennik.MONTH +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.ENDPOINT_LIBRUS_SYNERGIA_INFO +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia + +class LibrusSynergiaInfo(override val data: DataLibrus, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : LibrusSynergia(data, lastSync) { + companion object { + const val TAG = "LibrusSynergiaInfo" + } + + init { + synergiaGet(TAG, "informacja") { text -> + val doc = Jsoup.parse(text) + + doc.select("table.form tbody").firstOrNull()?.children()?.also { info -> + val studentNumber = info[2].select("td").text().trim().toIntOrNull() + + studentNumber?.also { + data.profile?.studentNumber = it + } + } + + data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_INFO, MONTH) + onSuccess(ENDPOINT_LIBRUS_SYNERGIA_INFO) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt new file mode 100644 index 00000000..04a7f5b6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaMarkAllAnnouncementsAsRead.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-26 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia +import pl.szczodrzynski.edziennik.data.db.entity.Metadata + +class LibrusSynergiaMarkAllAnnouncementsAsRead(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergiaMarkAllAnnouncementsAsRead" + } + + init { + synergiaGet(TAG, "ogloszenia") { + data.app.db.metadataDao().setAllSeen(profileId, Metadata.TYPE_ANNOUNCEMENT, true) + onSuccess() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaTemplate.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaTemplate.kt new file mode 100644 index 00000000..ddfb4457 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/data/synergia/LibrusSynergiaTemplate.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-10-23 + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.synergia + +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusSynergia + +class LibrusSynergiaTemplate(override val data: DataLibrus, + val onSuccess: () -> Unit +) : LibrusSynergia(data, null) { + companion object { + const val TAG = "LibrusSynergia" + } + + init { + /* synergiaGet(TAG, "") { text -> + val doc = Jsoup.parse(text) + + data.setSyncNext(ENDPOINT_LIBRUS_SYNERGIA_, SYNC_ALWAYS) + onSuccess() + } */ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt new file mode 100644 index 00000000..f693c41a --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/firstlogin/LibrusFirstLogin.kt @@ -0,0 +1,132 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.firstlogin + +import org.greenrobot.eventbus.EventBus +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusPortal +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginApi +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.login.LibrusLoginPortal +import pl.szczodrzynski.edziennik.data.api.events.FirstLoginFinishedEvent +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.Profile + +class LibrusFirstLogin(val data: DataLibrus, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "LibrusFirstLogin" + } + + private val portal = LibrusPortal(data) + private val api = LibrusApi(data, null) + private val profileList = mutableListOf() + + init { + val loginStoreId = data.loginStore.id + val loginStoreType = LOGIN_TYPE_LIBRUS + var firstProfileId = loginStoreId + + if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL) { + // email login: use Portal for account list + LibrusLoginPortal(data) { + portal.portalGet(TAG, if (data.fakeLogin) FAKE_LIBRUS_ACCOUNTS else LIBRUS_ACCOUNTS_URL) { json, response -> + val accounts = json.getJsonArray("accounts") + + if (accounts == null || accounts.size() < 1) { + EventBus.getDefault().post(FirstLoginFinishedEvent(listOf(), data.loginStore)) + onSuccess() + return@portalGet + } + val accountDataTime = json.getLong("lastModification") + + for (accountEl in accounts) { + val account = accountEl.asJsonObject + + val state = account.getString("state") + when (state) { + "requiring_an_action" -> ERROR_LIBRUS_PORTAL_SYNERGIA_DISCONNECTED + "need-activation" -> ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED + else -> null + }?.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json) + .withResponse(response)) + return@portalGet + } + + val isParent = account.getString("group") == "parent" + + val id = account.getInt("id") ?: continue + val login = account.getString("login") ?: continue + val token = account.getString("accessToken") ?: continue + val tokenTime = (accountDataTime ?: 0) + DAY + val studentNameLong = account.getString("studentName").fixName() + val studentNameShort = studentNameLong.getShortName() + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + data.portalEmail, + studentNameLong, + studentNameShort, + if (isParent) studentNameLong else null /* temporarily - there is no parent name provided, only the type */ + ).apply { + studentData["accountId"] = id + studentData["accountLogin"] = login + studentData["accountToken"] = token + studentData["accountTokenTime"] = tokenTime + } + profileList.add(profile) + } + + EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } + else { + // synergia or JST login: use Api for account info + LibrusLoginApi(data) { + api.apiGet(TAG, "Me") { json -> + + val me = json.getJsonObject("Me") + val account = me?.getJsonObject("Account") + val user = me?.getJsonObject("User") + + val login = account.getString("Login") + val isParent = account?.getInt("GroupId") in 5..6 + + val studentNameLong = buildFullName(user?.getString("FirstName"), user?.getString("LastName")) + val studentNameShort = studentNameLong.getShortName() + val accountNameLong = if (isParent) + buildFullName(account?.getString("FirstName"), account?.getString("LastName")) + else null + + val profile = Profile( + firstProfileId++, + loginStoreId, + loginStoreType, + studentNameLong, + login, + studentNameLong, + studentNameShort, + accountNameLong + ).apply { + studentData["isPremium"] = account?.getBoolean("IsPremium") == true || account?.getBoolean("IsPremiumDemo") == true + studentData["accountId"] = account.getInt("Id") ?: 0 + studentData["accountLogin"] = login + studentData["accountToken"] = data.apiAccessToken + studentData["accountTokenTime"] = data.apiTokenExpiryTime + studentData["accountRefreshToken"] = data.apiRefreshToken + } + profileList.add(profile) + + EventBus.getDefault().post(FirstLoginFinishedEvent(profileList, data.loginStore)) + onSuccess() + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt new file mode 100644 index 00000000..c79acd2e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLogin.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_API +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_MESSAGES +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_PORTAL +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_LIBRUS_SYNERGIA +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.utils.Utils + +class LibrusLogin(val data: DataLibrus, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "LibrusLogin" + } + + private var cancelled = false + + init { + nextLoginMethod(onSuccess) + } + + private fun nextLoginMethod(onSuccess: () -> Unit) { + if (data.targetLoginMethodIds.isEmpty()) { + onSuccess() + return + } + if (cancelled) { + onSuccess() + return + } + useLoginMethod(data.targetLoginMethodIds.removeAt(0)) { usedMethodId -> + data.progress(data.progressStep) + if (usedMethodId != -1) + data.loginMethods.add(usedMethodId) + nextLoginMethod(onSuccess) + } + } + + private fun useLoginMethod(loginMethodId: Int, onSuccess: (usedMethodId: Int) -> Unit) { + // this should never be true + if (data.loginMethods.contains(loginMethodId)) { + onSuccess(-1) + return + } + Utils.d(TAG, "Using login method $loginMethodId") + when (loginMethodId) { + LOGIN_METHOD_LIBRUS_PORTAL -> { + data.startProgress(R.string.edziennik_progress_login_librus_portal) + LibrusLoginPortal(data) { onSuccess(loginMethodId) } + } + LOGIN_METHOD_LIBRUS_API -> { + data.startProgress(R.string.edziennik_progress_login_librus_api) + LibrusLoginApi(data) { onSuccess(loginMethodId) } + } + LOGIN_METHOD_LIBRUS_SYNERGIA -> { + data.startProgress(R.string.edziennik_progress_login_librus_synergia) + LibrusLoginSynergia(data) { onSuccess(loginMethodId) } + } + LOGIN_METHOD_LIBRUS_MESSAGES -> { + data.startProgress(R.string.edziennik_progress_login_librus_messages) + LibrusLoginMessages(data) { onSuccess(loginMethodId) } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt new file mode 100644 index 00000000..fe155abe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginApi.kt @@ -0,0 +1,252 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.body.MediaTypeUtils +import im.wangchao.mhttp.callback.JsonCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getInt +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection.* + +class LibrusLoginApi { + companion object { + private const val TAG = "LoginLibrusApi" + } + + private lateinit var data: DataLibrus + private lateinit var onSuccess: () -> Unit + + /* do NOT move this to primary constructor */ + constructor(data: DataLibrus, onSuccess: () -> Unit) { + this.data = data + this.onSuccess = onSuccess + + if (data.loginStore.mode == LOGIN_MODE_LIBRUS_EMAIL && data.profile == null) { + data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) + return + } + + if (data.isApiLoginValid()) { + onSuccess() + } + else { + when (data.loginStore.mode) { + LOGIN_MODE_LIBRUS_EMAIL -> loginWithPortal() + LOGIN_MODE_LIBRUS_SYNERGIA -> loginWithSynergia() + LOGIN_MODE_LIBRUS_JST -> loginWithJst() + else -> { + data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE)) + } + } + } + } + + private fun loginWithPortal() { + if (!data.loginMethods.contains(LOGIN_METHOD_LIBRUS_PORTAL)) { + data.error(ApiError(TAG, ERROR_LOGIN_METHOD_NOT_SATISFIED)) + return + } + SynergiaTokenExtractor(data) { + onSuccess() + } + } + + private fun copyFromLoginStore() { + data.loginStore.data?.apply { + if (has("accountLogin")) { + data.apiLogin = getString("accountLogin") + remove("accountLogin") + } + if (has("accountPassword")) { + data.apiPassword = getString("accountPassword") + remove("accountPassword") + } + if (has("accountCode")) { + data.apiCode = getString("accountCode") + remove("accountCode") + } + if (has("accountPin")) { + data.apiPin = getString("accountPin") + remove("accountPin") + } + } + } + + private fun loginWithSynergia() { + copyFromLoginStore() + if (data.apiRefreshToken != null) { + // refresh a Synergia token + synergiaRefreshToken() + } + else if (data.apiLogin != null && data.apiPassword != null) { + synergiaGetToken() + } + else { + // cannot log in: token expired, no login data present + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + + private fun loginWithJst() { + copyFromLoginStore() + + if (data.apiRefreshToken != null) { + // refresh a JST token + jstRefreshToken() + } + else if (data.apiCode != null && data.apiPin != null) { + // get a JST token from Code and PIN + jstGetToken() + } + else { + // cannot log in: token expired, no login data present + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + + private val tokenCallback = object : JsonCallbackHandler() { + 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) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + if (response?.code() != 200) json.getString("error")?.let { error -> + when (error) { + "librus_captcha_needed" -> ERROR_LOGIN_LIBRUS_API_CAPTCHA_NEEDED + "connection_problems" -> ERROR_LOGIN_LIBRUS_API_CONNECTION_PROBLEMS + "invalid_client" -> ERROR_LOGIN_LIBRUS_API_INVALID_CLIENT + "librus_reg_accept_needed" -> ERROR_LOGIN_LIBRUS_API_REG_ACCEPT_NEEDED + "librus_change_password_error" -> ERROR_LOGIN_LIBRUS_API_CHANGE_PASSWORD_ERROR + "librus_password_change_required" -> ERROR_LOGIN_LIBRUS_API_PASSWORD_CHANGE_REQUIRED + "invalid_grant" -> ERROR_LOGIN_LIBRUS_API_INVALID_LOGIN + "invalid_request" -> ERROR_LOGIN_LIBRUS_API_INVALID_REQUEST + else -> ERROR_LOGIN_LIBRUS_API_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } + } + + try { + data.apiAccessToken = json.getString("access_token") + data.apiRefreshToken = json.getString("refresh_token") + data.apiTokenExpiryTime = response.getUnixDate() + json.getInt("expires_in", 86400) + onSuccess() + } catch (e: NullPointerException) { + data.error(ApiError(TAG, EXCEPTION_LOGIN_LIBRUS_API_TOKEN) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + private fun synergiaGetToken() { + d(TAG, "Request: Librus/Login/Api - $LIBRUS_API_TOKEN_URL") + + Request.builder() + .url(LIBRUS_API_TOKEN_URL) + .userAgent(LIBRUS_USER_AGENT) + .addParameter("grant_type", "password") + .addParameter("username", data.apiLogin) + .addParameter("password", data.apiPassword) + .addParameter("librus_long_term_token", "1") + .addParameter("librus_rules_accepted", "1") + .addHeader("Authorization", "Basic $LIBRUS_API_AUTHORIZATION") + .contentType(MediaTypeUtils.APPLICATION_FORM) + .post() + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_UNAUTHORIZED) + .allowErrorCode(HTTP_UNAVAILABLE) + .callback(tokenCallback) + .build() + .enqueue() + } + private fun synergiaRefreshToken() { + d(TAG, "Request: Librus/Login/Api - $LIBRUS_API_TOKEN_URL") + + Request.builder() + .url(LIBRUS_API_TOKEN_URL) + .userAgent(LIBRUS_USER_AGENT) + .addParameter("grant_type", "refresh_token") + .addParameter("refresh_token", data.apiRefreshToken) + .addParameter("librus_long_term_token", "1") + .addParameter("librus_rules_accepted", "1") + .addHeader("Authorization", "Basic $LIBRUS_API_AUTHORIZATION") + .contentType(MediaTypeUtils.APPLICATION_FORM) + .post() + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_UNAUTHORIZED) + .callback(tokenCallback) + .build() + .enqueue() + } + private fun jstGetToken() { + d(TAG, "Request: Librus/Login/Api - $LIBRUS_API_TOKEN_JST_URL") + + Request.builder() + .url(LIBRUS_API_TOKEN_JST_URL) + .userAgent(LIBRUS_USER_AGENT) + .addParameter("grant_type", "implicit_grant") + .addParameter("client_id", LIBRUS_API_CLIENT_ID_JST) + .addParameter("secret", LIBRUS_API_SECRET_JST) + .addParameter("code", data.apiCode) + .addParameter("pin", data.apiPin) + .addParameter("librus_rules_accepted", "1") + .addParameter("librus_mobile_rules_accepted", "1") + .addParameter("librus_long_term_token", "1") + .contentType(MediaTypeUtils.APPLICATION_FORM) + .post() + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_UNAUTHORIZED) + .callback(tokenCallback) + .build() + .enqueue() + } + private fun jstRefreshToken() { + d(TAG, "Request: Librus/Login/Api - $LIBRUS_API_TOKEN_JST_URL") + + Request.builder() + .url(LIBRUS_API_TOKEN_JST_URL) + .userAgent(LIBRUS_USER_AGENT) + .addParameter("grant_type", "refresh_token") + .addParameter("client_id", LIBRUS_API_CLIENT_ID_JST) + .addParameter("refresh_token", data.apiRefreshToken) + .addParameter("librus_long_term_token", "1") + .addParameter("mobile_app_accept_rules", "1") + .addParameter("synergy_accept_rules", "1") + .contentType(MediaTypeUtils.APPLICATION_FORM) + .post() + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_UNAUTHORIZED) + .callback(tokenCallback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt new file mode 100644 index 00000000..ba8bcf76 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginMessages.kt @@ -0,0 +1,156 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.body.MediaTypeUtils +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getUnixDate +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) { + companion object { + 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("ok") == true -> { + saveSessionId(response, text) + onSuccess() + } + text?.contains("Niepoprawny login i/lub hasło.") == 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("error") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ERROR, response, text) + text?.contains("eVarWhitThisNameNotExists") == true -> data.error(TAG, ERROR_LIBRUS_MESSAGES_ACCESS_DENIED, response, text) + text?.contains("") == 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 { + if (data.profile == null) { + data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) + return@run + } + + if (data.isMessagesLoginValid()) { + data.app.cookieJar.set("wiadomosci.librus.pl", "DZIENNIKSID", data.messagesSessionId) + onSuccess() + } + else { + data.app.cookieJar.clear("wiadomosci.librus.pl") + if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_SYNERGIA)) { + loginWithSynergia() + } + else if (data.apiLogin != null && data.apiPassword != null && false) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + /** + * XML (Flash messages website) login method. Uses a Synergia login and password. + */ + 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("password") + 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() + } + + /** + * A login method using the Synergia website (/wiadomosci2 Auto Login). + */ + private fun loginWithSynergia(url: String = "https://synergia.librus.pl/wiadomosci2") { + d(TAG, "Request: Librus/Login/Messages - $url") + + Request.builder() + .url(url) + .userAgent(SYNERGIA_USER_AGENT) + .get() + .callback(callback) + .withClient(data.app.httpLazy) + .build() + .enqueue() + } + + private fun saveSessionId(response: Response?, text: String?) { + var sessionId = data.app.cookieJar.get("wiadomosci.librus.pl", "DZIENNIKSID") + sessionId = sessionId?.replace("-MAINT", "") // dunno what's this + sessionId = sessionId?.replace("MAINT", "") // dunno what's this + if (sessionId == null) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_MESSAGES_NO_SESSION_ID) + .withResponse(response) + .withApiResponse(text)) + return + } + data.messagesSessionId = sessionId + data.messagesSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */ + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt new file mode 100644 index 00000000..166a4ae5 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginPortal.kt @@ -0,0 +1,259 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login + +import android.util.Pair +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.body.MediaTypeUtils +import im.wangchao.mhttp.callback.JsonCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection.* +import java.util.* +import java.util.regex.Pattern + +class LibrusLoginPortal(val data: DataLibrus, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "LoginLibrusPortal" + } + + init { run { + if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) { + data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE)) + return@run + } + if (data.portalEmail == null || data.portalPassword == null) { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + return@run + } + + // succeed having a non-expired access token and a refresh token + if (data.isPortalLoginValid()) { + onSuccess() + } + else if (data.portalRefreshToken != null) { + if (data.fakeLogin) { + data.app.cookieJar.clear("librus.szkolny.eu") + } + else { + data.app.cookieJar.clear("portal.librus.pl") + } + accessToken(null, data.portalRefreshToken) + } + else { + if (data.fakeLogin) { + data.app.cookieJar.clear("librus.szkolny.eu") + } + else { + data.app.cookieJar.clear("portal.librus.pl") + } + authorize(if (data.fakeLogin) FAKE_LIBRUS_AUTHORIZE else LIBRUS_AUTHORIZE_URL) + } + }} + + private fun authorize(url: String?) { + d(TAG, "Request: Librus/Login/Portal - $url") + + Request.builder() + .url(url) + .userAgent(LIBRUS_USER_AGENT) + .withClient(data.app.httpLazy) + .callback(object : TextCallbackHandler() { + override fun onSuccess(text: String, response: Response) { + val location = response.headers().get("Location") + if (location != null) { + val authMatcher = Pattern.compile("http://localhost/bar\\?code=([A-z0-9]+?)$", Pattern.DOTALL or Pattern.MULTILINE).matcher(location) + when { + authMatcher.find() -> { + accessToken(authMatcher.group(1), null) + } + location.contains("rejected_client") -> { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID) + .withResponse(response) + .withApiResponse("Location: $location\n$text")) + } + else -> { + authorize(location) + } + } + } else { + val csrfMatcher = Pattern.compile("name=\"csrf-token\" content=\"([A-z0-9=+/\\-_]+?)\"", Pattern.DOTALL).matcher(text) + if (csrfMatcher.find()) { + login(csrfMatcher.group(1)) + } else { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_CSRF_MISSING) + .withResponse(response) + .withApiResponse(text)) + } + } + } + + override fun onFailure(response: Response, throwable: Throwable) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + }) + .build() + .enqueue() + } + + private fun login(csrfToken: String) { + d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL}") + + val recaptchaCode = data.arguments?.getString("recaptchaCode") ?: data.loginStore.getLoginData("recaptchaCode", null) + val recaptchaTime = data.arguments?.getLong("recaptchaTime") ?: data.loginStore.getLoginData("recaptchaTime", 0L) + data.loginStore.removeLoginData("recaptchaCode") + data.loginStore.removeLoginData("recaptchaTime") + + Request.builder() + .url(if (data.fakeLogin) FAKE_LIBRUS_LOGIN else LIBRUS_LOGIN_URL) + .userAgent(LIBRUS_USER_AGENT) + .addParameter("email", data.portalEmail) + .addParameter("password", data.portalPassword) + .also { + if (recaptchaCode != null && System.currentTimeMillis() - recaptchaTime < 2*60*1000 /* 2 minutes */) + it.addParameter("g-recaptcha-response", recaptchaCode) + } + .addHeader("X-CSRF-TOKEN", csrfToken) + .allowErrorCode(HTTP_BAD_REQUEST) + .allowErrorCode(HTTP_FORBIDDEN) + .contentType(MediaTypeUtils.APPLICATION_JSON) + .post() + .callback(object : JsonCallbackHandler() { + 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 (response.parserErrorBody?.contains("wciąż nieaktywne") == true) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_NOT_ACTIVATED) + .withResponse(response)) + return + } + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + val error = if (response.code() == 200) null else + json.getJsonArray("errors")?.getString(0) + error?.let { code -> + when { + code.contains("Sesja logowania wygasła") -> ERROR_LOGIN_LIBRUS_PORTAL_CSRF_EXPIRED + code.contains("Upewnij się, że nie") -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_LOGIN + // this doesn't work anyway: `errors` is an object with `g-recaptcha-response` set + code.contains("robotem") -> ERROR_CAPTCHA_LIBRUS_PORTAL + else -> ERROR_LOGIN_LIBRUS_PORTAL_ACTION_ERROR + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } + } + if (json.getBoolean("captchaRequired") == true) { + data.error(ApiError(TAG, ERROR_CAPTCHA_LIBRUS_PORTAL) + .withResponse(response) + .withApiResponse(json)) + return + } + authorize(json.getString("redirect", LIBRUS_AUTHORIZE_URL)) + } + + override fun onFailure(response: Response, throwable: Throwable) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + }) + .build() + .enqueue() + } + + private fun accessToken(code: String?, refreshToken: String?) { + d(TAG, "Request: Librus/Login/Portal - ${if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL}") + + val onSuccess = { json: JsonObject, response: Response? -> + data.portalAccessToken = json.getString("access_token") + data.portalRefreshToken = json.getString("refresh_token") + data.portalTokenExpiryTime = response.getUnixDate() + json.getInt("expires_in", 86400) + onSuccess() + } + + val callback = object : JsonCallbackHandler() { + override fun onSuccess(json: JsonObject?, response: Response?) { + if (json == null) { + data.error(TAG, ERROR_RESPONSE_EMPTY, response) + return + } + val error = if (response?.code() == 200) null else + json.getString("hint") ?: json.getString("error") + error?.let { code -> + when (code) { + "Authorization code has expired" -> ERROR_LOGIN_LIBRUS_PORTAL_CODE_EXPIRED + "Authorization code has been revoked" -> ERROR_LOGIN_LIBRUS_PORTAL_CODE_REVOKED + "Cannot decrypt the refresh token" -> ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_INVALID + "Token has been revoked" -> ERROR_LOGIN_LIBRUS_PORTAL_REFRESH_REVOKED + "Check the `client_id` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_CLIENT_ID + "Check the `code` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_CODE + "Check the `refresh_token` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REFRESH + "Check the `redirect_uri` parameter" -> ERROR_LOGIN_LIBRUS_PORTAL_NO_REDIRECT + "unsupported_grant_type" -> ERROR_LOGIN_LIBRUS_PORTAL_UNSUPPORTED_GRANT + "invalid_client" -> ERROR_LOGIN_LIBRUS_PORTAL_INVALID_CLIENT_ID + else -> ERROR_LOGIN_LIBRUS_PORTAL_OTHER + }.let { errorCode -> + data.error(ApiError(TAG, errorCode) + .withApiResponse(json) + .withResponse(response)) + return + } + } + + try { + onSuccess(json, response) + } catch (e: NullPointerException) { + data.error(ApiError(TAG, EXCEPTION_LOGIN_LIBRUS_PORTAL_TOKEN) + .withResponse(response) + .withThrowable(e) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + val params = ArrayList>() + params.add(Pair("client_id", LIBRUS_CLIENT_ID)) + if (code != null) { + params.add(Pair("grant_type", "authorization_code")) + params.add(Pair("code", code)) + params.add(Pair("redirect_uri", LIBRUS_REDIRECT_URL)) + } else if (refreshToken != null) { + params.add(Pair("grant_type", "refresh_token")) + params.add(Pair("refresh_token", refreshToken)) + } + + Request.builder() + .url(if (data.fakeLogin) FAKE_LIBRUS_TOKEN else LIBRUS_TOKEN_URL) + .userAgent(LIBRUS_USER_AGENT) + .addParams(params) + .post() + .allowErrorCode(HTTP_UNAUTHORIZED) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt new file mode 100644 index 00000000..4a292cfe --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/LibrusLoginSynergia.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-9-20. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusApi +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.getString +import pl.szczodrzynski.edziennik.getUnixDate +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.net.HttpURLConnection + +class LibrusLoginSynergia(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusApi(data, null) { + companion object { + private const val TAG = "LoginLibrusSynergia" + } + + init { run { + if (data.profile == null) { + data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) + return@run + } + + if (data.isSynergiaLoginValid()) { + data.app.cookieJar.set("synergia.librus.pl", "DZIENNIKSID", data.synergiaSessionId) + onSuccess() + } + else { + data.app.cookieJar.clear("synergia.librus.pl") + if (data.loginMethods.contains(LOGIN_METHOD_LIBRUS_API)) { + loginWithApi() + } + else if (data.apiLogin != null && data.apiPassword != null && false) { + loginWithCredentials() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + /** + * HTML form-based login method. Uses a Synergia login and password. + */ + private fun loginWithCredentials() { + + } + + /** + * A login method using the Synergia API (AutoLoginToken endpoint). + */ + private fun loginWithApi() { + d(TAG, "Request: Librus/Login/Synergia - $LIBRUS_API_URL/AutoLoginToken") + + val onSuccess = { json: JsonObject -> + loginWithToken(json.getString("Token")) + } + + apiGet(TAG, "AutoLoginToken", POST, onSuccess = onSuccess) + } + + private fun loginWithToken(token: String?) { + if (token == null) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_TOKEN)) + return + } + + d(TAG, "Request: Librus/Login/Synergia - " + LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien") + + val callback = object : TextCallbackHandler() { + override fun onSuccess(json: String?, response: Response?) { + 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) { + val sessionId = data.app.cookieJar.get("synergia.librus.pl", "DZIENNIKSID") + if (sessionId == null) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_NO_SESSION_ID) + .withResponse(response) + .withApiResponse(json)) + return + } + data.synergiaSessionId = sessionId + data.synergiaSessionIdExpiryTime = response.getUnixDate() + 45 * 60 /* 45min */ + onSuccess() + } + else { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_SYNERGIA_TOKEN_INVALID) + .withResponse(response) + .withApiResponse(json)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.clear("synergia.librus.pl") + Request.builder() + .url(LIBRUS_SYNERGIA_TOKEN_LOGIN_URL.replace("TOKEN", token) + "/uczen/widok/centrum_powiadomien") + .userAgent(LIBRUS_USER_AGENT) + .get() + .allowErrorCode(HttpURLConnection.HTTP_BAD_REQUEST) + .allowErrorCode(HttpURLConnection.HTTP_FORBIDDEN) + .allowErrorCode(HttpURLConnection.HTTP_UNAUTHORIZED) + .callback(callback) + .withClient(data.app.httpLazy) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt new file mode 100644 index 00000000..5072e5a2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/librus/login/SynergiaTokenExtractor.kt @@ -0,0 +1,68 @@ +package pl.szczodrzynski.edziennik.data.api.edziennik.librus.login + +import com.google.gson.JsonObject +import im.wangchao.mhttp.Response +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.DataLibrus +import pl.szczodrzynski.edziennik.data.api.edziennik.librus.data.LibrusPortal +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d + +class SynergiaTokenExtractor(override val data: DataLibrus, val onSuccess: () -> Unit) : LibrusPortal(data) { + companion object { + private const val TAG = "SynergiaTokenExtractor" + } + + init { run { + if (data.loginStore.mode != LOGIN_MODE_LIBRUS_EMAIL) { + data.error(ApiError(TAG, ERROR_INVALID_LOGIN_MODE)) + return@run + } + if (data.profile == null) { + data.error(ApiError(TAG, ERROR_PROFILE_MISSING)) + return@run + } + + if (data.apiTokenExpiryTime-30 > currentTimeUnix() && data.apiAccessToken.isNotNullNorEmpty()) { + onSuccess() + } + else { + if (!synergiaAccount()) { + data.error(ApiError(TAG, ERROR_LOGIN_DATA_MISSING)) + } + } + }} + + /** + * Get an Api token from the Portal account, using Portal API. + * If necessary, refreshes the token. + */ + private fun synergiaAccount(): Boolean { + + val accountLogin = data.apiLogin ?: return false + data.portalAccessToken ?: return false + + d(TAG, "Request: Librus/SynergiaTokenExtractor - ${if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL}$accountLogin") + + val onSuccess = { json: JsonObject, response: Response? -> + // synergiaAccount is executed when a synergia token needs a refresh + val accountId = json.getInt("id") + val accountToken = json.getString("accessToken") + if (accountId == null || accountToken == null) { + data.error(ApiError(TAG, ERROR_LOGIN_LIBRUS_PORTAL_SYNERGIA_TOKEN_MISSING) + .withResponse(response) + .withApiResponse(json)) + } + else { + data.apiAccessToken = accountToken + data.apiTokenExpiryTime = response.getUnixDate() + 6 * 60 * 60 + + onSuccess() + } + } + + portalGet(TAG, (if (data.fakeLogin) FAKE_LIBRUS_ACCOUNT else LIBRUS_ACCOUNT_URL)+accountLogin, onSuccess = onSuccess) + return true + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt new file mode 100644 index 00000000..ddfb0113 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/DataMobidziennik.kt @@ -0,0 +1,157 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-6. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik + +import android.util.LongSparseArray +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.currentTimeUnix +import pl.szczodrzynski.edziennik.data.api.LOGIN_METHOD_MOBIDZIENNIK_WEB +import pl.szczodrzynski.edziennik.data.api.models.Data +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.isNotNullNorEmpty +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class DataMobidziennik(app: App, profile: Profile?, loginStore: LoginStore) : Data(app, profile, loginStore) { + + fun isWebLoginValid() = webSessionIdExpiryTime-30 > currentTimeUnix() + && webSessionValue.isNotNullNorEmpty() + && webSessionKey.isNotNullNorEmpty() + && webServerId.isNotNullNorEmpty() + + fun isApi2LoginValid() = loginEmail.isNotNullNorEmpty() + && loginId.isNotNullNorEmpty() + && globalId.isNotNullNorEmpty() + + override fun satisfyLoginMethods() { + loginMethods.clear() + if (isWebLoginValid()) { + loginMethods += LOGIN_METHOD_MOBIDZIENNIK_WEB + } + } + + override fun generateUserCode() = "$loginServerName:$loginUsername:$studentId" + + val teachersMap = LongSparseArray() + val subjectsMap = LongSparseArray() + + val gradeAddedDates = sortedMapOf() + val gradeAverages = sortedMapOf() + val gradeColors = sortedMapOf() + + private var mLoginServerName: String? = null + var loginServerName: String? + get() { mLoginServerName = mLoginServerName ?: loginStore.getLoginData("serverName", null); return mLoginServerName } + set(value) { loginStore.putLoginData("serverName", value); mLoginServerName = value } + + private var mLoginUsername: String? = null + var loginUsername: String? + get() { mLoginUsername = mLoginUsername ?: loginStore.getLoginData("username", null); return mLoginUsername } + set(value) { loginStore.putLoginData("username", value); mLoginUsername = value } + + private var mLoginPassword: String? = null + var loginPassword: String? + get() { mLoginPassword = mLoginPassword ?: loginStore.getLoginData("password", null); return mLoginPassword } + set(value) { loginStore.putLoginData("password", value); mLoginPassword = value } + + private var mStudentId: Int? = null + var studentId: Int + get() { mStudentId = mStudentId ?: profile?.getStudentData("studentId", 0); return mStudentId ?: 0 } + set(value) { profile?.putStudentData("studentId", value) ?: return; mStudentId = value } + + /* __ __ _ + \ \ / / | | + \ \ /\ / /__| |__ + \ \/ \/ / _ \ '_ \ + \ /\ / __/ |_) | + \/ \/ \___|_._*/ + private var mWebSessionKey: String? = null + var webSessionKey: String? + get() { mWebSessionKey = mWebSessionKey ?: loginStore.getLoginData("sessionCookie", null); return mWebSessionKey } + set(value) { loginStore.putLoginData("sessionCookie", value); mWebSessionKey = value } + + private var mWebSessionValue: String? = null + var webSessionValue: String? + get() { mWebSessionValue = mWebSessionValue ?: loginStore.getLoginData("sessionID", null); return mWebSessionValue } + set(value) { loginStore.putLoginData("sessionID", value); mWebSessionValue = value } + + private var mWebServerId: String? = null + var webServerId: String? + get() { mWebServerId = mWebServerId ?: loginStore.getLoginData("sessionServer", null); return mWebServerId } + set(value) { loginStore.putLoginData("sessionServer", value); mWebServerId = value } + + private var mWebSessionIdExpiryTime: Long? = null + var webSessionIdExpiryTime: Long + get() { mWebSessionIdExpiryTime = mWebSessionIdExpiryTime ?: loginStore.getLoginData("sessionIDTime", 0L); return mWebSessionIdExpiryTime ?: 0L } + set(value) { loginStore.putLoginData("sessionIDTime", value); mWebSessionIdExpiryTime = value } + + /* _____ _____ ___ + /\ | __ \_ _| |__ \ + / \ | |__) || | ) | + / /\ \ | ___/ | | / / + / ____ \| | _| |_ / /_ + /_/ \_\_| |_____| |___*/ + /** + * A global ID (whatever it is) used in API 2 + * and Firebase push from Mobidziennik. + */ + var globalId: String? + get() { mGlobalId = mGlobalId ?: profile?.getStudentData("globalId", null); return mGlobalId } + set(value) { profile?.putStudentData("globalId", value) ?: return; mGlobalId = value } + private var mGlobalId: String? = null + + /** + * User's email that may or may not + * be retrieved from Web by [MobidziennikWebAccountEmail]. + * Used to log in to API 2. + */ + var loginEmail: String? + get() { mLoginEmail = mLoginEmail ?: profile?.getStudentData("email", null); return mLoginEmail } + set(value) { profile?.putStudentData("email", value); mLoginEmail = value } + private var mLoginEmail: String? = null + + /** + * A login ID used in the API 2. + * Looks more or less like "7063@2019@zslpoznan". + */ + var loginId: String? + get() { mLoginId = mLoginId ?: profile?.getStudentData("loginId", null); return mLoginId } + set(value) { profile?.putStudentData("loginId", value) ?: return; mLoginId = value } + private var mLoginId: String? = null + + /** + * No need to explain. + */ + var ciasteczkoAutoryzacji: String? + get() { mCiasteczkoAutoryzacji = mCiasteczkoAutoryzacji ?: profile?.getStudentData("ciasteczkoAutoryzacji", null); return mCiasteczkoAutoryzacji } + set(value) { profile?.putStudentData("ciasteczkoAutoryzacji", value) ?: return; mCiasteczkoAutoryzacji = value } + private var mCiasteczkoAutoryzacji: String? = null + + + override fun saveData() { + super.saveData() + if (gradeAddedDates.isNotEmpty()) { + app.db.gradeDao().updateDetails(profileId, gradeAverages, gradeAddedDates, gradeColors) + } + } + + val mobiLessons = mutableListOf() + + data class MobiLesson( + var id: Long, + var subjectId: Long, + var teacherId: Long, + var teamId: Long, + var topic: String, + var date: Date, + var startTime: Time, + var endTime: Time, + var presentCount: Int, + var absentCount: Int, + var lessonNumber: Int, + var signed: String + ) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt new file mode 100644 index 00000000..5e08f6c4 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/Mobidziennik.kt @@ -0,0 +1,157 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik + +import com.google.gson.JsonObject +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikData +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebGetAttachment +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebGetMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebGetRecipientList +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.MobidziennikWebSendMessage +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.firstlogin.MobidziennikFirstLogin +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.login.MobidziennikLogin +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikCallback +import pl.szczodrzynski.edziennik.data.api.interfaces.EdziennikInterface +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.LoginStore +import pl.szczodrzynski.edziennik.data.db.entity.Message +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.Teacher +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull +import pl.szczodrzynski.edziennik.data.db.full.MessageFull +import pl.szczodrzynski.edziennik.utils.Utils.d + +class Mobidziennik(val app: App, val profile: Profile?, val loginStore: LoginStore, val callback: EdziennikCallback) : EdziennikInterface { + companion object { + private const val TAG = "Mobidziennik" + + const val API_KEY = "szkolny_eu_72c7dbc8b97f1e5dd2d118cacf51c2b8543d15c0f65b7a59979adb0a1296b235d7febb826dd2a28688def6efe0811b924b04d7f3c7b7d005354e06dc56815d57" + } + + val internalErrorList = mutableListOf() + val data: DataMobidziennik + private var afterLogin: (() -> Unit)? = null + + init { + data = DataMobidziennik(app, profile, loginStore).apply { + callback = wrapCallback(this@Mobidziennik.callback) + satisfyLoginMethods() + } + } + + private fun completed() { + data.saveData() + callback.onCompleted() + } + + /* _______ _ _ _ _ _ + |__ __| | /\ | | (_) | | | + | | | |__ ___ / \ | | __ _ ___ _ __ _| |_| |__ _ __ ___ + | | | '_ \ / _ \ / /\ \ | |/ _` |/ _ \| '__| | __| '_ \| '_ ` _ \ + | | | | | | __/ / ____ \| | (_| | (_) | | | | |_| | | | | | | | | + |_| |_| |_|\___| /_/ \_\_|\__, |\___/|_| |_|\__|_| |_|_| |_| |_| + __/ | + |__*/ + override fun sync(featureIds: List, viewId: Int?, onlyEndpoints: List?, arguments: JsonObject?) { + data.arguments = arguments + data.prepare(mobidziennikLoginMethods, MobidziennikFeatures, featureIds, viewId, onlyEndpoints) + login() + } + + private fun login(loginMethodId: Int? = null, afterLogin: (() -> Unit)? = null) { + d(TAG, "Trying to login with ${data.targetLoginMethodIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + loginMethodId?.let { data.prepareFor(mobidziennikLoginMethods, it) } + afterLogin?.let { this.afterLogin = it } + MobidziennikLogin(data) { + data() + } + } + + private fun data() { + d(TAG, "Endpoint IDs: ${data.targetEndpointIds}") + if (internalErrorList.isNotEmpty()) { + d(TAG, " - Internal errors:") + internalErrorList.forEach { d(TAG, " - code $it") } + } + afterLogin?.invoke() ?: MobidziennikData(data) { + completed() + } + } + + override fun getMessage(message: MessageFull) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetMessage(data, message) { + completed() + } + } + } + + override fun sendMessage(recipients: List, subject: String, text: String) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebSendMessage(data, recipients, subject, text) { + completed() + } + } + } + + override fun markAllAnnouncementsAsRead() {} + override fun getAnnouncement(announcement: AnnouncementFull) {} + + override fun getAttachment(message: Message, attachmentId: Long, attachmentName: String) { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetAttachment(data, message, attachmentId, attachmentName) { + completed() + } + } + } + + override fun getRecipientList() { + login(LOGIN_METHOD_MOBIDZIENNIK_WEB) { + MobidziennikWebGetRecipientList(data) { + completed() + } + } + } + + override fun firstLogin() { MobidziennikFirstLogin(data) { completed() } } + override fun cancel() { + d(TAG, "Cancelled") + data.cancel() + } + + private fun wrapCallback(callback: EdziennikCallback): EdziennikCallback { + return object : EdziennikCallback { + override fun onCompleted() { callback.onCompleted() } + override fun onProgress(step: Float) { callback.onProgress(step) } + override fun onStartProgress(stringRes: Int) { callback.onStartProgress(stringRes) } + override fun onError(apiError: ApiError) { + if (apiError.errorCode in internalErrorList) { + // finish immediately if the same error occurs twice during the same sync + callback.onError(apiError) + return + } + internalErrorList.add(apiError.errorCode) + when (apiError.errorCode) { + ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED, + ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY, + ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE, + ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID -> { + data.loginMethods.remove(LOGIN_METHOD_MOBIDZIENNIK_WEB) + data.prepareFor(mobidziennikLoginMethods, LOGIN_METHOD_MOBIDZIENNIK_WEB) + data.webSessionIdExpiryTime = 0 + login() + } + else -> callback.onError(apiError) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt new file mode 100644 index 00000000..d056fc4b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/MobidziennikFeatures.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik + +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.models.Feature + +const val ENDPOINT_MOBIDZIENNIK_API_MAIN = 1000 +const val ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX = 2011 +const val ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT = 2012 +const val ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL = 2019 +const val ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR = 2020 +const val ENDPOINT_MOBIDZIENNIK_WEB_GRADES = 2030 +const val ENDPOINT_MOBIDZIENNIK_WEB_NOTICES = 2040 +const val ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE = 2050 +const val ENDPOINT_MOBIDZIENNIK_WEB_MANUALS = 2100 +const val ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL = 2200 +const val ENDPOINT_MOBIDZIENNIK_API2_MAIN = 3000 + +val MobidziennikFeatures = listOf( + // always synced + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ALWAYS_NEEDED, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), // TODO divide features into separate view IDs (all with API_MAIN) + + // push config + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_PUSH_CONFIG, listOf( + ENDPOINT_MOBIDZIENNIK_API2_MAIN to LOGIN_METHOD_MOBIDZIENNIK_API2 + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_API2)).withShouldSync { data -> + !data.app.config.sync.tokenMobidziennikList.contains(data.profileId) + }, + + + + + + /** + * Agenda - "API" + web scraping. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_AGENDA, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + /** + * Grades - "API" + web scraping. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_GRADES, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_GRADES to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + /** + * Behaviour - "API" + web scraping. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_BEHAVIOUR, listOf( + ENDPOINT_MOBIDZIENNIK_API_MAIN to LOGIN_METHOD_MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB, LOGIN_METHOD_MOBIDZIENNIK_WEB)), + /** + * Attendance - only web scraping. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_ATTENDANCE, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), + + + + + + /** + * Messages inbox - using web scraper. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_MESSAGES_INBOX, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX to LOGIN_METHOD_MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)), + /** + * Messages sent - using web scraper. + */ + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_MESSAGES_SENT, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT to LOGIN_METHOD_MOBIDZIENNIK_WEB, + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)) + + // lucky number possibilities + // all endpoints that may supply the lucky number + /*Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_MANUALS to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 10 }, + + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 3 }, + + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_GRADES to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 2 }, + + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_NOTICES to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 1 }, + + Feature(LOGIN_TYPE_MOBIDZIENNIK, FEATURE_LUCKY_NUMBER, listOf( + ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE to LOGIN_METHOD_MOBIDZIENNIK_WEB + ), listOf(LOGIN_METHOD_MOBIDZIENNIK_WEB)).apply { priority = 4 }*/ + +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt new file mode 100644 index 00000000..09e1e9fb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikData.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data + +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api.MobidziennikApi +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api2.MobidziennikApi2Main +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.web.* +import pl.szczodrzynski.edziennik.utils.Utils + +class MobidziennikData(val data: DataMobidziennik, val onSuccess: () -> Unit) { + companion object { + private const val TAG = "MobidziennikData" + } + + init { + nextEndpoint(onSuccess) + } + + private fun nextEndpoint(onSuccess: () -> Unit) { + if (data.targetEndpointIds.isEmpty()) { + onSuccess() + return + } + if (data.cancelled) { + onSuccess() + return + } + val id = data.targetEndpointIds.firstKey() + val lastSync = data.targetEndpointIds.remove(id) + useEndpoint(id, lastSync) { endpointId -> + data.progress(data.progressStep) + nextEndpoint(onSuccess) + } + } + + private fun useEndpoint(endpointId: Int, lastSync: Long?, onSuccess: (endpointId: Int) -> Unit) { + Utils.d(TAG, "Using endpoint $endpointId. Last sync time = $lastSync") + when (endpointId) { + ENDPOINT_MOBIDZIENNIK_API_MAIN -> { + data.startProgress(R.string.edziennik_progress_endpoint_data) + MobidziennikApi(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_API2_MAIN -> { + data.startProgress(R.string.edziennik_progress_endpoint_push_config) + MobidziennikApi2Main(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_INBOX -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_inbox) + MobidziennikWebMessagesInbox(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_SENT -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages_outbox) + MobidziennikWebMessagesSent(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_MESSAGES_ALL -> { + data.startProgress(R.string.edziennik_progress_endpoint_messages) + MobidziennikWebMessagesAll(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_CALENDAR -> { + data.startProgress(R.string.edziennik_progress_endpoint_calendar) + MobidziennikWebCalendar(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_GRADES -> { + data.startProgress(R.string.edziennik_progress_endpoint_grades) + MobidziennikWebGrades(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_ACCOUNT_EMAIL -> { + data.startProgress(R.string.edziennik_progress_endpoint_account_details) + MobidziennikWebAccountEmail(data, lastSync, onSuccess) + } + ENDPOINT_MOBIDZIENNIK_WEB_ATTENDANCE -> { + data.startProgress(R.string.edziennik_progress_endpoint_attendance) + MobidziennikWebAttendance(data, lastSync, onSuccess) + }/* + ENDPOINT_MOBIDZIENNIK_WEB_NOTICES -> { + data.startProgress(R.string.edziennik_progress_endpoint_behaviour) + MobidziennikWebNotices(data, lastSync, onSuccess) + }] + ENDPOINT_MOBIDZIENNIK_WEB_MANUALS -> { + data.startProgress(R.string.edziennik_progress_endpoint_lucky_number) + MobidziennikWebManuals(data, lastSync, onSuccess) + }*/ + else -> onSuccess(endpointId) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikWeb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikWeb.kt new file mode 100644 index 00000000..cfb4ab49 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/MobidziennikWeb.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-5. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data + +import im.wangchao.mhttp.Request +import im.wangchao.mhttp.Response +import im.wangchao.mhttp.callback.FileCallbackHandler +import im.wangchao.mhttp.callback.TextCallbackHandler +import pl.szczodrzynski.edziennik.data.api.* +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.utils.Utils.d +import java.io.File + +open class MobidziennikWeb(open val data: DataMobidziennik, open val lastSync: Long?) { + companion object { + private const val TAG = "MobidziennikWeb" + } + + val profileId + get() = data.profile?.id ?: -1 + + val profile + get() = data.profile + + /* TODO + add error handling: + + +
    + +

    Ważna informacja!

    + +
    + Korzystasz z hasła, które zostało wygenerowane automatycznie.
    + Pamiętaj, aby je zmienić oraz uzupełnić dane w swoim profilu.

    + Obie te operacje można wykonać uruchamiając opcję + Moje konto->Edycja profilu. +
    + + */ + + fun webGet( + tag: String, + endpoint: String, + method: Int = GET, + parameters: List> = emptyList(), + fullUrl: String? = null, + onSuccess: (text: String) -> Unit + ) { + val url = fullUrl ?: "https://${data.loginServerName}.mobidziennik.pl$endpoint" + + d(tag, "Request: Mobidziennik/Web - $url") + + if (data.webSessionKey == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY) + return + } + if (data.webSessionValue == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE) + return + } + if (data.webServerId == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID) + return + } + + val callback = object : TextCallbackHandler() { + override fun onSuccess(text: String?, response: Response?) { + if (text.isNullOrEmpty()) { + data.error(ApiError(TAG, ERROR_RESPONSE_EMPTY) + .withResponse(response)) + return + } + if (text == "Nie jestes zalogowany" + || text.contains("przypomnij_haslo_email")) { + data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_ACCESS_DENIED) + .withResponse(response)) + return + } + + if (text.contains("

    Problemy z wydajnością

    ")) { + data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_SERVER_PROBLEM) + .withResponse(response)) + return + } + + try { + onSuccess(text) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_REQUEST) + .withResponse(response) + .withThrowable(e) + .withApiResponse(text)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue) + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId) + + Request.builder() + .url(url) + .userAgent(MOBIDZIENNIK_USER_AGENT) + .apply { + when (method) { + GET -> get() + POST -> post() + } + parameters.map { (name, value) -> + addParameter(name, value) + } + } + .callback(callback) + .build() + .enqueue() + } + + fun webGetFile(tag: String, action: String, targetFile: File, onSuccess: (file: File) -> Unit, + onProgress: (written: Long, total: Long) -> Unit) { + val url = "https://${data.loginServerName}.mobidziennik.pl$action" + + d(tag, "Request: Mobidziennik/Web - $url") + + if (data.webSessionKey == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_KEY) + return + } + if (data.webSessionValue == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SESSION_VALUE) + return + } + if (data.webServerId == null) { + data.error(TAG, ERROR_MOBIDZIENNIK_WEB_NO_SERVER_ID) + return + } + + val callback = object : FileCallbackHandler(targetFile) { + override fun onSuccess(file: File?, response: Response?) { + if (file == null) { + data.error(ApiError(TAG, ERROR_FILE_DOWNLOAD) + .withResponse(response)) + return + } + + try { + onSuccess(file) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST) + .withResponse(response) + .withThrowable(e)) + } + } + + override fun onProgress(bytesWritten: Long, bytesTotal: Long) { + try { + onProgress(bytesWritten, bytesTotal) + } catch (e: Exception) { + data.error(ApiError(tag, EXCEPTION_MOBIDZIENNIK_WEB_FILE_REQUEST) + .withThrowable(e)) + } + } + + override fun onFailure(response: Response?, throwable: Throwable?) { + data.error(ApiError(TAG, ERROR_REQUEST_FAILURE) + .withResponse(response) + .withThrowable(throwable)) + } + } + + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", data.webSessionKey, data.webSessionValue) + data.app.cookieJar.set("${data.loginServerName}.mobidziennik.pl", "SERVERID", data.webServerId) + + Request.builder() + .url(url) + .userAgent(MOBIDZIENNIK_USER_AGENT) + .callback(callback) + .build() + .enqueue() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApi.kt new file mode 100644 index 00000000..323aeb14 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApi.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.ENDPOINT_MOBIDZIENNIK_API_MAIN +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.MobidziennikWeb +import pl.szczodrzynski.edziennik.data.api.models.ApiError +import pl.szczodrzynski.edziennik.data.db.entity.SYNC_ALWAYS + +class MobidziennikApi(override val data: DataMobidziennik, + override val lastSync: Long?, + val onSuccess: (endpointId: Int) -> Unit +) : MobidziennikWeb(data, lastSync) { + companion object { + private const val TAG = "MobidziennikApi" + } + + init { + webGet(TAG, "/api/zrzutbazy") { text -> + if (!text.contains("T@B#LA")) { + data.error(ApiError(TAG, ERROR_MOBIDZIENNIK_WEB_INVALID_RESPONSE) + .withApiResponse(text)) + return@webGet + } + + val tables = text.split("T@B#LA") + tables.forEachIndexed { index, table -> + val rows = table.split("\n") + when (index) { + 0 -> MobidziennikApiUsers(data, rows) + 3 -> MobidziennikApiDates(data, rows) + 4 -> MobidziennikApiSubjects(data, rows) + 7 -> MobidziennikApiTeams(data, rows, null) + 8 -> MobidziennikApiStudent(data, rows) + 9 -> MobidziennikApiTeams(data, null, rows) + 14 -> MobidziennikApiGradeCategories(data, rows) + 15 -> MobidziennikApiLessons(data, rows) + //16 -> MobidziennikApiAttendance(data, rows) // disabled since the new web scrapper is used + 17 -> MobidziennikApiNotices(data, rows) + 18 -> MobidziennikApiGrades(data, rows) + 21 -> MobidziennikApiEvents(data, rows) + 23 -> MobidziennikApiHomework(data, rows) + 24 -> MobidziennikApiTimetable(data, rows) + } + } + + data.setSyncNext(ENDPOINT_MOBIDZIENNIK_API_MAIN, SYNC_ALWAYS) + onSuccess(ENDPOINT_MOBIDZIENNIK_API_MAIN) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt new file mode 100644 index 00000000..be68de4d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiAttendance.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Attendance +import pl.szczodrzynski.edziennik.data.db.entity.Attendance.* +import pl.szczodrzynski.edziennik.data.db.entity.Metadata + +class MobidziennikApiAttendance(val data: DataMobidziennik, rows: List) { + init { run { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val studentId = cols[2].toInt() + if (studentId != data.studentId) + return@run + + val id = cols[0].toLong() + val lessonId = cols[1].toLong() + data.mobiLessons.singleOrNull { it.id == lessonId }?.let { lesson -> + val type = when (cols[4]) { + "2" -> TYPE_ABSENT + "5" -> TYPE_ABSENT_EXCUSED + "4" -> TYPE_RELEASED + else -> TYPE_PRESENT + } + val semester = data.profile?.dateToSemester(lesson.date) ?: 1 + + val attendanceObject = Attendance( + data.profileId, + id, + lesson.teacherId, + lesson.subjectId, + semester, + lesson.topic, + lesson.date, + lesson.startTime, + type) + + data.attendanceList.add(attendanceObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_ATTENDANCE, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false, + System.currentTimeMillis() + )) + } + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiDates.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiDates.kt new file mode 100644 index 00000000..a0b3dbc2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiDates.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.utils.models.Date + +class MobidziennikApiDates(val data: DataMobidziennik, rows: List) { + init { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + when (cols[1]) { + "semestr1_poczatek" -> data.profile?.dateSemester1Start = Date.fromYmd(cols[3]) + "semestr2_poczatek" -> data.profile?.dateSemester2Start = Date.fromYmd(cols[3]) + "koniec_roku_szkolnego" -> data.profile?.dateYearEnd = Date.fromYmd(cols[3]) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt new file mode 100644 index 00000000..bfc6eef3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiEvents.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import androidx.core.util.contains +import pl.szczodrzynski.edziennik.data.api.Regexes +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class MobidziennikApiEvents(val data: DataMobidziennik, rows: List) { + init { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val teamId = cols[2].toLong() + if (data.teamList.contains(teamId)) { + + val id = cols[0].toLong() + val teacherId = cols[1].toLong() + val subjectId = cols[3].toLong() + var type = Event.TYPE_DEFAULT + var topic = cols[5] + Regexes.MOBIDZIENNIK_EVENT_TYPE.find(topic)?.let { + val typeText = it.groupValues[1] + when (typeText) { + "sprawdzian" -> type = Event.TYPE_EXAM + "kartkówka" -> type = Event.TYPE_SHORT_QUIZ + } + topic = topic.replace("($typeText)", "").trim() + } + val eventDate = Date.fromYmd(cols[4]) + val startTime = Time.fromYmdHm(cols[6]) + val format = SimpleDateFormat("yyyyMMddHHmmss", Locale.US) + val addedDate = try { + format.parse(cols[7]).time + } catch (e: ParseException) { + e.printStackTrace() + System.currentTimeMillis() + } + + + val eventObject = Event( + profileId = data.profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = type, + teacherId = teacherId, + subjectId = subjectId, + teamId = teamId) + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_EVENT, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false, + addedDate + )) + } + } + + data.toRemove.add(DataRemoveModel.Events.futureExceptType(Event.TYPE_HOMEWORK)) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGradeCategories.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGradeCategories.kt new file mode 100644 index 00000000..b3bb8177 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGradeCategories.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-7. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import android.graphics.Color +import androidx.core.util.contains +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.GradeCategory + +class MobidziennikApiGradeCategories(val data: DataMobidziennik, rows: List) { + init { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val teamId = cols[1].toLong() + if (data.teamList.contains(teamId)) { + + val id = cols[0].toLong() + val weight = cols[3].toFloat() + val color = Color.parseColor("#" + cols[6]) + val category = cols[4] + val columns = cols[7].split(";") + + data.gradeCategories.put( + id, + GradeCategory( + data.profileId, + id, + weight, + color, + category + ).addColumns(columns) + ) + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt new file mode 100644 index 00000000..609c895c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiGrades.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-8. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Grade +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_NORMAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER1_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_SEMESTER2_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_FINAL +import pl.szczodrzynski.edziennik.data.db.entity.Grade.Companion.TYPE_YEAR_PROPOSED +import pl.szczodrzynski.edziennik.data.db.entity.Metadata + +class MobidziennikApiGrades(val data: DataMobidziennik, rows: List) { + init { data.profile?.also { profile -> run { + data.db.gradeDao().getDetails( + data.profileId, + data.gradeAddedDates, + data.gradeAverages, + data.gradeColors + ) + var addedDate = System.currentTimeMillis() + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val studentId = cols[1].toInt() + if (studentId != data.studentId) + return@run + + val id = cols[0].toLong() + val categoryId = cols[6].toLongOrNull() ?: -1 + val categoryColumn = cols[10].toIntOrNull() ?: 1 + val name = cols[7] + val value = cols[11].toFloat() + val semester = cols[5].toInt() + val teacherId = cols[2].toLong() + val subjectId = cols[3].toLong() + val type = when (cols[8]) { + "3" -> if (semester == 1) TYPE_SEMESTER1_PROPOSED else TYPE_SEMESTER2_PROPOSED + "1" -> if (semester == 1) TYPE_SEMESTER1_FINAL else TYPE_SEMESTER2_FINAL + "4" -> TYPE_YEAR_PROPOSED + "2" -> TYPE_YEAR_FINAL + else -> TYPE_NORMAL + } + + var weight = 0.0f + var category = "" + var description = "" + var color = -1 + data.gradeCategories.get(categoryId)?.let { gradeCategory -> + weight = gradeCategory.weight + category = gradeCategory.text + description = gradeCategory.columns[categoryColumn-1] + color = gradeCategory.color + } + + // fix for "0" value grades, so they're not counted in the average + if (value == 0.0f/* && data.app.appConfig.dontCountZeroToAverage*/) { + weight = 0.0f + } + + val gradeObject = Grade( + profileId = data.profileId, + id = id, + name = name, + type = type, + value = value, + weight = weight, + color = color, + category = category, + description = description, + comment = null, + semester = semester, + teacherId = teacherId, + subjectId = subjectId) + + if (data.profile?.empty == true) { + addedDate = data.profile.dateSemester1Start.inMillis + } + + data.gradeList.add(gradeObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_GRADE, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false, + addedDate + )) + addedDate++ + } + data.toRemove.addAll(listOf( + TYPE_NORMAL, + TYPE_SEMESTER1_FINAL, + TYPE_SEMESTER2_FINAL, + TYPE_SEMESTER1_PROPOSED, + TYPE_SEMESTER2_PROPOSED, + TYPE_YEAR_FINAL, + TYPE_YEAR_PROPOSED + ).map { + DataRemoveModel.Grades.semesterWithType(profile.currentSemester, it) + }) + }}} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt new file mode 100644 index 00000000..fe75674f --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiHomework.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import android.text.Html +import androidx.core.util.contains +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class MobidziennikApiHomework(val data: DataMobidziennik, rows: List) { + init { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val teamId = cols[5].toLong() + if (data.teamList.contains(teamId)) { + + val id = cols[0].toLong() + val teacherId = cols[7].toLong() + val subjectId = cols[6].toLong() + val topic = Html.fromHtml(cols[1])?.toString() ?: "" + val eventDate = Date.fromYmd(cols[2]) + val startTime = Time.fromYmdHm(cols[3]) + + val eventObject = Event( + profileId = data.profileId, + id = id, + date = eventDate, + time = startTime, + topic = topic, + color = null, + type = Event.TYPE_HOMEWORK, + teacherId = teacherId, + subjectId = subjectId, + teamId = teamId) + + data.eventList.add(eventObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_HOMEWORK, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false, + System.currentTimeMillis() + )) + } + } + + data.toRemove.add(DataRemoveModel.Events.futureWithType(Event.TYPE_HOMEWORK)) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiLessons.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiLessons.kt new file mode 100644 index 00000000..47ab7494 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiLessons.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-7. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class MobidziennikApiLessons(val data: DataMobidziennik, rows: List) { + init { + data.mobiLessons.clear() + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val id = cols[0].toLong() + val subjectId = cols[1].toLong() + val teacherId = cols[2].toLong() + val teamId = cols[3].toLong() + val topic = cols[4] + val date = Date.fromYmd(cols[5]) + val startTime = Time.fromYmdHm(cols[6]) + val endTime = Time.fromYmdHm(cols[7]) + val presentCount = cols[8].toInt() + val absentCount = cols[9].toInt() + val lessonNumber = cols[10].toInt() + val signed = cols[11] + + val lesson = DataMobidziennik.MobiLesson( + id, + subjectId, + teacherId, + teamId, + topic, + date, + startTime, + endTime, + presentCount, + absentCount, + lessonNumber, + signed + ) + data.mobiLessons.add(lesson) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt new file mode 100644 index 00000000..f1d9aed2 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiNotices.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Notice +import pl.szczodrzynski.edziennik.utils.models.Date + +class MobidziennikApiNotices(val data: DataMobidziennik, rows: List) { + init { run { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val studentId = cols[2].toInt() + if (studentId != data.studentId) + return@run + + val id = cols[0].toLong() + val text = cols[4] + val semester = cols[6].toInt() + val type = when (cols[3]) { + "0" -> Notice.TYPE_NEGATIVE + "1" -> Notice.TYPE_POSITIVE + "3" -> Notice.TYPE_NEUTRAL + else -> Notice.TYPE_NEUTRAL + } + val teacherId = cols[5].toLong() + val addedDate = Date.fromYmd(cols[7]).inMillis + + val noticeObject = Notice( + data.profileId, + id, + text, + semester, + type, + teacherId) + + data.noticeList.add(noticeObject) + data.metadataList.add( + Metadata( + data.profileId, + Metadata.TYPE_NOTICE, + id, + data.profile?.empty ?: false, + data.profile?.empty ?: false, + addedDate + )) + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiStudent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiStudent.kt new file mode 100644 index 00000000..2435478e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiStudent.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-6. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik + +class MobidziennikApiStudent(val data: DataMobidziennik, rows: List) { + init { run { + if (rows.size < 2) { + return@run + } + + val student1 = rows[0].split("|") + val student2 = rows[1].split("|") + + // FROM OLD Mobidziennik API - this information seems to be unused + /*students.clear(); + String[] student = table.split("\n"); + for (int i = 0; i < student.length; i++) { + if (student[i].isEmpty()) { + continue; + } + String[] student1 = student[i].split("\\|", Integer.MAX_VALUE); + String[] student2 = student[++i].split("\\|", Integer.MAX_VALUE); + students.put(strToInt(student1[0]), new Pair<>(student1, student2)); + } + Pair studentData = students.get(studentId); + try { + profile.setAttendancePercentage(Float.parseFloat(studentData.second[1])); + } + catch (Exception e) { + e.printStackTrace(); + }*/ + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiSubjects.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiSubjects.kt new file mode 100644 index 00000000..ce635860 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiSubjects.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-6. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Subject + +class MobidziennikApiSubjects(val data: DataMobidziennik, rows: List) { + init { + for (row in rows) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val id = cols[0].toLong() + val longName = cols[1].trim() + val shortName = cols[2].trim() + + data.subjectsMap.put(id, longName) + data.subjectList.put(id, Subject(data.profileId, id, longName, shortName)) + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt new file mode 100644 index 00000000..6440b04e --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTeams.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.db.entity.Team +import pl.szczodrzynski.edziennik.getById +import pl.szczodrzynski.edziennik.values + +class MobidziennikApiTeams(val data: DataMobidziennik, tableTeams: List?, tableRelations: List?) { + init { + if (tableTeams != null) { + for (row in tableTeams) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val id = cols[0].toLong() + val name = cols[1]+cols[2] + val type = cols[3].toInt() + val code = data.loginServerName+":"+name + val teacherId = cols[4].toLongOrNull() ?: -1 + + val teamObject = Team( + data.profileId, + id, + name, + type, + code, + teacherId) + data.teamList.put(id, teamObject) + } + } + if (tableRelations != null) { + val allTeams = data.teamList.values() + data.teamList.clear() + + for (row in tableRelations) { + if (row.isEmpty()) + continue + val cols = row.split("|") + + val studentId = cols[1].toInt() + val teamId = cols[2].toLong() + val studentNumber = cols[4].toIntOrNull() ?: -1 + + if (studentId != data.studentId) + continue + val team = allTeams.getById(teamId) + if (team != null) { + if (team.type == 1) { + data.profile?.studentNumber = studentNumber + data.teamClass = team + data.profile?.studentClassName = team.name + } + data.teamList.put(teamId, team) + } + } + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt new file mode 100644 index 00000000..6a7ce02d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/edziennik/mobidziennik/data/api/MobidziennikApiTimetable.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-10-11. + */ + +package pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.data.api + +import android.util.SparseArray +import androidx.core.util.set +import pl.szczodrzynski.edziennik.data.api.edziennik.mobidziennik.DataMobidziennik +import pl.szczodrzynski.edziennik.data.api.models.DataRemoveModel +import pl.szczodrzynski.edziennik.data.db.entity.LessonRange +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.fixName +import pl.szczodrzynski.edziennik.keys +import pl.szczodrzynski.edziennik.singleOrNull +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time + +class MobidziennikApiTimetable(val data: DataMobidziennik, rows: List) { + init { data.profile?.also { profile -> + val lessons = rows.filterNot { it.isEmpty() }.map { it.split("|") } + + val dataStart = Date.getToday() + val dataEnd = dataStart.clone().stepForward(0, 0, 7 + (6 - dataStart.weekDay)) + + data.toRemove.add(DataRemoveModel.Timetable.between(dataStart.clone(), dataEnd)) + + val dataDays = mutableListOf() + while (dataStart <= dataEnd) { + dataDays += dataStart.value + dataStart.stepForward(0, 0, 1) + } + + val lessonRanges = SparseArray
  • ", "
  • - ")) + } + + val scrollView = ScrollView(activity) + scrollView.addView(textView) + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.whats_new) + .setView(scrollView) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + }} +} \ No newline at end of file diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt new file mode 100644 index 00000000..b46925f0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/day/DayDialog.kt @@ -0,0 +1,190 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-16. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.day + +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.android.synthetic.main.row_lesson_change_item.view.* +import kotlinx.android.synthetic.main.row_teacher_absence_item.view.* +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.databinding.DialogDayBinding +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog +import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.coroutines.CoroutineContext + +class DayDialog( + val activity: AppCompatActivity, + val profileId: Int, + val date: Date, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "DayDialog" + } + + private lateinit var app: App + private lateinit var b: DialogDayBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var adapter: EventListAdapter + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = DialogDayBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .setNeutralButton(R.string.add, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick { + EventManualDialog( + activity, + profileId, + defaultDate = date, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + + update() + }} + + private fun update() { launch { + b.dayDate.setText( + R.string.dialog_day_date_format, + Week.getFullDayName(date.weekDay), + date.formattedString + ) + + val lessons = withContext(Dispatchers.Default) { + app.db.timetableDao().getForDateNow(profileId, date) + }.filter { it.type != Lesson.TYPE_NO_LESSONS } + + if (lessons.isNotEmpty()) { run { + val startTime = lessons.first().startTime ?: return@run + val endTime = lessons.last().endTime ?: return@run + val diff = Time.diff(startTime, endTime) + + b.lessonsInfo.setText( + R.string.dialog_day_lessons_info, + startTime.stringHM, + endTime.stringHM, + lessons.size.toString(), + diff.hour.toString(), + diff.minute.toString() + ) + + b.lessonsInfo.visibility = View.VISIBLE + }} + + val lessonChanges = withContext(Dispatchers.Default) { + app.db.timetableDao().getChangesForDateNow(profileId, date) + } + + lessonChanges.ifNotEmpty { + b.lessonChangeContainer.visibility = View.VISIBLE + b.lessonChangeContainer.lessonChangeCount.text = it.size.toString() + + b.lessonChangeLayout.onClick { + LessonChangeDialog( + activity, + profileId, + date, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + } + + val teacherAbsences = withContext(Dispatchers.Default) { + app.db.teacherAbsenceDao().getAllByDateNow(profileId, date) + } + + teacherAbsences.ifNotEmpty { + b.teacherAbsenceContainer.visibility = View.VISIBLE + b.teacherAbsenceContainer.teacherAbsenceCount.text = it.size.toString() + + b.teacherAbsenceLayout.onClick { + TeacherAbsenceDialog( + activity, + profileId, + date, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + } + + adapter = EventListAdapter( + activity, + onItemClick = { + EventDetailsDialog( + activity, + it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + }, + onEventEditClick = { + EventManualDialog( + activity, + it.profileId, + editingEvent = it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + ) + + app.db.eventDao().getAllByDate(profileId, date).observe(activity, Observer { events -> + adapter.items = events + if (b.eventsView.adapter == null) { + b.eventsView.adapter = adapter + b.eventsView.apply { + isNestedScrollingEnabled = false + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + } + adapter.notifyDataSetChanged() + + if (events != null && events.isNotEmpty()) { + b.eventsView.visibility = View.VISIBLE + b.eventsNoData.visibility = View.GONE + } else { + b.eventsView.visibility = View.GONE + b.eventsNoData.visibility = View.VISIBLE + } + }) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt new file mode 100644 index 00000000..a5a682c6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventDetailsDialog.kt @@ -0,0 +1,277 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-18. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.provider.CalendarContract +import android.provider.CalendarContract.Events +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.databinding.DialogEventDetailsBinding +import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.utils.BetterLink +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class EventDetailsDialog( + val activity: AppCompatActivity, + val event: EventFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "EventDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: DialogEventDetailsBinding + private lateinit var dialog: AlertDialog + private var removeEventDialog: AlertDialog? = null + private val eventShared = event.sharedBy != null + private val eventOwn = event.sharedBy == "self" + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val api by lazy { + SzkolnyApi(app) + } + + private var progressDialog: AlertDialog? = null + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = DialogEventDetailsBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .apply { + if (event.addedManually) + setNeutralButton(R.string.remove, null) + } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + progressDialog?.dismiss() + } + .show() + + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick { + showRemoveEventDialog() + } + + update() + }} + + private fun update() { + b.event = event + b.eventShared = eventShared + b.eventOwn = eventOwn + + val bullet = " • " + val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(activity) + + try { + b.monthName = app.resources.getStringArray(R.array.months_day_of_array)[event.date.month - 1] + } + catch (_: Exception) {} + + b.typeColor.background?.setTintColor(event.eventColor) + + b.details = mutableListOf( + event.subjectLongName, + event.teamName?.asColoredSpannable(colorSecondary) + ).concat(bullet) + + b.addedBy.setText( + when (event.sharedBy) { + null -> when { + event.addedManually -> R.string.event_details_added_by_self_format + event.teacherName == null -> R.string.event_details_added_by_unknown_format + else -> R.string.event_details_added_by_format + } + "self" -> R.string.event_details_shared_by_self_format + else -> R.string.event_details_shared_by_format + }, + Date.fromMillis(event.addedDate).formattedString, + event.sharedByName ?: event.teacherName ?: "" + ) + + b.editButton.visibility = if (event.addedManually) View.VISIBLE else View.GONE + b.editButton.setOnClickListener { + EventManualDialog( + activity, + event.profileId, + editingEvent = event, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + + b.goToTimetableButton.setOnClickListener { + dialog.dismiss() + val dateStr = event.date.stringY_m_d + + val intent = + if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_TIMETABLE) + Intent(TimetableFragment.ACTION_SCROLL_TO_DATE) + else if (activity is MainActivity) + Intent("android.intent.action.MAIN") + else + Intent(activity, MainActivity::class.java) + + intent.apply { + putExtra("fragmentId", MainActivity.DRAWER_ITEM_TIMETABLE) + putExtra("timetableDate", dateStr) + } + if (activity is MainActivity) + activity.sendBroadcast(intent) + else + activity.startActivity(intent) + } + b.saveInCalendarButton.setOnClickListener { + openInCalendar() + } + + b.goToTimetableButton.setOnLongClickListener { + Toast.makeText(activity, R.string.hint_go_to_timetable, Toast.LENGTH_SHORT).show() + true + } + b.saveInCalendarButton.setOnLongClickListener { + Toast.makeText(activity, R.string.hint_save_in_calendar, Toast.LENGTH_SHORT).show() + true + } + b.editButton.setOnLongClickListener { + Toast.makeText(activity, R.string.hint_edit_event, Toast.LENGTH_SHORT).show() + true + } + + b.topic.text = event.topic + BetterLink.attach(b.topic) { + dialog.dismiss() + } + } + + private fun showRemovingProgressDialog() { + if (progressDialog != null) { + return + } + + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.event_removing_text) + .setCancelable(false) + .show() + } + + private fun showRemoveEventDialog() { + val shareNotice = when { + eventShared && eventOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self) + eventShared && !eventOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared) + else -> "" + } + removeEventDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(activity.getString(R.string.dialog_register_event_manual_remove_confirmation)+shareNotice) + .setPositiveButton(R.string.yes, null) + .setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() } + .create() + .apply { + setOnShowListener { dialog -> + val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE) + positiveButton?.setOnClickListener { + removeEvent() + } + } + + show() + } + } + + private fun removeEvent() { + launch { + if (eventShared && eventOwn) { + // unshare + remove own event + showRemovingProgressDialog() + + api.runCatching(activity) { + unshareEvent(event) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + finishRemoving() + } else if (eventShared && !eventOwn) { + // remove + blacklist somebody's event + Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show() + // TODO + } else { + // remove event + Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show() + finishRemoving() + } + progressDialog?.dismiss() + } + } + + private fun finishRemoving() { + launch { + withContext(Dispatchers.Default) { + app.db.eventDao().remove(event) + } + } + + removeEventDialog?.dismiss() + dialog.dismiss() + Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() + if (activity is MainActivity && activity.navTargetId == MainActivity.DRAWER_ITEM_AGENDA) + activity.reloadTarget() + } + + private fun openInCalendar() { launch { + val title = event.typeName ?: "" + + (if (event.typeName.isNotNullNorBlank() && event.subjectLongName.isNotNullNorBlank()) " - " else " ") + + (event.subjectLongName ?: "") + + val intent = Intent(Intent.ACTION_EDIT).apply { + data = Events.CONTENT_URI + putExtra(Events.TITLE, title) + putExtra(Events.DESCRIPTION, event.topic) + + if (event.time == null) { + putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, true) + putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, event.date.inMillis) + putExtra(CalendarContract.EXTRA_EVENT_END_TIME, event.date.inMillis) + } else { + val startTime = event.date.combineWith(event.time) + val endTime = startTime + 45 * 60 * 1000 /* 45 min */ + + putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, false) + putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startTime) + putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime) + } + } + + try { + activity.startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText(activity, R.string.calendar_app_not_found, Toast.LENGTH_SHORT).show() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt new file mode 100644 index 00000000..64c4b6ce --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventListAdapter.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2019-11-30 + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.databinding.EventListItemBinding +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Week + +class EventListAdapter( + val context: Context, + val simpleMode: Boolean = false, + val showDate: Boolean = false, + val showWeekDay: Boolean = false, + val onItemClick: ((event: EventFull) -> Unit)? = null, + val onEventEditClick: ((event: EventFull) -> Unit)? = null +) : RecyclerView.Adapter() { + + private val app by lazy { context.applicationContext as App } + + var items = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = EventListItemBinding.inflate(inflater, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val event = items[position] + val b = holder.b + + b.root.onClick { + onItemClick?.invoke(event) + } + + val bullet = " • " + + b.simpleMode = simpleMode + + b.topic.text = event.topic + + b.details.text = mutableListOf( + if (showWeekDay) Week.getFullDayName(event.date.weekDay) else null, + if (showDate) event.date.getRelativeString(context, 7) ?: event.date.formattedStringShort else null, + event.typeName, + if (simpleMode) null else event.time?.stringHM ?: app.getString(R.string.event_all_day), + if (simpleMode) null else event.subjectLongName + ).concat(bullet) + + b.addedBy.setText( + when (event.sharedBy) { + null -> when { + event.addedManually -> R.string.event_list_added_by_self_format + event.teacherName == null -> R.string.event_list_added_by_unknown_format + else -> R.string.event_list_added_by_format + } + "self" -> R.string.event_list_shared_by_self_format + else -> R.string.event_list_shared_by_format + }, + Date.fromMillis(event.addedDate).formattedString, + event.sharedByName ?: event.teacherName ?: "", + event.teamName?.let { bullet+it } ?: "" + ) + + b.typeColor.background?.setTintColor(event.eventColor) + + b.editButton.visibility = if (event.addedManually && !simpleMode) View.VISIBLE else View.GONE + b.editButton.onClick { + onEventEditClick?.invoke(event) + } + + b.editButton.setOnLongClickListener { + Toast.makeText(context, R.string.hint_edit_event, Toast.LENGTH_SHORT).show() + true + } + + /*with(holder) { + b.eventListItemRoot.background.colorFilter = when (event.type) { + Event.TYPE_HOMEWORK -> PorterDuffColorFilter(0xffffffff.toInt(), PorterDuff.Mode.CLEAR) + else -> PorterDuffColorFilter(event.color, PorterDuff.Mode.MULTIPLY) + } + + b.eventListItemStartTime.text = if (event.startTime == null) app.getString(R.string.event_all_day) else event.startTime?.stringHM + b.eventListItemTeamName.text = bs(event.teamName) + b.eventListItemTeacherName.text = app.getString(R.string.concat_2_strings, bs(null, event.teacherFullName, "\n"), bs(event.subjectLongName)) + b.eventListItemAddedDate.text = Date.fromMillis(event.addedDate).formattedStringShort + b.eventListItemType.text = event.typeName + b.eventListItemTopic.text = event.topic + b.eventListItemHomework.visibility = if (event.type == Event.TYPE_HOMEWORK) View.VISIBLE else View.GONE + b.eventListItemSharedBy.text = app.getString(R.string.event_shared_by_format, if (event.sharedBy == "self") app.getString(R.string.event_shared_by_self) else event.sharedByName) + b.eventListItemSharedBy.visibility = if (event.sharedByName.isNullOrBlank()) View.GONE else View.VISIBLE + + b.eventListItemEdit.visibility = if (event.addedManually) View.VISIBLE else View.GONE + b.eventListItemEdit.setOnClickListener { + parentDialog.dismiss() + + EventManualDialog( + context as MainActivity, + event.profileId, + editingEvent = event, + onShowListener = parentDialog.onShowListener, + onDismissListener = parentDialog.onDismissListener + ) + } + }*/ + } + + override fun getItemCount() = items.size + + class ViewHolder(val b: EventListItemBinding) : RecyclerView.ViewHolder(b.root) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt new file mode 100644 index 00000000..ecf1ff71 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/event/EventManualDialog.kt @@ -0,0 +1,614 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-12. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.event + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL +import androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.jaredrummler.android.colorpicker.ColorPickerDialog +import com.jaredrummler.android.colorpicker.ColorPickerDialogListener +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.MainActivity.Companion.DRAWER_ITEM_AGENDA +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.EventType +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.full.EventFull +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.DialogEventManualV2Binding +import pl.szczodrzynski.edziennik.ui.dialogs.sync.RegistrationEnableDialog +import pl.szczodrzynski.edziennik.ui.modules.views.TimeDropdown.Companion.DISPLAY_LESSONS +import pl.szczodrzynski.edziennik.utils.Anim +import pl.szczodrzynski.edziennik.utils.TextInputDropDown +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import kotlin.coroutines.CoroutineContext + +class EventManualDialog( + val activity: AppCompatActivity, + val profileId: Int, + val defaultLesson: LessonFull? = null, + val defaultDate: Date? = null, + val defaultTime: Time? = null, + val defaultType: Long? = null, + val editingEvent: EventFull? = null, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + + companion object { + private const val TAG = "EventManualDialog" + } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val app by lazy { activity.application as App } + private lateinit var b: DialogEventManualV2Binding + private lateinit var dialog: AlertDialog + private var profile: Profile? = null + + private var customColor: Int? = null + private val editingShared = editingEvent?.sharedBy != null + private val editingOwn = editingEvent?.sharedBy == "self" + private var removeEventDialog: AlertDialog? = null + + private val api by lazy { + SzkolnyApi(app) + } + + private var enqueuedWeekDialog: AlertDialog? = null + private var enqueuedWeekStart = Date.getToday() + + private var progressDialog: AlertDialog? = null + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + EventBus.getDefault().register(this) + b = DialogEventManualV2Binding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_event_manual_title) + .setView(b.root) + .setNegativeButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.save, null) + .apply { + if (editingEvent != null) { + setNeutralButton(R.string.remove, null) + } + } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + EventBus.getDefault().unregister(this@EventManualDialog) + enqueuedWeekDialog?.dismiss() + progressDialog?.dismiss() + } + .setCancelable(false) + .create() + .apply { + setOnShowListener { dialog -> + val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE) + positiveButton?.setOnClickListener { + saveEvent() + } + + val neutralButton = dialog.getButton(BUTTON_NEUTRAL) + neutralButton?.setOnClickListener { + showRemoveEventDialog() + } + } + + show() + } + + b.shareSwitch.isChecked = editingShared + b.shareSwitch.isEnabled = !editingShared || (editingShared && editingOwn) + + 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) + } + } + + updateShareText() + b.shareSwitch.onChange { _, isChecked -> + updateShareText(isChecked) + } + + loadLists() + }} + + private fun updateShareText(checked: Boolean = b.shareSwitch.isChecked) { + b.shareDetails.visibility = if (checked || editingShared) + View.VISIBLE + else View.GONE + + val text = when { + checked && editingShared && editingOwn -> R.string.dialog_event_manual_share_will_change + checked && editingShared -> R.string.dialog_event_manual_share_will_request + !checked && editingShared -> R.string.dialog_event_manual_share_will_remove + else -> R.string.dialog_event_manual_share_first_notice + } + + b.shareDetails.setText(text, editingEvent?.sharedByName ?: "") + } + + private fun syncTimetable(date: Date) { + if (enqueuedWeekDialog != null) { + return + } + if (app.profile.getStudentData("timetableNotPublic", false)) { + return + } + val weekStart = date.weekStart + enqueuedWeekDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.timetable_syncing_text) + .setCancelable(false) + .show() + + enqueuedWeekStart = weekStart + + EdziennikTask.syncProfile( + profileId = profileId, + viewIds = listOf( + MainActivity.DRAWER_ITEM_TIMETABLE to 0 + ), + arguments = JsonObject( + "weekStart" to weekStart.stringY_m_d + ) + ).enqueue(activity) + } + + private fun showSharingProgressDialog() { + if (progressDialog != null) { + return + } + + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.event_sharing_text) + .setCancelable(false) + .show() + } + + private fun showRemovingProgressDialog() { + if (progressDialog != null) { + return + } + + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.event_removing_text) + .setCancelable(false) + .show() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { + if (event.profileId == profileId) { + enqueuedWeekDialog?.dismiss() + enqueuedWeekDialog = null + progressDialog?.dismiss() + launch { + b.timeDropdown.loadItems() + b.timeDropdown.selectDefault(editingEvent?.time) + b.timeDropdown.selectDefault(defaultLesson?.displayStartTime ?: defaultTime) + } + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { + enqueuedWeekDialog?.dismiss() + enqueuedWeekDialog = null + progressDialog?.dismiss() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { + enqueuedWeekDialog?.dismiss() + enqueuedWeekDialog = null + progressDialog?.dismiss() + } + + private fun loadLists() { launch { + profile = withContext(Dispatchers.Default) { app.db.profileDao().getByIdNow(profileId) } + + with (b.dateDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showWeekDays = false + showDays = true + showOtherDate = true + defaultLesson?.let { + nextLessonSubjectId = it.displaySubjectId + nextLessonSubjectName = it.displaySubjectName + nextLessonTeamId = it.displayTeamId + } + loadItems() + selectDefault(editingEvent?.date) + selectDefault(defaultLesson?.displayDate ?: defaultDate) + onDateSelected = { date, lesson -> + b.timeDropdown.deselect() + b.timeDropdown.lessonsDate = date + this@EventManualDialog.launch { + if (!b.timeDropdown.loadItems()) + syncTimetable(date) + lesson?.displayStartTime?.let { b.timeDropdown.selectTime(it) } + lesson?.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect() + lesson?.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect() + lesson?.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass() + } + } + } + + with (b.timeDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showAllDay = true + showCustomTime = true + lessonsDate = b.dateDropdown.getSelected() as? Date ?: Date.getToday() + displayMode = DISPLAY_LESSONS + if (!loadItems()) + syncTimetable(lessonsDate ?: Date.getToday()) + selectDefault(editingEvent?.time) + if (editingEvent != null && editingEvent.time == null) + select(0L) + selectDefault(defaultLesson?.displayStartTime ?: defaultTime) + onLessonSelected = { lesson -> + lesson.displaySubjectId?.let { b.subjectDropdown.selectSubject(it) } ?: b.subjectDropdown.deselect() + lesson.displayTeacherId?.let { b.teacherDropdown.selectTeacher(it) } ?: b.teacherDropdown.deselect() + lesson.displayTeamId?.let { b.teamDropdown.selectTeam(it) } ?: b.teamDropdown.selectTeamClass() + } + } + + with (b.teamDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showNoTeam = true + loadItems() + selectTeamClass() + selectDefault(editingEvent?.teamId) + selectDefault(defaultLesson?.displayTeamId) + } + + with (b.subjectDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showNoSubject = true + showCustomSubject = false + loadItems() + selectDefault(editingEvent?.subjectId) + selectDefault(defaultLesson?.displaySubjectId) + } + + with (b.teacherDropdown) { + db = app.db + profileId = this@EventManualDialog.profileId + showNoTeacher = true + loadItems() + selectDefault(editingEvent?.teacherId) + selectDefault(defaultLesson?.displayTeacherId) + } + + + val deferred = async(Dispatchers.Default) { + // get the event type list + var eventTypes = app.db.eventTypeDao().getAllNow(profileId) + + if (eventTypes.none { it.id in -1L..10L }) { + eventTypes = app.db.eventTypeDao().addDefaultTypes(activity, profileId) + } + + b.typeDropdown.clear() + b.typeDropdown += eventTypes.map { TextInputDropDown.Item(it.id, it.name, tag = it) } + } + deferred.await() + + b.typeDropdown.isEnabled = true + + defaultType?.let { + b.typeDropdown.select(it) + } + + b.typeDropdown.selected?.let { item -> + customColor = (item.tag as EventType).color + } + + // copy IDs from event being edited + editingEvent?.let { + b.topic.setText(it.topic) + b.typeDropdown.select(it.type)?.let { item -> + customColor = (item.tag as EventType).color + } + if (it.color != null && it.color != -1) + customColor = it.color + } + + // copy IDs from the LessonFull + defaultLesson?.let { + b.teamDropdown.select(it.displayTeamId) + } + + 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") + } + }} + + private fun showRemoveEventDialog() { + val shareNotice = when { + editingShared && editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared_self) + editingShared && !editingOwn -> "\n\n"+activity.getString(R.string.dialog_event_manual_remove_shared) + else -> "" + } + removeEventDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(activity.getString(R.string.dialog_register_event_manual_remove_confirmation)+shareNotice) + .setPositiveButton(R.string.yes, null) + .setNegativeButton(R.string.no) { dialog, _ -> dialog.dismiss() } + .create() + .apply { + setOnShowListener { dialog -> + val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE) + positiveButton?.setOnClickListener { + removeEvent() + } + } + + show() + } + } + + private fun saveEvent() { + val date = b.dateDropdown.getSelected() as? Date + val timeSelected = b.timeDropdown.getSelected() + val teamId = b.teamDropdown.getSelected() as? Long + val type = b.typeDropdown.selected?.id + val topic = b.topic.text?.toString() + val subjectId = b.subjectDropdown.getSelected() as? Long + val teacherId = b.teacherDropdown.getSelected() + + val share = b.shareSwitch.isChecked + + if (share && profile?.registration != Profile.REGISTRATION_ENABLED) { + RegistrationEnableDialog(activity, profileId).showEventShareDialog { + if (it != null) + profile = it + saveEvent() + } + return + } + + b.dateDropdown.error = null + b.teamDropdown.error = null + b.typeDropdown.error = null + b.topic.error = null + + var isError = false + + if (date == null) { + b.dateDropdown.error = app.getString(R.string.dialog_event_manual_date_choose) + b.dateDropdown.requestFocus() + isError = true + } + + if (timeSelected !is Pair<*, *> && timeSelected != 0L) { + b.timeDropdown.error = app.getString(R.string.dialog_event_manual_time_choose) + if (!isError) b.timeDropdown.parent.requestChildFocus(b.timeDropdown, b.timeDropdown) + isError = true + } + + if (share && teamId == null) { + b.teamDropdown.error = app.getString(R.string.dialog_event_manual_team_choose) + if (!isError) b.teamDropdown.parent.requestChildFocus(b.teamDropdown, b.teamDropdown) + isError = true + } + + if (type == null) { + b.typeDropdown.error = app.getString(R.string.dialog_event_manual_type_choose) + if (!isError) b.typeDropdown.requestFocus() + isError = true + } + + if (topic.isNullOrBlank()) { + b.topic.error = app.getString(R.string.dialog_event_manual_topic_choose) + if (!isError) b.topic.requestFocus() + isError = true + } + + val startTime = if (timeSelected == 0L) + null + else + (timeSelected as? Pair<*, *>)?.first as? Time + + if (isError) return + date ?: return + topic ?: return + + val id = System.currentTimeMillis() + + val eventObject = Event( + profileId = profileId, + id = editingEvent?.id ?: id, + date = date, + time = startTime, + topic = topic, + color = customColor, + type = type ?: Event.TYPE_DEFAULT, + teacherId = teacherId ?: -1, + subjectId = subjectId ?: -1, + teamId = teamId ?: -1 + ).also { + it.addedManually = true + } + + val metadataObject = Metadata( + profileId, + when (type) { + Event.TYPE_HOMEWORK -> Metadata.TYPE_HOMEWORK + else -> Metadata.TYPE_EVENT + }, + eventObject.id, + true, + true, + editingEvent?.addedDate ?: System.currentTimeMillis() + ) + + launch { + val profile = app.db.profileDao().getByIdNow(profileId) + + if (!share && !editingShared) { + //Toast.makeText(activity, R.string.event_manual_saving, Toast.LENGTH_SHORT).show() + finishAdding(eventObject, metadataObject) + } + else if (editingShared && !editingOwn) { + Toast.makeText(activity, "Opcja edycji wydarzeń innych uczniów nie została jeszcze zaimplementowana.", Toast.LENGTH_LONG).show() + // TODO + } + else if (!share && editingShared) { + showSharingProgressDialog() + + eventObject.apply { + sharedBy = null + sharedByName = profile?.studentNameLong + } + + api.runCatching(activity) { + unshareEvent(eventObject) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + eventObject.sharedByName = null + finishAdding(eventObject, metadataObject) + } + else if (share) { + showSharingProgressDialog() + + eventObject.apply { + sharedBy = profile?.userCode + sharedByName = profile?.studentNameLong + } + + metadataObject.addedDate = System.currentTimeMillis() + + api.runCatching(activity) { + shareEvent(eventObject.withMetadata(metadataObject)) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + eventObject.sharedBy = "self" + finishAdding(eventObject, metadataObject) + } + else { + Toast.makeText(activity, "Unknown action :(", Toast.LENGTH_SHORT).show() + } + progressDialog?.dismiss() + } + } + + private fun removeEvent() { + launch { + if (editingShared && editingOwn) { + // unshare + remove own event + showRemovingProgressDialog() + + api.runCatching(activity) { + unshareEvent(editingEvent!!) + } ?: run { + progressDialog?.dismiss() + return@launch + } + + finishRemoving() + } else if (editingShared && !editingOwn) { + // remove + blacklist somebody's event + Toast.makeText(activity, "Nie zaimplementowana opcja :(", Toast.LENGTH_SHORT).show() + // TODO + } else { + // remove event + //Toast.makeText(activity, R.string.event_manual_remove, Toast.LENGTH_SHORT).show() + finishRemoving() + } + progressDialog?.dismiss() + } + } + + private fun finishAdding(eventObject: Event, metadataObject: Metadata) { + launch { + withContext(Dispatchers.Default) { + app.db.eventDao().add(eventObject) + app.db.metadataDao().add(metadataObject) + } + } + + dialog.dismiss() + Toast.makeText(activity, R.string.saved, Toast.LENGTH_SHORT).show() + if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA) + activity.reloadTarget() + } + private fun finishRemoving() { + editingEvent ?: return + launch { + withContext(Dispatchers.Default) { + app.db.eventDao().remove(editingEvent) + } + } + + removeEventDialog?.dismiss() + dialog.dismiss() + Toast.makeText(activity, R.string.removed, Toast.LENGTH_SHORT).show() + if (activity is MainActivity && activity.navTargetId == DRAWER_ITEM_AGENDA) + activity.reloadTarget() + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt new file mode 100644 index 00000000..0e063758 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/grade/GradeDetailsDialog.kt @@ -0,0 +1,92 @@ +package pl.szczodrzynski.edziennik.ui.dialogs.grade + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.ColorUtils +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.GradeFull +import pl.szczodrzynski.edziennik.databinding.DialogGradeDetailsBinding +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.setTintColor +import pl.szczodrzynski.edziennik.ui.dialogs.settings.GradesConfigDialog +import pl.szczodrzynski.edziennik.ui.modules.grades.GradesAdapter +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import kotlin.coroutines.CoroutineContext + +class GradeDetailsDialog( + val activity: AppCompatActivity, + val grade: GradeFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "GradeDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: DialogGradeDetailsBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = DialogGradeDetailsBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + val manager = app.gradesManager + + val gradeColor = manager.getGradeColor(grade) + b.grade = grade + b.weightText = manager.getWeightString(app, grade) + b.commentVisible = false + b.devMode = App.debugMode + b.gradeName.setTextColor(if (ColorUtils.calculateLuminance(gradeColor) > 0.3) 0xaa000000.toInt() else 0xccffffff.toInt()) + b.gradeName.background.setTintColor(gradeColor) + + b.gradeValue = if (grade.weight == 0f || grade.value < 0f) -1f else manager.getGradeValue(grade) + + b.customValueDivider.isVisible = manager.plusValue != null || manager.minusValue != null + b.customValueLayout.isVisible = b.customValueDivider.isVisible + b.customValueButton.onClick { + GradesConfigDialog(activity, reloadOnDismiss = true) + } + + launch { + val historyList = withContext(Dispatchers.Default) { + app.db.gradeDao().getAllWithParentIdNow(App.profileId, grade.id) + } + if (historyList.isEmpty()) { + b.historyVisible = false + return@launch + } + b.historyVisible = true + //b.gradeHistoryNest.isNestedScrollingEnabled = false + b.gradeHistoryList.adapter = GradesAdapter(activity, { + GradeDetailsDialog(activity, it) + }).also { it.items = historyList.toMutableList() } + b.gradeHistoryList.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/home/StudentNumberDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/home/StudentNumberDialog.kt new file mode 100644 index 00000000..265bd17b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/home/StudentNumberDialog.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-24. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.home + +import android.text.InputType +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.afollestad.materialdialogs.MaterialDialog +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Profile + +class StudentNumberDialog( + val activity: AppCompatActivity, + val profile: Profile, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + private const val TAG = "StudentNumberDialog" + } + + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + MaterialDialog.Builder(activity) + .title(R.string.card_lucky_number_set_title) + .content(R.string.card_lucky_number_set_text) + .inputType(InputType.TYPE_CLASS_NUMBER) + .input(null, if (profile.studentNumber == -1) "" else profile.studentNumber.toString()) { _: MaterialDialog?, input: CharSequence -> + try { + profile.studentNumber = input.toString().toInt() + } catch (e: Exception) { + Toast.makeText(activity, R.string.incorrect_format, Toast.LENGTH_SHORT).show() + } + } + .dismissListener { + onDismissListener?.invoke(TAG) + }.show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeAdapter.kt new file mode 100644 index 00000000..b15803ed --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeAdapter.kt @@ -0,0 +1,189 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-12-19. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.lessonchange + +import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.view.updateLayoutParams +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.TimetableLessonBinding +import pl.szczodrzynski.navlib.getColorFromAttr + +class LessonChangeAdapter( + val context: Context, + private val onItemClick: ((lesson: LessonFull) -> Unit)? = null +) : RecyclerView.Adapter() { + + var items = listOf() + + private val arrowRight = " → " + private val bullet = " • " + private val colorSecondary = android.R.attr.textColorSecondary.resolveAttr(context) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = TimetableLessonBinding.inflate(inflater, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val lesson = items[position] + val b = holder.b + + b.root.onClick { + onItemClick?.invoke(lesson) + } + + val startTime = lesson.displayStartTime ?: return + val endTime = lesson.displayEndTime ?: return + + val timeRange = "${startTime.stringHM} - ${endTime.stringHM}".asColoredSpannable(colorSecondary) + + b.unread = false + b.root.background = null + + b.root.updateLayoutParams { + topMargin = 16.dp + } + + // teacher + val teacherInfo = if (lesson.teacherId != null && lesson.teacherId == lesson.oldTeacherId) + lesson.teacherName ?: "?" + else + mutableListOf().apply { + lesson.oldTeacherName?.let { add(it.asStrikethroughSpannable()) } + lesson.teacherName?.let { add(it) } + }.concat(arrowRight) + + // team + val teamInfo = if (lesson.teamId != null && lesson.teamId == lesson.oldTeamId) + lesson.teamName ?: "?" + else + mutableListOf().apply { + lesson.oldTeamName?.let { add(it.asStrikethroughSpannable()) } + lesson.teamName?.let { add(it) } + }.concat(arrowRight) + + // classroom + val classroomInfo = if (lesson.classroom != null && lesson.classroom == lesson.oldClassroom) + lesson.classroom ?: "?" + else + mutableListOf().apply { + lesson.oldClassroom?.let { add(it.asStrikethroughSpannable()) } + lesson.classroom?.let { add(it) } + }.concat(arrowRight) + + + b.lessonNumber = lesson.displayLessonNumber + b.subjectName.text = lesson.displaySubjectName?.let { + if (lesson.type == Lesson.TYPE_CANCELLED || lesson.type == Lesson.TYPE_SHIFTED_SOURCE) + it.asStrikethroughSpannable().asColoredSpannable(colorSecondary) + else + it + } + b.detailsFirst.text = listOfNotEmpty(timeRange, classroomInfo).concat(bullet) + b.detailsSecond.text = listOfNotEmpty(teacherInfo, teamInfo).concat(bullet) + + //lb.subjectName.typeface = Typeface.create("sans-serif-light", Typeface.BOLD) + when (lesson.type) { + Lesson.TYPE_NORMAL -> { + b.annotationVisible = false + } + Lesson.TYPE_CANCELLED -> { + b.annotationVisible = true + b.annotation.setText(R.string.timetable_lesson_cancelled) + b.annotation.background.colorFilter = PorterDuffColorFilter( + getColorFromAttr(context, R.attr.timetable_lesson_cancelled_color), + PorterDuff.Mode.SRC_ATOP + ) + //lb.subjectName.typeface = Typeface.DEFAULT + } + Lesson.TYPE_CHANGE -> { + b.annotationVisible = true + when { + lesson.subjectId != lesson.oldSubjectId && lesson.teacherId != lesson.oldTeacherId + && lesson.oldSubjectName != null && lesson.oldTeacherName != null -> + b.annotation.setText( + R.string.timetable_lesson_change_format, + "${lesson.oldSubjectName ?: "?"}, ${lesson.oldTeacherName ?: "?"}" + ) + + lesson.subjectId != lesson.oldSubjectId && lesson.oldSubjectName != null -> + b.annotation.setText( + R.string.timetable_lesson_change_format, + lesson.oldSubjectName ?: "?" + ) + + lesson.teacherId != lesson.oldTeacherId && lesson.oldTeacherName != null -> + b.annotation.setText( + R.string.timetable_lesson_change_format, + lesson.oldTeacherName ?: "?" + ) + else -> b.annotation.setText(R.string.timetable_lesson_change) + } + + b.annotation.background.colorFilter = PorterDuffColorFilter( + getColorFromAttr(context, R.attr.timetable_lesson_change_color), + PorterDuff.Mode.SRC_ATOP + ) + } + Lesson.TYPE_SHIFTED_SOURCE -> { + b.annotationVisible = true + when { + lesson.date != lesson.oldDate && lesson.date != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_other_day, + lesson.date?.stringY_m_d ?: "?", + lesson.startTime?.stringHM ?: "" + ) + + lesson.startTime != lesson.oldStartTime && lesson.startTime != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_same_day, + lesson.startTime?.stringHM ?: "?" + ) + + else -> b.annotation.setText(R.string.timetable_lesson_shifted) + } + + b.annotation.background.setTintColor(R.attr.timetable_lesson_shifted_source_color.resolveAttr(context)) + } + Lesson.TYPE_SHIFTED_TARGET -> { + b.annotationVisible = true + when { + lesson.date != lesson.oldDate && lesson.oldDate != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_from_other_day, + lesson.oldDate?.stringY_m_d ?: "?", + lesson.oldStartTime?.stringHM ?: "" + ) + + lesson.startTime != lesson.oldStartTime && lesson.oldStartTime != null -> + b.annotation.setText( + R.string.timetable_lesson_shifted_from_same_day, + lesson.oldStartTime?.stringHM ?: "?" + ) + + else -> b.annotation.setText(R.string.timetable_lesson_shifted_from) + } + + b.annotation.background.colorFilter = PorterDuffColorFilter( + getColorFromAttr(context, R.attr.timetable_lesson_shifted_target_color), + PorterDuff.Mode.SRC_ATOP + ) + } + } + } + + override fun getItemCount() = items.size + + class ViewHolder(val b: TimetableLessonBinding) : RecyclerView.ViewHolder(b.root) +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.kt new file mode 100644 index 00000000..78d40e68 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/lessonchange/LessonChangeDialog.kt @@ -0,0 +1,76 @@ +package pl.szczodrzynski.edziennik.ui.dialogs.lessonchange + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.DialogLessonChangeListBinding +import pl.szczodrzynski.edziennik.ui.dialogs.timetable.LessonDetailsDialog +import pl.szczodrzynski.edziennik.utils.models.Date +import kotlin.coroutines.CoroutineContext + +class LessonChangeDialog( + val activity: AppCompatActivity, + val profileId: Int, + private val defaultDate: Date, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + const val TAG = "LessonChangeDialog" + } + + private val app by lazy { activity.application as App } + + private lateinit var job: Job + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var b: DialogLessonChangeListBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + job = Job() + onShowListener?.invoke(TAG) + b = DialogLessonChangeListBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(defaultDate.formattedString) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .create() + loadLessonChanges() + }} + + private fun loadLessonChanges() { launch { + val lessonChanges = withContext(Dispatchers.Default) { + app.db.timetableDao().getChangesForDateNow(profileId, defaultDate) + } + + val adapter = LessonChangeAdapter( + activity, + onItemClick = { + LessonDetailsDialog( + activity, + it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + ).apply { + items = lessonChanges + } + + b.lessonChangeView.adapter = adapter + b.lessonChangeView.layoutManager = LinearLayoutManager(activity) + + dialog.show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt new file mode 100644 index 00000000..ad29e8f7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/GradesConfigDialog.kt @@ -0,0 +1,162 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-16 + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.settings + +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import it.sephiroth.android.library.numberpicker.doOnStopTrackingTouch +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.databinding.DialogConfigGradesBinding +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_DEFAULT +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.COLOR_MODE_WEIGHTED +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_DATE_DESC +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.ORDER_BY_SUBJECT_ASC +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_AVG +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_AVG_2_SEM +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_AVG +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_1_SEM_2_SEM +import pl.szczodrzynski.edziennik.utils.managers.GradesManager.Companion.YEAR_ALL_GRADES +import java.util.* + +class GradesConfigDialog( + val activity: AppCompatActivity, + private val reloadOnDismiss: Boolean = true, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + const val TAG = "GradesConfigDialog" + } + + private val app by lazy { activity.application as App } + private val config by lazy { app.config.grades } + private val profileConfig by lazy { app.config.getFor(app.profileId).grades } + + private lateinit var b: DialogConfigGradesBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + b = DialogConfigGradesBinding.inflate(activity.layoutInflater) + onShowListener?.invoke(TAG) + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.menu_grades_config) + .setView(b.root) + .setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + saveConfig() + onDismissListener?.invoke(TAG) + if (reloadOnDismiss) (activity as? MainActivity)?.reloadTarget() + } + .create() + initView() + loadConfig() + dialog.show() + }} + + private fun loadConfig() { + b.customPlusCheckBox.isChecked = profileConfig.plusValue != null + b.customPlusValue.isVisible = b.customPlusCheckBox.isChecked + b.customMinusCheckBox.isChecked = profileConfig.minusValue != null + b.customMinusValue.isVisible = b.customMinusCheckBox.isChecked + + b.customPlusValue.progress = profileConfig.plusValue ?: 0.5f + b.customMinusValue.progress = profileConfig.minusValue ?: 0.25f + + when (config.orderBy) { + ORDER_BY_DATE_DESC -> b.sortGradesByDateRadio + ORDER_BY_SUBJECT_ASC -> b.sortGradesBySubjectRadio + else -> null + }?.isChecked = true + + when (profileConfig.colorMode) { + COLOR_MODE_DEFAULT -> b.gradeColorFromERegister + COLOR_MODE_WEIGHTED -> b.gradeColorByValue + else -> null + }?.isChecked = true + + when (profileConfig.yearAverageMode) { + YEAR_ALL_GRADES -> b.gradeAverageMode4 + YEAR_1_AVG_2_AVG -> b.gradeAverageMode0 + YEAR_1_SEM_2_AVG -> b.gradeAverageMode1 + YEAR_1_AVG_2_SEM -> b.gradeAverageMode2 + YEAR_1_SEM_2_SEM -> b.gradeAverageMode3 + else -> null + }?.isChecked = true + + b.dontCountGrades.isChecked = profileConfig.dontCountEnabled && profileConfig.dontCountGrades.isNotEmpty() + b.hideImproved.isChecked = profileConfig.hideImproved + b.averageWithoutWeight.isChecked = profileConfig.averageWithoutWeight + + if (profileConfig.dontCountGrades.isEmpty()) { + b.dontCountGradesText.setText("nb, 0, bz, bd") + } + else { + b.dontCountGradesText.setText(profileConfig.dontCountGrades.join(", ")) + } + } + + private fun saveConfig() { + profileConfig.plusValue = if (b.customPlusCheckBox.isChecked) b.customPlusValue.progress else null + profileConfig.minusValue = if (b.customMinusCheckBox.isChecked) b.customMinusValue.progress else null + + b.dontCountGradesText.setText( + b.dontCountGradesText + .text + ?.toString() + ?.toLowerCase(Locale.getDefault()) + ?.replace(", ", ",") + ) + profileConfig.dontCountEnabled = b.dontCountGrades.isChecked + profileConfig.dontCountGrades = b.dontCountGradesText.text + ?.split(",") + ?.map { it.trim() } + ?: listOf() + } + + private fun initView() { + b.customPlusCheckBox.onChange { _, isChecked -> + b.customPlusValue.isVisible = isChecked + } + b.customMinusCheckBox.onChange { _, isChecked -> + b.customMinusValue.isVisible = isChecked + } + + // who the hell named those methods + // THIS SHIT DOES NOT EVEN WORK + b.customPlusValue.doOnStopTrackingTouch { + profileConfig.plusValue = it.progress + } + b.customMinusValue.doOnStopTrackingTouch { + profileConfig.minusValue = it.progress + } + + b.sortGradesByDateRadio.setOnSelectedListener { config.orderBy = ORDER_BY_DATE_DESC } + b.sortGradesBySubjectRadio.setOnSelectedListener { config.orderBy = ORDER_BY_SUBJECT_ASC } + + b.gradeColorFromERegister.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_DEFAULT } + b.gradeColorByValue.setOnSelectedListener { profileConfig.colorMode = COLOR_MODE_WEIGHTED } + + b.gradeAverageMode4.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_ALL_GRADES } + b.gradeAverageMode0.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_AVG } + b.gradeAverageMode1.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_AVG } + b.gradeAverageMode2.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_AVG_2_SEM } + b.gradeAverageMode3.setOnSelectedListener { profileConfig.yearAverageMode = YEAR_1_SEM_2_SEM } + + b.hideImproved.onChange { _, isChecked -> profileConfig.hideImproved = isChecked } + b.averageWithoutWeight.onChange { _, isChecked -> profileConfig.averageWithoutWeight = isChecked } + + b.averageWithoutWeightHelp.onClick { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.grades_config_average_without_weight) + .setMessage(R.string.grades_config_average_without_weight_message) + .setPositiveButton(R.string.ok, null) + .show() + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt new file mode 100644 index 00000000..14321317 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/settings/ProfileRemoveDialog.kt @@ -0,0 +1,95 @@ +/* + * 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 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.attendanceTypeDao().clear(profileId) + app.db.classroomDao().clear(profileId) + app.db.configDao().clear(profileId) + app.db.endpointTimerDao().clear(profileId) + app.db.eventDao().clear(profileId) + app.db.eventTypeDao().clear(profileId) + app.db.gradeCategoryDao().clear(profileId) + app.db.gradeDao().clear(profileId) + app.db.lessonRangeDao().clear(profileId) + app.db.librusLessonDao().clear(profileId) + app.db.luckyNumberDao().clear(profileId) + app.db.messageDao().clear(profileId) + app.db.messageRecipientDao().clear(profileId) + app.db.noticeDao().clear(profileId) + app.db.noticeTypeDao().clear(profileId) + app.db.noticeTypeDao().clear(profileId) + app.db.notificationDao().clear(profileId) + app.db.subjectDao().clear(profileId) + app.db.teacherAbsenceDao().clear(profileId) + app.db.teacherAbsenceDao().clear(profileId) + app.db.teacherAbsenceTypeDao().clear(profileId) + app.db.teacherDao().clear(profileId) + app.db.teamDao().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) + + if (App.profileId == profileId) { + app.profileLoadLast { } + } + } + deferred.await() + dialog.dismiss() + activity.reloadTarget() + Toast.makeText(activity, R.string.dialog_profile_remove_success, Toast.LENGTH_LONG).show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt new file mode 100644 index 00000000..ad31c02d --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/NotificationFilterDialog.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-2-21. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.sync + +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.data.db.entity.Notification +import pl.szczodrzynski.edziennik.onClick +import kotlin.coroutines.CoroutineContext + +// TODO refactor dialog to allow configuring other profiles +// than the selected one in UI +class NotificationFilterDialog( + val activity: AppCompatActivity, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "NotificationFilterDialog" + private val notificationTypes = listOf( + Notification.TYPE_TIMETABLE_LESSON_CHANGE to R.string.notification_type_timetable_lesson_change, + Notification.TYPE_NEW_GRADE to R.string.notification_type_new_grade, + Notification.TYPE_NEW_EVENT to R.string.notification_type_new_event, + Notification.TYPE_NEW_HOMEWORK to R.string.notification_type_new_homework, + Notification.TYPE_NEW_MESSAGE to R.string.notification_type_new_message, + Notification.TYPE_LUCKY_NUMBER to R.string.notification_type_lucky_number, + Notification.TYPE_NEW_NOTICE to R.string.notification_type_notice, + Notification.TYPE_NEW_ATTENDANCE to R.string.notification_type_attendance, + Notification.TYPE_NEW_ANNOUNCEMENT to R.string.notification_type_new_announcement, + Notification.TYPE_NEW_SHARED_EVENT to R.string.notification_type_new_shared_event, + Notification.TYPE_NEW_SHARED_HOMEWORK to R.string.notification_type_new_shared_homework, + Notification.TYPE_REMOVED_SHARED_EVENT to R.string.notification_type_removed_shared_event, + Notification.TYPE_TEACHER_ABSENCE to R.string.notification_type_new_teacher_absence + ) + } + + private lateinit var app: App + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val notificationFilter = mutableListOf() + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + + notificationFilter.clear() + notificationFilter += app.config.forProfile().sync.notificationFilter + val items = notificationTypes.map { app.getString(it.second) }.toTypedArray() + val checkedItems = notificationTypes.map { !notificationFilter.contains(it.first) }.toBooleanArray() + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.dialog_notification_filter_title) + //.setMessage(R.string.dialog_notification_filter_text) + .setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> + val type = notificationTypes[which].first + notificationFilter.remove(type) + if (!isChecked) + notificationFilter += type + } + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick { + if (notificationFilter.isEmpty()) { + app.config.forProfile().sync.notificationFilter = notificationFilter + dialog.dismiss() + return@onClick + } + // warn user when he tries to disable some notifications + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.are_you_sure) + .setMessage(R.string.notification_filter_warning) + .setPositiveButton(R.string.ok) { _, _ -> + app.config.forProfile().sync.notificationFilter = notificationFilter + dialog.dismiss() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/RegistrationEnableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/RegistrationEnableDialog.kt new file mode 100644 index 00000000..7fc0caf3 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/RegistrationEnableDialog.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-3-15. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.sync + +import android.text.Html +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.task.AppSync +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import kotlin.coroutines.CoroutineContext + +class RegistrationEnableDialog( + val activity: AppCompatActivity, + val profileId: Int +) : CoroutineScope { + companion object { + private const val TAG = "RegistrationEnableDialog" + } + + private lateinit var app: App + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + // local variables go here + private var progressDialog: AlertDialog? = null + + init { run { + if (activity.isFinishing) + return@run + app = activity.applicationContext as App + }} + + fun showEventShareDialog(onSuccess: (profile: Profile?) -> Unit) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.event_manual_need_registration_title) + .setMessage(R.string.event_manual_need_registration_text) + .setPositiveButton(R.string.ok) { dialog, which -> + enableRegistration(onSuccess) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + fun showEnableDialog(onSuccess: (profile: Profile?) -> Unit) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.registration_enable_dialog_title) + .setMessage(Html.fromHtml(app.getString(R.string.registration_enable_dialog_text))) + .setPositiveButton(R.string.ok) { dialog, which -> + enableRegistration(onSuccess) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + private fun enableRegistration(onSuccess: (profile: Profile?) -> Unit) { launch { + progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.registration_enable_progress_text) + .setCancelable(false) + .show() + + val profile = withContext(Dispatchers.Default) { + val profile = app.db.profileDao().getByIdNow(profileId) ?: return@withContext null + profile.registration = Profile.REGISTRATION_ENABLED + + // force full registration of the user + App.config.getFor(profile.id).hash = "" + + AppSync(app, mutableListOf(), listOf(profile), SzkolnyApi(app)).run(0L, markAsSeen = true) + app.db.profileDao().add(profile) + if (profile.id == App.profileId) { + App.profile.registration = profile.registration + } + return@withContext profile + } + + progressDialog?.dismiss() + onSuccess(profile) + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt new file mode 100644 index 00000000..b9cbd0a0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/sync/SyncViewListDialog.kt @@ -0,0 +1,116 @@ +/* + * 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.data.api.edziennik.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( + 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()) + } + + if (selectedViewIds.isNotEmpty()) { + activity.swipeRefreshLayout.isRefreshing = true + EdziennikTask.syncProfile( + App.profileId, + selectedViewIds + ).enqueue(activity) + } + } + .setNeutralButton(R.string.sync_feature_all) { _, _ -> + dialog.dismiss() + + activity.swipeRefreshLayout.isRefreshing = true + EdziennikTask.syncProfile(App.profileId).enqueue(activity) + } + .setNegativeButton(R.string.cancel) { _, _ -> + dialog.dismiss() + } + .show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt new file mode 100644 index 00000000..d8c11307 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceAdapter.kt @@ -0,0 +1,65 @@ +package pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.full.TeacherAbsenceFull +import pl.szczodrzynski.edziennik.utils.models.Date + +class TeacherAbsenceAdapter( + private val context: Context, + private val date: Date, + private val teacherAbsenceList: List +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater: LayoutInflater = LayoutInflater.from(context) + val view: View = inflater.inflate(R.layout.row_dialog_teacher_absence_item, parent, false) + return ViewHolder(view) + } + + override fun getItemCount(): Int { + return teacherAbsenceList.size + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val teacherAbsence: TeacherAbsenceFull = teacherAbsenceList[position] + + holder.teacherAbsenceTeacher.text = teacherAbsence.teacherFullName + + val time = when (teacherAbsence.timeFrom != null && teacherAbsence.timeTo != null) { + true -> when (teacherAbsence.dateFrom.compareTo(teacherAbsence.dateTo)) { + 0 -> teacherAbsence.dateFrom.formattedStringShort + " " + + teacherAbsence.timeFrom.stringHM + " - " + teacherAbsence.timeTo.stringHM + + else -> teacherAbsence.dateFrom.formattedStringShort + " " + teacherAbsence.timeTo.stringHM + + " - " + teacherAbsence.dateTo.formattedStringShort + " " + teacherAbsence.timeTo.stringHM + } + + false -> when (teacherAbsence.dateFrom.compareTo(teacherAbsence.dateTo)) { + 0 -> teacherAbsence.dateFrom.formattedStringShort + else -> teacherAbsence.dateFrom.formattedStringShort + " - " + teacherAbsence.dateTo.formattedStringShort + } + } + + holder.teacherAbsenceTime.text = time + + if (teacherAbsence.name != null) { + holder.teacherAbsenceName.visibility = View.VISIBLE + holder.teacherAbsenceName.text = teacherAbsence.name + } else { + holder.teacherAbsenceName.visibility = View.GONE + } + + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var teacherAbsenceTeacher: TextView = itemView.findViewById(R.id.teacherAbsenceTeacher) + var teacherAbsenceTime: TextView = itemView.findViewById(R.id.teacherAbsenceTime) + var teacherAbsenceName: TextView = itemView.findViewById(R.id.teacherAbsenceName) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt new file mode 100644 index 00000000..836197f0 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/teacherabsence/TeacherAbsenceDialog.kt @@ -0,0 +1,57 @@ +package pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence + +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.databinding.DialogTeacherAbsenceListBinding +import pl.szczodrzynski.edziennik.utils.models.Date + +class TeacherAbsenceDialog( + val activity: AppCompatActivity, + val profileId: Int, + val date: Date, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) { + companion object { + private const val TAG = "TeacherAbsenceDialog" + } + + private val app by lazy { activity.application as App } + + private lateinit var b: DialogTeacherAbsenceListBinding + private lateinit var dialog: AlertDialog + + init { run { + if (activity.isFinishing) + return@run + + b = DialogTeacherAbsenceListBinding.inflate(activity.layoutInflater) + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(date.formattedString) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .create() + + b.teacherAbsenceView.setHasFixedSize(true) + b.teacherAbsenceView.layoutManager = LinearLayoutManager(activity) + + app.db.teacherAbsenceDao().getAllByDateFull(profileId, date).observe(activity as LifecycleOwner, Observer { absenceList -> + val adapter = TeacherAbsenceAdapter(activity, date, absenceList) + b.teacherAbsenceView.adapter = adapter + b.teacherAbsenceView.visibility = View.VISIBLE + }) + + dialog.show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt new file mode 100644 index 00000000..aefd16f9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt @@ -0,0 +1,418 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-5 + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.timetable + +import android.content.Intent +import android.graphics.* +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.util.Log +import android.view.View +import android.view.View.MeasureSpec +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.cardview.widget.CardView +import androidx.core.content.FileProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.wdullaer.materialdatetimepicker.date.DatePickerDialog +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.edziennik.EdziennikTask +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskAllFinishedEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskErrorEvent +import pl.szczodrzynski.edziennik.data.api.events.ApiTaskFinishedEvent +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.DialogGenerateBlockTimetableBinding +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Time +import pl.szczodrzynski.edziennik.utils.models.Week +import java.io.File +import java.io.FileOutputStream +import kotlin.coroutines.CoroutineContext +import kotlin.math.roundToInt + +class GenerateBlockTimetableDialog( + val activity: AppCompatActivity, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + const val TAG = "GenerateBlockTimetableDialog" + + private const val WIDTH_CONSTANT = 70 + private const val WIDTH_WEEKDAY = 285 + private const val WIDTH_SPACING = 15 + private const val HEIGHT_CONSTANT = 60 + private const val HEIGHT_MINUTE = 3 + private const val HEIGHT_FOOTER = 40 + } + + private val heightProfileName by lazy { if (showProfileName) 100 else 0 } + + private val app by lazy { activity.application as App } + + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var dialog: AlertDialog + private lateinit var b: DialogGenerateBlockTimetableBinding + + private var showProfileName: Boolean = false + private var showTeachersNames: Boolean = true + private var noColors: Boolean = false + + private var enqueuedWeekDialog: AlertDialog? = null + private var enqueuedWeekStart = Date.getToday() + private var enqueuedWeekEnd = Date.getToday() + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + EventBus.getDefault().register(this) + + val weekCurrentStart = Week.getWeekStart() + val weekCurrentEnd = Week.getWeekEnd() + val weekNextStart = weekCurrentEnd.clone().stepForward(0, 0, 1) + val weekNextEnd = weekNextStart.clone().stepForward(0, 0, 6) + + b = DialogGenerateBlockTimetableBinding.inflate(activity.layoutInflater) + + b.withChangesCurrentWeekRadio.setText(R.string.timetable_generate_current_week_format, weekCurrentStart.formattedStringShort, weekCurrentEnd.formattedStringShort) + b.withChangesNextWeekRadio.setText(R.string.timetable_generate_next_week_format, weekNextStart.formattedStringShort, weekCurrentEnd.formattedStringShort) + + b.showProfileNameCheckbox.setOnCheckedChangeListener { _, isChecked -> showProfileName = isChecked } + b.showTeachersNamesCheckbox.setOnCheckedChangeListener { _, isChecked -> showTeachersNames = isChecked } + b.noColorsCheckbox.setOnCheckedChangeListener { _, isChecked -> noColors = isChecked } + + dialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.timetable_generate_range) + .setView(b.root) + .setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() } + .setPositiveButton(R.string.save, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + EventBus.getDefault().unregister(this@GenerateBlockTimetableDialog) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.onClick { + when (b.weekSelectionRadioGroup.checkedRadioButtonId) { + R.id.withChangesCurrentWeekRadio -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd) + R.id.withChangesNextWeekRadio -> generateBlockTimetable(weekNextStart, weekNextEnd) + R.id.forSelectedWeekRadio -> selectDate() + } + } + }} + + private fun selectDate() { + val date = Date.getToday() + DatePickerDialog + .newInstance({ _, year, monthOfYear, dayOfMonth -> + val dateSelected = Date(year, monthOfYear, dayOfMonth) + generateBlockTimetable(dateSelected.weekStart, dateSelected.weekEnd) + }, date.year, date.month, date.day) + .apply { + accentColor = R.attr.colorPrimary.resolveAttr(this@GenerateBlockTimetableDialog.activity) + show(this@GenerateBlockTimetableDialog.activity.supportFragmentManager, "DatePickerDialog") + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskFinishedEvent(event: ApiTaskFinishedEvent) { + if (event.profileId == App.profileId) { + enqueuedWeekDialog?.dismiss() + generateBlockTimetable(enqueuedWeekStart, enqueuedWeekEnd) + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskAllFinishedEvent(event: ApiTaskAllFinishedEvent) { + enqueuedWeekDialog?.dismiss() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onApiTaskErrorEvent(event: ApiTaskErrorEvent) { + dialog.dismiss() + enqueuedWeekDialog?.dismiss() + } + + private fun generateBlockTimetable(weekStart: Date, weekEnd: Date) { launch { + val weekDays = mutableListOf>() + for (i in weekStart.weekDay..weekEnd.weekDay) { + weekDays.add(mutableListOf()) + } + + val allLessons = withContext(Dispatchers.Default) { + app.db.timetableDao().getBetweenDatesNow(weekStart, weekEnd) + } + val lessonRanges = mutableMapOf() + + var maxWeekDay = 5 + var minTime: Time? = null + var maxTime: Time? = null + + val lessons: List = allLessons.mapNotNull { lesson -> + if (lesson.profileId != app.profile.id || lesson.type == Lesson.TYPE_NO_LESSONS + || lesson.displayDate == null || lesson.displayStartTime == null || lesson.displayEndTime == null) + return@mapNotNull null + + if (lesson.displayDate!!.weekDay > maxWeekDay) + maxWeekDay = lesson.displayDate!!.weekDay + + lessonRanges[lesson.displayStartTime!!.value] = lesson.displayEndTime!!.value + weekDays[lesson.displayDate!!.weekDay].add(lesson) + + if (minTime == null || lesson.displayStartTime!! < minTime!!) { + minTime = lesson.displayStartTime!!.clone() + } + + if (maxTime == null || lesson.displayEndTime!! > maxTime!!) { + maxTime = lesson.displayEndTime!!.clone() + } + + return@mapNotNull lesson + } + + if (lessons.isEmpty()) { + if (enqueuedWeekDialog != null) { + return@launch + } + enqueuedWeekDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.please_wait) + .setMessage(R.string.timetable_syncing_text) + .setCancelable(false) + .show() + + enqueuedWeekStart = weekStart + enqueuedWeekEnd = weekEnd + + EdziennikTask.syncProfile( + profileId = App.profileId, + viewIds = listOf( + MainActivity.DRAWER_ITEM_TIMETABLE to 0 + ), + arguments = JsonObject( + "weekStart" to weekStart.stringY_m_d + ) + ).enqueue(activity) + return@launch + } + + val progressDialog = MaterialAlertDialogBuilder(activity) + .setTitle(R.string.timetable_generate_progress_title) + .setMessage(R.string.timetable_generate_progress_text) + .show() + + if (minTime == null) { + progressDialog.dismiss() + // TODO: Toast + return@launch + } + + dialog.dismiss() + + val uri = withContext(Dispatchers.Default) { + + val diff = Time.diff(maxTime, minTime) + + val imageWidth = WIDTH_CONSTANT + maxWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) - WIDTH_SPACING + val imageHeight = heightProfileName + HEIGHT_CONSTANT + diff.inMinutes * HEIGHT_MINUTE + HEIGHT_FOOTER + val bitmap = Bitmap.createBitmap(imageWidth + 20, imageHeight + 30, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + + if (noColors) canvas.drawARGB(255, 255, 255, 255) + else canvas.drawARGB(255, 225, 225, 225) + + val paint = Paint().apply { + isAntiAlias = true + isFilterBitmap = true + isDither = true + } + + lessons.forEach { lesson -> + val lessonLength = Time.diff(lesson.displayEndTime, lesson.displayStartTime) + val firstOffset = Time.diff(lesson.displayStartTime, minTime) + val lessonWeekDay = lesson.displayDate!!.weekDay + + val left = WIDTH_CONSTANT + lessonWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) + val top = heightProfileName + HEIGHT_CONSTANT + firstOffset.inMinutes * HEIGHT_MINUTE + + val blockWidth = WIDTH_WEEKDAY + val blockHeight = lessonLength.inMinutes * HEIGHT_MINUTE + + val viewWidth = 380.dp + val viewHeight = lessonLength.inMinutes * 4.dp + + val layout = activity.layoutInflater.inflate(R.layout.row_timetable_block_item, null) as LinearLayout + + val item: LinearLayout = layout.findViewById(R.id.timetableItemLayout) + val card: CardView = layout.findViewById(R.id.timetableItemCard) + val subjectName: TextView = layout.findViewById(R.id.timetableItemSubjectName) + val classroomName: TextView = layout.findViewById(R.id.timetableItemClassroomName) + val teacherName: TextView = layout.findViewById(R.id.timetableItemTeacherName) + val teamName: TextView = layout.findViewById(R.id.timetableItemTeamName) + + if (noColors) { + card.setCardBackgroundColor(Color.WHITE) + card.cardElevation = 0f + item.setBackgroundResource(R.drawable.bg_rounded_16dp_outline) + subjectName.setTextColor(Color.BLACK) + classroomName.setTextColor(0xffaaaaaa.toInt()) + teacherName.setTextColor(0xffaaaaaa.toInt()) + teamName.setTextColor(0xffaaaaaa.toInt()) + } + + subjectName.text = lesson.displaySubjectName ?: "" + classroomName.text = lesson.displayClassroom ?: "" + teacherName.text = lesson.displayTeacherName ?: "" + teamName.text = lesson.displayTeamName ?: "" + + if (!showTeachersNames) teacherName.visibility = View.GONE + + when (lesson.type) { + Lesson.TYPE_NORMAL -> { + } + Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> { + card.setCardBackgroundColor(Color.BLACK) + subjectName.setTextColor(Color.WHITE) + subjectName.text = lesson.displaySubjectName?.asStrikethroughSpannable() + ?: "" + } + else -> { + card.setCardBackgroundColor(0xff234158.toInt()) + subjectName.setTextColor(Color.WHITE) + subjectName.setTypeface(null, Typeface.BOLD_ITALIC) + } + } + + layout.isDrawingCacheEnabled = true + layout.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY)) + layout.layout(0, 0, layout.measuredWidth, layout.measuredHeight) + layout.buildDrawingCache(true) + + val itemBitmap = layout.drawingCache + canvas.drawBitmap(itemBitmap, null, Rect(left, top, left + blockWidth, top + blockHeight), paint) + } + + val textPaint = Paint().apply { + setARGB(255, 0, 0, 0) + textAlign = Paint.Align.CENTER + textSize = 30f + isAntiAlias = true + isFilterBitmap = true + isDither = true + } + + for (w in 0..maxWeekDay) { + val x = WIDTH_CONSTANT + w * WIDTH_WEEKDAY + w * WIDTH_SPACING + canvas.drawText(Week.getFullDayName(w), x + (WIDTH_WEEKDAY / 2f), heightProfileName + HEIGHT_CONSTANT / 2 + 10f, textPaint) + } + + if (showProfileName) { + textPaint.textSize = 50f + canvas.drawText("${app.profile.name} - plan lekcji, ${weekStart.formattedStringShort} - ${weekEnd.formattedStringShort}", (imageWidth + 20) / 2f, 80f, textPaint) + } + + textPaint.apply { + setARGB(128, 0, 0, 0) + textAlign = Paint.Align.RIGHT + textSize = 26f + typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC) + } + + val footerTextPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt() + canvas.drawText("Wygenerowano w aplikacji Szkolny.eu", imageWidth - 10f, imageHeight - footerTextPaintCenter - 10f, textPaint) + + textPaint.apply { + setARGB(255, 127, 127, 127) + textAlign = Paint.Align.CENTER + textSize = 16f + typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL) + } + + val textPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt() + + val linePaint = Paint().apply { + setARGB(255, 100, 100, 100) + style = Paint.Style.STROKE + pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f) + isAntiAlias = true + isFilterBitmap = true + isDither = true + } + + val minTimeInt = ((minTime!!.value / 10000) * 60) + ((minTime!!.value / 100) % 100) + + lessonRanges.forEach { (startTime, endTime) -> + listOf(startTime, endTime).forEach { value -> + val hour = value / 10000 + val minute = (value / 100) % 100 + val time = Time(hour, minute, 0) + + val firstOffset = time.inMinutes - minTimeInt // offset in minutes + val top = (heightProfileName + HEIGHT_CONSTANT + firstOffset * HEIGHT_MINUTE).toFloat() + + canvas.drawText(time.stringHM, WIDTH_CONSTANT / 2f, top - textPaintCenter, textPaint) + canvas.drawLine(WIDTH_CONSTANT.toFloat(), top, imageWidth.toFloat(), top, linePaint) + } + } + + val today = Date.getToday().stringY_m_d + val now = Time.getNow().stringH_M_S + + val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() } + val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png") + + try { + val fos = FileOutputStream(outputFile) + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos) + fos.close() + } catch (e: Exception) { + Log.e("SAVE_IMAGE", e.message, e) + return@withContext null + } + + val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile) + } else { + Uri.parse("file://" + outputFile.absolutePath) + } + uri + } + + progressDialog.dismiss() + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.timetable_generate_success_title) + .setMessage(R.string.timetable_generate_success_text) + .setPositiveButton(R.string.share) { dialog, _ -> + dialog.dismiss() + + val intent = Intent(Intent.ACTION_SEND) + intent.setDataAndType(null, "image/*") + intent.putExtra(Intent.EXTRA_STREAM, uri) + activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_intent))) + } + .setNegativeButton(R.string.open) { dialog, _ -> + dialog.dismiss() + + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(uri, "image/*") + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + activity.startActivity(intent) + } + .setNeutralButton(R.string.do_nothing) { dialog, _ -> dialog.dismiss() } + .show() + }} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt new file mode 100644 index 00000000..b384cb46 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/LessonDetailsDialog.kt @@ -0,0 +1,214 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2019-11-11. + */ + +package pl.szczodrzynski.edziennik.ui.dialogs.timetable + +import android.content.Intent +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Lesson +import pl.szczodrzynski.edziennik.data.db.full.LessonFull +import pl.szczodrzynski.edziennik.databinding.DialogLessonDetailsBinding +import pl.szczodrzynski.edziennik.onClick +import pl.szczodrzynski.edziennik.setText +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventDetailsDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventListAdapter +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.modules.timetable.TimetableFragment +import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.edziennik.utils.models.Week +import kotlin.coroutines.CoroutineContext + +class LessonDetailsDialog( + val activity: AppCompatActivity, + val lesson: LessonFull, + val onShowListener: ((tag: String) -> Unit)? = null, + val onDismissListener: ((tag: String) -> Unit)? = null +) : CoroutineScope { + companion object { + private const val TAG = "LessonDetailsDialog" + } + + private lateinit var app: App + private lateinit var b: DialogLessonDetailsBinding + private lateinit var dialog: AlertDialog + + private val job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private lateinit var adapter: EventListAdapter + private val manager by lazy { app.timetableManager } + + init { run { + if (activity.isFinishing) + return@run + onShowListener?.invoke(TAG) + app = activity.applicationContext as App + b = DialogLessonDetailsBinding.inflate(activity.layoutInflater) + dialog = MaterialAlertDialogBuilder(activity) + .setView(b.root) + .setPositiveButton(R.string.close) { dialog, _ -> + dialog.dismiss() + } + .setNeutralButton(R.string.add, null) + .setOnDismissListener { + onDismissListener?.invoke(TAG) + } + .show() + + dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.onClick { + EventManualDialog( + activity, + lesson.profileId, + defaultLesson = lesson, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + + if (App.devMode) + b.lessonId.visibility = View.VISIBLE + + update() + }} + + private fun update() { + b.lesson = lesson + val lessonDate = lesson.displayDate ?: return + val lessonTime = lesson.displayStartTime ?: return + b.lessonDate.text = Week.getFullDayName(lessonDate.weekDay) + ", " + lessonDate.formattedString + + b.annotationVisible = manager.getAnnotation(activity, lesson, b.annotation) + + if (lesson.type >= Lesson.TYPE_SHIFTED_SOURCE) { + b.shiftedLayout.visibility = View.VISIBLE + var otherLessonDate: Date? = null + when (lesson.type) { + Lesson.TYPE_SHIFTED_SOURCE -> { + otherLessonDate = lesson.date + when { + lesson.date != lesson.oldDate -> b.shiftedText.setText( + R.string.timetable_lesson_shifted_other_day, + lesson.date?.stringY_m_d ?: "?", + lesson.startTime?.stringHM ?: "?" + ) + lesson.startTime != lesson.oldStartTime -> b.shiftedText.setText( + R.string.timetable_lesson_shifted_same_day, + lesson.startTime?.stringHM ?: "?" + ) + else -> b.shiftedText.setText(R.string.timetable_lesson_shifted) + } + } + Lesson.TYPE_SHIFTED_TARGET -> { + otherLessonDate = lesson.oldDate + when { + lesson.date != lesson.oldDate -> b.shiftedText.setText( + R.string.timetable_lesson_shifted_from_other_day, + lesson.oldDate?.stringY_m_d ?: "?", + lesson.oldStartTime?.stringHM ?: "?" + ) + lesson.startTime != lesson.oldStartTime -> b.shiftedText.setText( + R.string.timetable_lesson_shifted_from_same_day, + lesson.oldStartTime?.stringHM ?: "?" + ) + else -> b.shiftedText.setText(R.string.timetable_lesson_shifted_from) + } + } + } + b.shiftedGoTo.setOnClickListener { + dialog.dismiss() + val dateStr = otherLessonDate?.stringY_m_d ?: return@setOnClickListener + val intent = Intent(TimetableFragment.ACTION_SCROLL_TO_DATE).apply { + putExtra("timetableDate", dateStr) + } + activity.sendBroadcast(intent) + } + } + else { + b.shiftedLayout.visibility = View.GONE + } + + if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldSubjectId != null && lesson.subjectId != lesson.oldSubjectId) { + b.oldSubjectName = lesson.oldSubjectName + } + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displaySubjectId != null) { + b.subjectName = lesson.subjectName + } + + if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeacherId != null && lesson.teacherId != lesson.oldTeacherId) { + b.oldTeacherName = lesson.oldTeacherName + } + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeacherId != null) { + b.teacherName = lesson.teacherName + } + + if (lesson.oldClassroom != null && lesson.classroom != lesson.oldClassroom) { + b.oldClassroom = lesson.oldClassroom + } + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayClassroom != null) { + b.classroom = lesson.classroom + } + + if (lesson.type < Lesson.TYPE_SHIFTED_SOURCE && lesson.oldTeamId != null && lesson.teamId != lesson.oldTeamId) { + b.oldTeamName = lesson.oldTeamName + } + if (lesson.type != Lesson.TYPE_CANCELLED && lesson.displayTeamId != null) { + b.teamName = lesson.teamName + } + + adapter = EventListAdapter( + activity, + onItemClick = { + EventDetailsDialog( + activity, + it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + }, + onEventEditClick = { + EventManualDialog( + activity, + it.profileId, + editingEvent = it, + onShowListener = onShowListener, + onDismissListener = onDismissListener + ) + } + ) + + app.db.eventDao().getAllByDateTime(lesson.profileId, lessonDate, lessonTime).observe(activity, Observer { events -> + adapter.items = events + if (b.eventsView.adapter == null) { + b.eventsView.adapter = adapter + b.eventsView.apply { + isNestedScrollingEnabled = false + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + addItemDecoration(SimpleDividerItemDecoration(context)) + } + } + adapter.notifyDataSetChanged() + + if (events != null && events.isNotEmpty()) { + b.eventsView.visibility = View.VISIBLE + b.eventsNoData.visibility = View.GONE + } else { + b.eventsView.visibility = View.GONE + b.eventsNoData.visibility = View.VISIBLE + } + }) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt new file mode 100644 index 00000000..9ee93be9 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/AgendaFragment.kt @@ -0,0 +1,305 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-25 + */ + +package pl.szczodrzynski.edziennik.ui.modules.agenda + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import com.applandeo.materialcalendarview.EventDay +import com.github.tibolte.agendacalendarview.CalendarPickerController +import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent +import com.github.tibolte.agendacalendarview.models.CalendarEvent +import com.github.tibolte.agendacalendarview.models.IDayItem +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 com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial.Icon2 +import com.mikepenz.iconics.typeface.library.szkolny.font.SzkolnyFont +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.MainActivity +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.db.entity.Metadata +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.databinding.FragmentAgendaCalendarBinding +import pl.szczodrzynski.edziennik.databinding.FragmentAgendaDefaultBinding +import pl.szczodrzynski.edziennik.ui.dialogs.day.DayDialog +import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog +import pl.szczodrzynski.edziennik.ui.dialogs.lessonchange.LessonChangeDialog +import pl.szczodrzynski.edziennik.ui.dialogs.teacherabsence.TeacherAbsenceDialog +import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeCounter +import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEvent +import pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange.LessonChangeEventRenderer +import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceCounter +import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEvent +import pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence.TeacherAbsenceEventRenderer +import pl.szczodrzynski.edziennik.utils.Colors +import pl.szczodrzynski.edziennik.utils.Themes +import pl.szczodrzynski.edziennik.utils.models.Date +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem +import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetSeparatorItem +import java.util.* +import kotlin.coroutines.CoroutineContext + +class AgendaFragment : Fragment(), CoroutineScope { + + private lateinit var activity: MainActivity + private lateinit var b: ViewDataBinding + + private val app by lazy { activity.app } + + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private var type: Int = Profile.AGENDA_DEFAULT + private var actualDate: Date? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + if (getActivity() == null || context == null) return null + activity = getActivity() as MainActivity + context?.theme?.applyStyle(Themes.appTheme, true) + type = app.config.forProfile().ui.agendaViewType + b = when (type) { + Profile.AGENDA_DEFAULT -> FragmentAgendaDefaultBinding.inflate(inflater, container, false) + Profile.AGENDA_CALENDAR -> FragmentAgendaCalendarBinding.inflate(inflater, container, false) + else -> return null + } + return b.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!isAdded) return + + activity.bottomSheet.prependItems( + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_add_event) + .withDescription(R.string.menu_add_event_desc) + .withIcon(SzkolnyFont.Icon.szf_calendar_plus_outline) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + EventManualDialog(activity, app.profileId, defaultDate = actualDate) + }), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_agenda_change_view) + .withIcon(if (type == Profile.AGENDA_DEFAULT) CommunityMaterial.Icon.cmd_calendar_outline else CommunityMaterial.Icon.cmd_format_list_bulleted_square) + .withOnClickListener(View.OnClickListener { + activity.bottomSheet.close() + type = if (type == Profile.AGENDA_DEFAULT) Profile.AGENDA_CALENDAR else Profile.AGENDA_DEFAULT + app.config.forProfile().ui.agendaViewType = type + activity.reloadTarget() + }), + BottomSheetSeparatorItem(true), + BottomSheetPrimaryItem(true) + .withTitle(R.string.menu_mark_as_read) + .withIcon(CommunityMaterial.Icon.cmd_eye_check_outline) + .withOnClickListener(View.OnClickListener { launch { + activity.bottomSheet.close() + withContext(Dispatchers.Default) { + App.db.metadataDao().setAllSeen(app.profileId, Metadata.TYPE_EVENT, true) + } + Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show() + }}) + ) + + activity.navView.bottomBar.fabEnable = true + activity.navView.bottomBar.fabExtendedText = getString(R.string.add) + activity.navView.bottomBar.fabIcon = Icon2.cmd_plus + activity.navView.setFabOnClickListener(View.OnClickListener { + EventManualDialog(activity, app.profileId, defaultDate = actualDate) + }) + + activity.gainAttention() + activity.gainAttentionFAB() + + when (type) { + Profile.AGENDA_DEFAULT -> createDefaultAgendaView() + Profile.AGENDA_CALENDAR -> createCalendarAgendaView() + } + } + + private fun createDefaultAgendaView() { (b as? FragmentAgendaDefaultBinding)?.let { b -> launch { + if (!isAdded) + return@launch + delay(500) + + val eventList = mutableListOf() + + val minDate = Calendar.getInstance().apply { + add(Calendar.MONTH, -2) + set(Calendar.DAY_OF_MONTH, 1) + } + val maxDate = Calendar.getInstance().apply { add(Calendar.MONTH, 2) } + + /** + * LESSON CHANGES + */ + if (!isAdded) + return@launch + + val lessons = withContext(Dispatchers.Default) { app.db.timetableDao().getAllChangesNow(app.profileId) } + val lessonChangeCounters = mutableListOf() + + lessons.forEach { lesson -> + lessonChangeCounters.firstOrNull { it.lessonChangeDate == lesson.displayDate }?.let { + it.lessonChangeCount += 1 + } ?: run { + lessonChangeCounters.add(LessonChangeCounter( + lesson.displayDate ?: return@forEach, + 1 + )) + } + } + + lessonChangeCounters.forEach { counter -> + eventList.add(LessonChangeEvent( + counter.lessonChangeDate.inMillis, + 0xff78909c.toInt(), + Colors.legibleTextColor(0xff78909c.toInt()), + counter.startTime, + counter.endTime, + app.profileId, + counter.lessonChangeDate, + counter.lessonChangeCount + )) + } + + /** + * TEACHER ABSENCES + */ + if (!isAdded) + return@launch + + val showTeacherAbsences = app.profile.getStudentData("showTeacherAbsences", true) + + if (showTeacherAbsences) { + val teacherAbsenceList = withContext(Dispatchers.Default) { app.db.teacherAbsenceDao().getAllFullNow(app.profileId) } + val teacherAbsenceCounters = mutableListOf() + + teacherAbsenceList.forEach { absence -> + val date = absence.dateFrom.clone() + + while (date <= absence.dateTo) { + teacherAbsenceCounters.firstOrNull { it.teacherAbsenceDate == date }?.let { + it.teacherAbsenceCount += 1 + } ?: run { + teacherAbsenceCounters.add(TeacherAbsenceCounter(date.clone(), 1)) + } + + date.stepForward(0, 0, 1) + } + } + + teacherAbsenceCounters.forEach { counter -> + eventList.add(TeacherAbsenceEvent( + counter.teacherAbsenceDate.inMillis, + 0xffff1744.toInt(), + Colors.legibleTextColor(0xffff1744.toInt()), + counter.startTime, + counter.endTime, + app.profileId, + counter.teacherAbsenceDate, + counter.teacherAbsenceCount + )) + } + } + + /** + * EVENTS + */ + if (!isAdded) + return@launch + + val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) } + val unreadEventDates = mutableSetOf() + + events.forEach { event -> + eventList.add(BaseCalendarEvent( + "${event.typeName ?: "wydarzenie"} - ${event.topic}", + "", + (if (event.time == null) getString(R.string.agenda_event_all_day) else event.time!!.stringHM) + + (event.subjectLongName?.let { ", $it" } ?: "") + + (event.teacherName?.let { ", $it" } ?: "") + + (event.teamName?.let { ", $it" } ?: ""), + event.eventColor, + Colors.legibleTextColor(event.eventColor), + event.startTimeCalendar, + event.endTimeCalendar, + event.time == null, + event.id, + !event.seen + )) + + if (!event.seen) unreadEventDates.add(event.date.value) + } + + b.agendaDefaultView.init(eventList, minDate, maxDate, Locale.getDefault(), object : CalendarPickerController { + override fun onDaySelected(dayItem: IDayItem?) {} + + override fun onScrollToDate(calendar: Calendar) { this@AgendaFragment.launch { + val date = Date.fromCalendar(calendar) + actualDate = date + + // Mark as read scrolled date + if (date.value in unreadEventDates) { + withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) } + unreadEventDates.remove(date.value) + } + }} + + override fun onEventSelected(event: CalendarEvent) { + val date = Date.fromCalendar(event.instanceDay) + + when (event) { + is BaseCalendarEvent -> DayDialog(activity, app.profileId, date) + is LessonChangeEvent -> LessonChangeDialog(activity, app.profileId, date) + is TeacherAbsenceEvent -> TeacherAbsenceDialog(activity, app.profileId, date) + } + } + + }, LessonChangeEventRenderer(), TeacherAbsenceEventRenderer()) + + b.progressBar.visibility = View.GONE + }}} + + private fun createCalendarAgendaView() { (b as? FragmentAgendaCalendarBinding)?.let { b -> launch { + delay(300) + + val dayList = mutableListOf() + + val events = withContext(Dispatchers.Default) { app.db.eventDao().getAllNow(app.profileId) } + val unreadEventDates = mutableSetOf() + + events.forEach { event -> + val eventIcon = IconicsDrawable(activity) + .icon(CommunityMaterial.Icon.cmd_checkbox_blank_circle) + .size(IconicsSize.dp(10)) + .color(IconicsColor.colorInt(event.eventColor)) + + dayList.add(EventDay(event.startTimeCalendar, eventIcon)) + + if (!event.seen) unreadEventDates.add(event.date.value) + } + + b.agendaCalendarView.setEvents(dayList) + b.agendaCalendarView.setOnDayClickListener { day -> this@AgendaFragment.launch { + val date = Date.fromCalendar(day.calendar) + + if (date.value in unreadEventDates) { + withContext(Dispatchers.Default) { app.db.eventDao().setSeenByDate(app.profileId, date, true) } + unreadEventDates.remove(date.value) + } + + DayDialog(activity, app.profileId, date) + }} + + b.progressBar.visibility = View.GONE + }}} +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt new file mode 100644 index 00000000..2b264ede --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeCounter.kt @@ -0,0 +1,19 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange + +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class LessonChangeCounter( + val lessonChangeDate: Date, + var lessonChangeCount: Int +) { + val startTime: Calendar + get() = Calendar.getInstance().apply { + set(lessonChangeDate.year, lessonChangeDate.month - 1, lessonChangeDate.day, 10, 0, 0) + } + + val endTime: Calendar + get() = Calendar.getInstance().apply { + timeInMillis = startTime.timeInMillis + (45 * 60 * 1000) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/fragments/agenda/LessonChangeEvent.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java similarity index 96% rename from app/src/main/java/pl/szczodrzynski/edziennik/fragments/agenda/LessonChangeEvent.java rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java index 0d92e6cd..077c2206 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/fragments/agenda/LessonChangeEvent.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEvent.java @@ -1,13 +1,12 @@ -package pl.szczodrzynski.edziennik.fragments.agenda; +package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange; -import com.github.tibolte.agendacalendarview.models.BaseCalendarEvent; import com.github.tibolte.agendacalendarview.models.CalendarEvent; import com.github.tibolte.agendacalendarview.models.IDayItem; import com.github.tibolte.agendacalendarview.models.IWeekItem; import java.util.Calendar; -import pl.szczodrzynski.edziennik.models.Date; +import pl.szczodrzynski.edziennik.utils.models.Date; public class LessonChangeEvent implements CalendarEvent { diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt new file mode 100644 index 00000000..e2306039 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/lessonchange/LessonChangeEventRenderer.kt @@ -0,0 +1,21 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.lessonchange + +import android.view.View +import android.widget.TextView +import androidx.cardview.widget.CardView +import com.github.tibolte.agendacalendarview.render.EventRenderer +import pl.szczodrzynski.edziennik.R + +class LessonChangeEventRenderer : EventRenderer() { + override fun render(view: View?, event: LessonChangeEvent) { + val card = view?.findViewById(R.id.lesson_change_card) + val changeText = view?.findViewById(R.id.lesson_change_text) + val changeCount = view?.findViewById(R.id.lessonChangeCount) + card?.setCardBackgroundColor(event.color) + changeText?.setTextColor(event.textColor) + changeCount?.setTextColor(event.textColor) + changeCount?.text = event.lessonChangeCount.toString() + } + + override fun getEventLayout(): Int = R.layout.agenda_event_lesson_change +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt new file mode 100644 index 00000000..71dea025 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceCounter.kt @@ -0,0 +1,19 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence + +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class TeacherAbsenceCounter ( + val teacherAbsenceDate: Date, + var teacherAbsenceCount: Int = 0 +) { + val startTime: Calendar + get() = Calendar.getInstance().apply { + set(teacherAbsenceDate.year, teacherAbsenceDate.month - 1, teacherAbsenceDate.day, 10, 0, 0) + } + + val endTime: Calendar + get() = Calendar.getInstance().apply { + timeInMillis = startTime.timeInMillis + (45 * 60 * 1000) + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt new file mode 100644 index 00000000..fc1c28db --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEvent.kt @@ -0,0 +1,188 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence + +import com.github.tibolte.agendacalendarview.models.CalendarEvent +import com.github.tibolte.agendacalendarview.models.IDayItem +import com.github.tibolte.agendacalendarview.models.IWeekItem +import pl.szczodrzynski.edziennik.utils.models.Date +import java.util.* + +class TeacherAbsenceEvent : CalendarEvent { + /** + * Id of the event. + */ + private var mId: Long = 0 + /** + * Color to be displayed in the agenda view. + */ + private var mColor: Int = 0 + /** + * Text color displayed on the background color + */ + private var mTextColor: Int = 0 + /** + * Calendar instance helping sorting the events per section in the agenda view. + */ + private var mInstanceDay: Calendar? = null + /** + * Start time of the event. + */ + private var mStartTime: Calendar? = null + /** + * End time of the event. + */ + private var mEndTime: Calendar? = null + /** + * References to a DayItem instance for that event, used to link interaction between the + * calendar view and the agenda view. + */ + private var mDayReference: IDayItem? = null + /** + * References to a WeekItem instance for that event, used to link interaction between the + * calendar view and the agenda view. + */ + private var mWeekReference: IWeekItem? = null + + + private var profileId: Int = 0 + var teacherAbsenceDate: Date? = null + var teacherAbsenceCount: Int = 0 + + constructor(calendarEvent: TeacherAbsenceEvent) { + this.mId = calendarEvent.id + this.mColor = calendarEvent.color + this.mTextColor = calendarEvent.textColor + this.mStartTime = calendarEvent.startTime + this.mEndTime = calendarEvent.endTime + this.profileId = calendarEvent.profileId + this.teacherAbsenceDate = calendarEvent.teacherAbsenceDate + this.teacherAbsenceCount = calendarEvent.teacherAbsenceCount + } + + constructor(mId: Long, mColor: Int, mTextColor: Int, mStartTime: Calendar, mEndTime: Calendar, profileId: Int, teacherAbsenceDate: Date, teacherAbsenceCount: Int) { + this.mId = mId + this.mColor = mColor + this.mTextColor = mTextColor + this.mStartTime = mStartTime + this.mEndTime = mEndTime + this.profileId = profileId + this.teacherAbsenceDate = teacherAbsenceDate + this.teacherAbsenceCount = teacherAbsenceCount + } + + override fun setPlaceholder(placeholder: Boolean) { + + } + + override fun isPlaceholder(): Boolean { + return false + } + + override fun getLocation(): String? { + return null + } + + override fun setLocation(mLocation: String) { + + } + + override fun getId(): Long { + return mId + } + + override fun setId(mId: Long) { + this.mId = mId + } + + override fun getShowBadge(): Boolean { + return false + } + + override fun setShowBadge(mShowBadge: Boolean) { + + } + + override fun getTextColor(): Int { + return mTextColor + } + + override fun setTextColor(mTextColor: Int) { + this.mTextColor = mTextColor + } + + override fun getDescription(): String? { + return null + } + + override fun setDescription(mDescription: String) { + + } + + override fun isAllDay(): Boolean { + return false + } + + override fun setAllDay(allDay: Boolean) { + + } + + override fun getStartTime(): Calendar? { + return mStartTime + } + + override fun setStartTime(mStartTime: Calendar) { + this.mStartTime = mStartTime + } + + override fun getEndTime(): Calendar? { + return mEndTime + } + + override fun setEndTime(mEndTime: Calendar) { + this.mEndTime = mEndTime + } + + override fun getTitle(): String? { + return null + } + + override fun setTitle(mTitle: String) { + + } + + override fun getInstanceDay(): Calendar? { + return mInstanceDay + } + + override fun setInstanceDay(mInstanceDay: Calendar) { + this.mInstanceDay = mInstanceDay + this.mInstanceDay!!.set(Calendar.HOUR, 0) + this.mInstanceDay!!.set(Calendar.MINUTE, 0) + this.mInstanceDay!!.set(Calendar.SECOND, 0) + this.mInstanceDay!!.set(Calendar.MILLISECOND, 0) + this.mInstanceDay!!.set(Calendar.AM_PM, 0) + } + + override fun getDayReference(): IDayItem? { + return mDayReference + } + + override fun setDayReference(mDayReference: IDayItem) { + this.mDayReference = mDayReference + } + + override fun getWeekReference(): IWeekItem? { + return mWeekReference + } + + override fun setWeekReference(mWeekReference: IWeekItem) { + this.mWeekReference = mWeekReference + } + + override fun copy(): CalendarEvent { + return TeacherAbsenceEvent(this) + } + + override fun getColor(): Int { + return mColor + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt new file mode 100644 index 00000000..b437769b --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/agenda/teacherabsence/TeacherAbsenceEventRenderer.kt @@ -0,0 +1,21 @@ +package pl.szczodrzynski.edziennik.ui.modules.agenda.teacherabsence + +import android.view.View +import android.widget.TextView +import androidx.cardview.widget.CardView +import com.github.tibolte.agendacalendarview.render.EventRenderer +import pl.szczodrzynski.edziennik.R + +class TeacherAbsenceEventRenderer : EventRenderer() { + override fun render(view: View?, event: TeacherAbsenceEvent) { + val card = view?.findViewById(R.id.teacherAbsenceCard) + val changeText = view?.findViewById(R.id.teacherAbsenceText) + val changeCount = view?.findViewById(R.id.teacherAbsenceCount) + card?.setCardBackgroundColor(event.color) + changeText?.setTextColor(event.textColor) + changeCount?.setTextColor(event.textColor) + changeCount?.text = event.teacherAbsenceCount.toString() + } + + override fun getEventLayout(): Int = R.layout.agenda_event_teacher_absence +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/AnnouncementsAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java similarity index 79% rename from app/src/main/java/pl/szczodrzynski/edziennik/adapters/AnnouncementsAdapter.java rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java index 26df42c9..7f9b600d 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/AnnouncementsAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/announcements/AnnouncementsAdapter.java @@ -1,6 +1,7 @@ -package pl.szczodrzynski.edziennik.adapters; +package pl.szczodrzynski.edziennik.ui.modules.announcements; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Typeface; @@ -8,14 +9,16 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import java.util.List; - import androidx.annotation.NonNull; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + import pl.szczodrzynski.edziennik.R; +import pl.szczodrzynski.edziennik.data.db.full.AnnouncementFull; import pl.szczodrzynski.edziennik.databinding.RowAnnouncementsItemBinding; -import pl.szczodrzynski.edziennik.datamodels.AnnouncementFull; +import pl.szczodrzynski.edziennik.ui.modules.messages.MessagesUtils; public class AnnouncementsAdapter extends RecyclerView.Adapter { @@ -51,7 +54,7 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter { + b.announcementsItem.setOnClickListener((v -> { if (onClick != null) { onClick.onClick(v, item); } @@ -59,7 +62,13 @@ public class AnnouncementsAdapter extends RecyclerView.Adapter { + activity.getBottomSheet().close(); + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS) { + EdziennikTask.Companion.announcementsRead(App.Companion.getProfileId()).enqueue(requireContext()); + } else { + AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ANNOUNCEMENT, true)); + Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); + } + }) + ); + + /*b.refreshLayout.setOnRefreshListener(() -> { + activity.syncCurrentFeature(MainActivity.DRAWER_ITEM_ANNOUNCEMENTS, b.refreshLayout); + });*/ + + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); + + //RecyclerViewExpandableItemManager expMgr = new RecyclerViewExpandableItemManager(null); + + recyclerView = b.announcementsView; + recyclerView.setHasFixedSize(true); + recyclerView.setLayoutManager(linearLayoutManager); + recyclerView.addItemDecoration(new SimpleDividerItemDecoration(view.getContext())); + + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (recyclerView.canScrollVertically(-1)) { + b.refreshLayout.setEnabled(false); + } + if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) { + b.refreshLayout.setEnabled(true); + } + } + }); + + app.db.announcementDao().getAll(App.Companion.getProfileId()).observe(this, announcements -> { + if (app == null || activity == null || b == null || !isAdded()) + return; + + if (announcements == null) { + recyclerView.setVisibility(View.GONE); + b.announcementsNoData.setVisibility(View.VISIBLE); + return; + } + + if (announcements.size() > 0) { + /*if ((adapter = (AnnouncementsAdapter) recyclerView.getAdapter()) != null) { + adapter.announcementList = announcements; + adapter.notifyDataSetChanged(); + return; + }*/ + AnnouncementsAdapter announcementsAdapter = new AnnouncementsAdapter(activity, announcements, (v, announcement) -> { + if (announcement.text == null || (app.getProfile().getLoginStoreType() == LOGIN_TYPE_LIBRUS && !announcement.seen && app.getNetworkUtils().isOnline())) { + EdziennikTask.Companion.announcementGet(App.Companion.getProfileId(), announcement).enqueue(requireContext()); + } else { + showAnnouncementDetailsDialog(announcement); + } + }); + + recyclerView.setAdapter(announcementsAdapter); + // NOTE: need to disable change animations to ripple effect work properly + //((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); + //expMgr.attachRecyclerView(recyclerView); + + recyclerView.setVisibility(View.VISIBLE); + b.announcementsNoData.setVisibility(View.GONE); + } + else { + recyclerView.setVisibility(View.GONE); + b.announcementsNoData.setVisibility(View.VISIBLE); + } + }); + } + + @Override + public void onStart() { + EventBus.getDefault().register(this); + super.onStart(); + } + + @Override + public void onStop() { + EventBus.getDefault().unregister(this); + super.onStop(); + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + public void onAnnouncementGetEvent(AnnouncementGetEvent event) { + EventBus.getDefault().removeStickyEvent(event); + showAnnouncementDetailsDialog(event.getAnnouncement()); + } + + private void showAnnouncementDetailsDialog(AnnouncementFull announcement) { + MaterialDialog dialog = new MaterialDialog.Builder(activity) + .title(announcement.subject) + .customView(R.layout.dialog_announcement, true) + .positiveText(R.string.ok) + .show(); + DialogAnnouncementBinding b = DialogAnnouncementBinding.bind(dialog.getCustomView()); + b.text.setText(announcement.teacherFullName+"\n\n"+ (announcement.startDate != null ? announcement.startDate.getFormattedString() : "-") + (announcement.endDate != null ? " do " + announcement.endDate.getFormattedString() : "")+"\n\n" +announcement.text); + if (!announcement.seen && app.getProfile().getLoginStoreType() != LOGIN_TYPE_LIBRUS) { + announcement.seen = true; + AsyncTask.execute(() -> App.db.metadataDao().setSeen(App.Companion.getProfileId(), announcement, true)); + if (recyclerView.getAdapter() != null) + recyclerView.getAdapter().notifyDataSetChanged(); + } + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/AttendancesAdapter.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java similarity index 80% rename from app/src/main/java/pl/szczodrzynski/edziennik/adapters/AttendancesAdapter.java rename to app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java index 79793f93..7b057395 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/adapters/AttendancesAdapter.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/attendance/AttendanceAdapter.java @@ -1,35 +1,37 @@ -package pl.szczodrzynski.edziennik.adapters; +package pl.szczodrzynski.edziennik.ui.modules.attendance; import android.content.Context; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.os.AsyncTask; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + import java.util.List; import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.datamodels.AttendanceFull; +import pl.szczodrzynski.edziennik.data.db.full.AttendanceFull; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_ABSENT_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_BELATED_EXCUSED; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_PRESENT; -import static pl.szczodrzynski.edziennik.datamodels.Attendance.TYPE_RELEASED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_ABSENT_EXCUSED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_BELATED_EXCUSED; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_DAY_FREE; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_PRESENT; +import static pl.szczodrzynski.edziennik.data.db.entity.Attendance.TYPE_RELEASED; -public class AttendancesAdapter extends RecyclerView.Adapter { +public class AttendanceAdapter extends RecyclerView.Adapter { private Context context; public List attendanceList; //getting the context and product list with constructor - public AttendancesAdapter(Context mCtx, List noticeList) { + public AttendanceAdapter(Context mCtx, List noticeList) { this.context = mCtx; this.attendanceList = noticeList; } @@ -56,6 +58,10 @@ public class AttendancesAdapter extends RecyclerView.Adapter { - app.db.metadataDao().setSeen(App.profileId, attendance, true); + App.db.metadataDao().setSeen(App.Companion.getProfileId(), attendance, true); //Intent i = new Intent("android.intent.action.MAIN").putExtra(MainActivity.ACTION_UPDATE_BADGES, "yes, sure"); //context.sendBroadcast(i); }); @@ -125,4 +131,4 @@ public class AttendancesAdapter extends RecyclerView.Adapter { activity.getBottomSheet().close(); - AsyncTask.execute(() -> app.db.metadataDao().setAllSeen(App.profileId, TYPE_ATTENDANCE, true)); + AsyncTask.execute(() -> App.db.metadataDao().setAllSeen(App.Companion.getProfileId(), TYPE_ATTENDANCE, true)); Toast.makeText(activity, R.string.main_menu_mark_as_read_success, Toast.LENGTH_SHORT).show(); }) ); /*b.refreshLayout.setOnRefreshListener(() -> { - activity.syncCurrentFeature(MainActivity.DRAWER_ITEM_ATTENDANCES, b.refreshLayout); + activity.syncCurrentFeature(MainActivity.DRAWER_ITEM_ATTENDANCE, b.refreshLayout); });*/ b.attendancePercentage.setProgressTextAdapter(PERCENTAGE_ADAPTER); b.attendancePercentage.setMaxProgress(100.0f); - b.attendancesSummaryTitle.setOnClickListener((v -> { - PopupMenu popupMenu = new PopupMenu(activity, b.attendancesSummaryTitle, Gravity.END); + b.attendanceSummaryTitle.setOnClickListener((v -> { + PopupMenu popupMenu = new PopupMenu(activity, b.attendanceSummaryTitle, Gravity.END); popupMenu.getMenu().add(0, 0, 0, R.string.summary_mode_year); popupMenu.getMenu().add(0, 1, 1, R.string.summary_mode_semester_1); popupMenu.getMenu().add(0, 2, 2, R.string.summary_mode_semester_2); @@ -126,17 +117,17 @@ public class RegisterAttendancesFragment extends Fragment { popupMenu.show(); })); - if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK) { - long attendancesLastSync = app.profile.getStudentData("attendancesLastSync", (long)0); - if (attendancesLastSync == 0) { - attendancesLastSync = app.profile.getSemesterStart(1).getInMillis(); + /*if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK) { + long attendanceLastSync = app.profile.getStudentData("attendanceLastSync", (long)0); + if (attendanceLastSync == 0) { + attendanceLastSync = app.profile.getSemesterStart(1).getInMillis(); } - Date lastSyncDate = Date.fromMillis(attendancesLastSync); + Date lastSyncDate = Date.fromMillis(attendanceLastSync); if (lastSyncDate.getValue() < Week.getWeekStart().getValue()) { CafeBar.builder(activity) .to(activity.getNavView().getCoordinator()) .content(R.string.sync_old_data_info) - .icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon2.cmd_sync).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity)))) + .icon(new IconicsDrawable(activity).icon(CommunityMaterial.Icon.cmd_download_outline).size(IconicsSize.dp(20)).color(IconicsColor.colorInt(Themes.INSTANCE.getPrimaryTextColor(activity)))) .positiveText(R.string.refresh) .positiveColor(0xff4caf50) .negativeText(R.string.ok) @@ -156,57 +147,65 @@ public class RegisterAttendancesFragment extends Fragment { .floating(true) .show(); } - } + }*/ - if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && false) { - b.attendancesSummarySubject.setVisibility(View.GONE); - } - else { - b.attendancesSummarySubject.setOnClickListener((v -> { - AsyncTask.execute(() -> { - List subjectList = app.db.subjectDao().getAllNow(App.profileId); - PopupMenu popupMenu = new PopupMenu(activity, b.attendancesSummarySubject, Gravity.END); - popupMenu.getMenu().add(0, -1, 0, R.string.subject_filter_disabled); - int index = 0; - DecimalFormat format = new DecimalFormat("0.00"); - for (Subject subject: subjectList) { - int total = subjectTotalCount.get(subject.id, new int[3])[displayMode]; - int absent = subjectAbsentCount.get(subject.id, new int[3])[displayMode]; - if (total == 0) - continue; - int present = total - absent; - float percentage = (float)present / (float)total * 100.0f; - String percentageStr = format.format(percentage); - popupMenu.getMenu().add(0, (int)subject.id, index++, getString(R.string.subject_filter_format, subject.longName, percentageStr)); - } - popupMenu.setOnMenuItemClickListener((item -> { - subjectIdFilter = item.getItemId(); - b.attendancesSummarySubject.setText(item.getTitle().toString().replaceAll("\\s-\\s[0-9]{1,2}\\.[0-9]{1,2}%", "")); - updateList(); - return true; - })); - new Handler(activity.getMainLooper()).post(popupMenu::show); - }); + b.attendanceSummarySubject.setOnClickListener((v -> { + AsyncTask.execute(() -> { + List subjectList = App.db.subjectDao().getAllNow(App.Companion.getProfileId()); + PopupMenu popupMenu = new PopupMenu(activity, b.attendanceSummarySubject, Gravity.END); + popupMenu.getMenu().add(0, -1, 0, R.string.subject_filter_disabled); + int index = 0; + DecimalFormat format = new DecimalFormat("0.00"); + for (Subject subject: subjectList) { + int total = subjectTotalCount.get(subject.id, new int[3])[displayMode]; + int absent = subjectAbsentCount.get(subject.id, new int[3])[displayMode]; + if (total == 0) + continue; + int present = total - absent; + float percentage = (float)present / (float)total * 100.0f; + String percentageStr = format.format(percentage); + popupMenu.getMenu().add(0, (int)subject.id, index++, getString(R.string.subject_filter_format, subject.longName, percentageStr)); + } + popupMenu.setOnMenuItemClickListener((item -> { + subjectIdFilter = item.getItemId(); + b.attendanceSummarySubject.setText(item.getTitle().toString().replaceAll("\\s-\\s[0-9]{1,2}\\.[0-9]{1,2}%", "")); + updateList(); + return true; + })); + new Handler(activity.getMainLooper()).post(popupMenu::show); + }); - })); - } + })); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); - b.attendancesView.setHasFixedSize(true); - b.attendancesView.setLayoutManager(linearLayoutManager); + b.attendanceView.setHasFixedSize(true); + b.attendanceView.setLayoutManager(linearLayoutManager); + b.attendanceView.addItemDecoration(new SimpleDividerItemDecoration(getContext())); - app.db.attendanceDao().getAll(App.profileId).observe(this, attendances -> { - if (app == null || app.profile == null || activity == null || b == null || !isAdded()) + b.attendanceView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + if (recyclerView.canScrollVertically(-1)) { + b.refreshLayout.setEnabled(false); + } + if (!recyclerView.canScrollVertically(-1) && newState == SCROLL_STATE_IDLE) { + b.refreshLayout.setEnabled(true); + } + } + }); + + App.db.attendanceDao().getAll(App.Companion.getProfileId()).observe(this, attendance -> { + if (app == null || activity == null || b == null || !isAdded()) return; - if (attendances == null) { - b.attendancesView.setVisibility(View.GONE); - b.attendancesNoData.setVisibility(View.VISIBLE); + if (attendance == null) { + b.attendanceView.setVisibility(View.GONE); + b.attendanceNoData.setVisibility(View.VISIBLE); return; } - attendanceList = attendances; + attendanceList = attendance; countSubjectStats(); @@ -218,7 +217,7 @@ public class RegisterAttendancesFragment extends Fragment { subjectTotalCount = new LongSparseArray<>(); subjectAbsentCount = new LongSparseArray<>(); for (AttendanceFull attendance: attendanceList) { - if (app.profile.getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED) + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN && attendance.type == TYPE_RELEASED) continue; int[] subjectTotal = subjectTotalCount.get(attendance.subjectId, new int[3]); int[] subjectAbsent = subjectAbsentCount.get(attendance.subjectId, new int[3]); @@ -237,7 +236,7 @@ public class RegisterAttendancesFragment extends Fragment { } private void updateList() { - if (app == null || app.profile == null || activity == null || b == null || !isAdded()) + if (app == null || activity == null || b == null || !isAdded()) return; int presentCount = 0; @@ -275,29 +274,29 @@ public class RegisterAttendancesFragment extends Fragment { } } if (filteredList.size() > 0) { - AttendancesAdapter adapter; - b.attendancesView.setVisibility(View.VISIBLE); - b.attendancesNoData.setVisibility(View.GONE); - if ((adapter = (AttendancesAdapter) b.attendancesView.getAdapter()) != null) { + AttendanceAdapter adapter; + b.attendanceView.setVisibility(View.VISIBLE); + b.attendanceNoData.setVisibility(View.GONE); + if ((adapter = (AttendanceAdapter) b.attendanceView.getAdapter()) != null) { adapter.attendanceList = filteredList; adapter.notifyDataSetChanged(); } else { - adapter = new AttendancesAdapter(getContext(), filteredList); - b.attendancesView.setAdapter(adapter); + adapter = new AttendanceAdapter(getContext(), filteredList); + b.attendanceView.setAdapter(adapter); } } else { - b.attendancesView.setVisibility(View.GONE); - b.attendancesNoData.setVisibility(View.VISIBLE); + b.attendanceView.setVisibility(View.GONE); + b.attendanceNoData.setVisibility(View.VISIBLE); } // SUMMARY if (displayMode == MODE_YEAR) { - b.attendancesSummaryTitle.setText(getString(R.string.attendances_summary_title_year)); + b.attendanceSummaryTitle.setText(getString(R.string.attendance_summary_title_year)); } else { - b.attendancesSummaryTitle.setText(getString(R.string.attendances_summary_title_semester_format, displayMode)); + b.attendanceSummaryTitle.setText(getString(R.string.attendance_summary_title_semester_format, displayMode)); } b.presentCountContainer.setVisibility(presentCount == 0 ? View.GONE : View.VISIBLE); b.presentCount.setText(String.format(Locale.getDefault(), "%d", presentCount)); @@ -315,10 +314,7 @@ public class RegisterAttendancesFragment extends Fragment { float attendancePercentage; // in Mobidziennik there are no TYPE_PRESENT records so we cannot calculate the percentage - if (app.profile.getLoginStoreType() == LOGIN_TYPE_MOBIDZIENNIK && false) { - attendancePercentage = app.profile.getAttendancePercentage(); - } - else if (app.profile.getLoginStoreType() == LOGIN_TYPE_VULCAN) { + if (app.getProfile().getLoginStoreType() == LOGIN_TYPE_VULCAN) { float allCount = presentCount + absentCount + belatedCount; // do not count releases float present = allCount - absentCount; attendancePercentage = present / allCount * 100.0f; diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt new file mode 100644 index 00000000..3ae649f6 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/base/CrashActivity.kt @@ -0,0 +1,174 @@ +package pl.szczodrzynski.edziennik.ui.modules.base + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.text.Html +import android.view.View +import android.widget.Button +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import cat.ereza.customactivityoncrash.CustomActivityOnCrash +import com.afollestad.materialdialogs.MaterialDialog +import kotlinx.coroutines.* +import pl.szczodrzynski.edziennik.App +import pl.szczodrzynski.edziennik.BuildConfig +import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.ERROR_APP_CRASH +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi +import pl.szczodrzynski.edziennik.data.api.szkolny.request.ErrorReportRequest +import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.utils.Themes.appTheme +import kotlin.coroutines.CoroutineContext + +/* + * Copyright 2014-2017 Eduard Ereza Martínez + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class CrashActivity : AppCompatActivity(), CoroutineScope { + companion object { + const val TAG = "CrashActivity" + } + + private val app by lazy { application as App } + private val api by lazy { SzkolnyApi(app) } + + private var job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setTheme(appTheme) + setContentView(R.layout.activity_crash) + val config = CustomActivityOnCrash.getConfigFromIntent(intent) + if (config == null) { //This should never happen - Just finish the activity to avoid a recursive crash. + finish() + return + } + + //Close/restart button logic: + //If a class if set, use restart. + //Else, use close and just finish the app. + //It is recommended that you follow this logic if implementing a custom error activity. + val restartButton = findViewById