diff --git a/.gitignore b/.gitignore index 345e61a..bd9cb87 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,11 @@ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: +local.properties .idea/**/workspace.xml .idea/**/tasks.xml .idea/dictionaries +app/build/ # Sensitive or high-churn files: .idea/**/dataSources/ @@ -47,3 +49,8 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties + +# Binary files +**.bin +**.lock +**.rawproto diff --git a/README.md b/README.md index bf78fb0..c47fc1e 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,8 @@ Java project; fall semester ## Not Dead by Deadline An app to help you organize your study life and not die by the time the deadline rolls around. + +### Features +* Adding new homeworks +* Displaying all current homeworks by subject +* Displaying all current/nearest deadlines diff --git a/aFileChooser/aFileChooser.iml b/aFileChooser/aFileChooser.iml new file mode 100644 index 0000000..0c05f7c --- /dev/null +++ b/aFileChooser/aFileChooser.iml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aFileChooser/build.gradle b/aFileChooser/build.gradle new file mode 100644 index 0000000..16bf6f8 --- /dev/null +++ b/aFileChooser/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 26 + + defaultConfig { + minSdkVersion 22 + targetSdkVersion 26 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + implementation 'com.android.support:support-v4:26.1.0' +} diff --git a/aFileChooser/src/main/AndroidManifest.xml b/aFileChooser/src/main/AndroidManifest.xml new file mode 100644 index 0000000..41d7e58 --- /dev/null +++ b/aFileChooser/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/aFileChooser/src/main/java/com/ianhanniballake/localstorage/LocalStorageProvider.java b/aFileChooser/src/main/java/com/ianhanniballake/localstorage/LocalStorageProvider.java new file mode 100644 index 0000000..8060bb8 --- /dev/null +++ b/aFileChooser/src/main/java/com/ianhanniballake/localstorage/LocalStorageProvider.java @@ -0,0 +1,234 @@ + +package com.ianhanniballake.localstorage; + +import android.content.res.AssetFileDescriptor; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.os.CancellationSignal; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; +import android.provider.DocumentsProvider; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import com.ipaulpro.afilechooser.R; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +public class LocalStorageProvider extends DocumentsProvider { + + // TODO can I fetch build variables from other modules? find out + public static final String AUTHORITY = "ru.spbau.group202.notdeadbydeadline"; + + /** + * Default root projection: everything but Root.COLUMN_MIME_TYPES + */ + private final static String[] DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, + Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_ICON, + Root.COLUMN_AVAILABLE_BYTES + }; + /** + * Default document projection: everything but Document.COLUMN_ICON and + * Document.COLUMN_SUMMARY + */ + private final static String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { + Document.COLUMN_DOCUMENT_ID, + Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS, Document.COLUMN_MIME_TYPE, + Document.COLUMN_SIZE, + Document.COLUMN_LAST_MODIFIED + }; + + @Override + public Cursor queryRoots(final String[] projection) throws FileNotFoundException { + // Create a cursor with either the requested fields, or the default + // projection if "projection" is null. + final MatrixCursor result = new MatrixCursor(projection != null ? projection + : DEFAULT_ROOT_PROJECTION); + // Add Home directory + File homeDir = Environment.getExternalStorageDirectory(); + final MatrixCursor.RowBuilder row = result.newRow(); + // These columns are required + row.add(Root.COLUMN_ROOT_ID, homeDir.getAbsolutePath()); + row.add(Root.COLUMN_DOCUMENT_ID, homeDir.getAbsolutePath()); + row.add(Root.COLUMN_TITLE, getContext().getString(R.string.internal_storage)); + row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE); + row.add(Root.COLUMN_ICON, R.drawable.ic_provider); + // These columns are optional + row.add(Root.COLUMN_AVAILABLE_BYTES, homeDir.getFreeSpace()); + // Root.COLUMN_MIME_TYPE is another optional column and useful if you + // have multiple roots with different + // types of mime types (roots that don't match the requested mime type + // are automatically hidden) + return result; + } + + @Override + public String createDocument(final String parentDocumentId, final String mimeType, + final String displayName) throws FileNotFoundException { + File newFile = new File(parentDocumentId, displayName); + try { + newFile.createNewFile(); + return newFile.getAbsolutePath(); + } catch (IOException e) { + Log.e(LocalStorageProvider.class.getSimpleName(), "Error creating new file " + newFile); + } + return null; + } + + @Override + public AssetFileDescriptor openDocumentThumbnail(final String documentId, final Point sizeHint, + final CancellationSignal signal) throws FileNotFoundException { + // Assume documentId points to an image file. Build a thumbnail no + // larger than twice the sizeHint + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(documentId, options); + final int targetHeight = 2 * sizeHint.y; + final int targetWidth = 2 * sizeHint.x; + final int height = options.outHeight; + final int width = options.outWidth; + options.inSampleSize = 1; + if (height > targetHeight || width > targetWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + // Calculate the largest inSampleSize value that is a power of 2 and + // keeps both + // height and width larger than the requested height and width. + while ((halfHeight / options.inSampleSize) > targetHeight + || (halfWidth / options.inSampleSize) > targetWidth) { + options.inSampleSize *= 2; + } + } + options.inJustDecodeBounds = false; + Bitmap bitmap = BitmapFactory.decodeFile(documentId, options); + // Write out the thumbnail to a temporary file + File tempFile = null; + FileOutputStream out = null; + try { + tempFile = File.createTempFile("thumbnail", null, getContext().getCacheDir()); + out = new FileOutputStream(tempFile); + bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); + } catch (IOException e) { + Log.e(LocalStorageProvider.class.getSimpleName(), "Error writing thumbnail", e); + return null; + } finally { + if (out != null) + try { + out.close(); + } catch (IOException e) { + Log.e(LocalStorageProvider.class.getSimpleName(), "Error closing thumbnail", e); + } + } + // It appears the Storage Framework UI caches these results quite + // aggressively so there is little reason to + // write your own caching layer beyond what you need to return a single + // AssetFileDescriptor + return new AssetFileDescriptor(ParcelFileDescriptor.open(tempFile, + ParcelFileDescriptor.MODE_READ_ONLY), 0, + AssetFileDescriptor.UNKNOWN_LENGTH); + } + + @Override + public Cursor queryChildDocuments(final String parentDocumentId, final String[] projection, + final String sortOrder) throws FileNotFoundException { + // Create a cursor with either the requested fields, or the default + // projection if "projection" is null. + final MatrixCursor result = new MatrixCursor(projection != null ? projection + : DEFAULT_DOCUMENT_PROJECTION); + final File parent = new File(parentDocumentId); + for (File file : parent.listFiles()) { + // Don't show hidden files/folders + if (!file.getName().startsWith(".")) { + // Adds the file's display name, MIME type, size, and so on. + includeFile(result, file); + } + } + return result; + } + + @Override + public Cursor queryDocument(final String documentId, final String[] projection) + throws FileNotFoundException { + // Create a cursor with either the requested fields, or the default + // projection if "projection" is null. + final MatrixCursor result = new MatrixCursor(projection != null ? projection + : DEFAULT_DOCUMENT_PROJECTION); + includeFile(result, new File(documentId)); + return result; + } + + private void includeFile(final MatrixCursor result, final File file) + throws FileNotFoundException { + final MatrixCursor.RowBuilder row = result.newRow(); + // These columns are required + row.add(Document.COLUMN_DOCUMENT_ID, file.getAbsolutePath()); + row.add(Document.COLUMN_DISPLAY_NAME, file.getName()); + String mimeType = getDocumentType(file.getAbsolutePath()); + row.add(Document.COLUMN_MIME_TYPE, mimeType); + int flags = file.canWrite() ? Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE + : 0; + // We only show thumbnails for image files - expect a call to + // openDocumentThumbnail for each file that has + // this flag set + if (mimeType.startsWith("image/")) + flags |= Document.FLAG_SUPPORTS_THUMBNAIL; + row.add(Document.COLUMN_FLAGS, flags); + // COLUMN_SIZE is required, but can be null + row.add(Document.COLUMN_SIZE, file.length()); + // These columns are optional + row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); + // Document.COLUMN_ICON can be a resource id identifying a custom icon. + // The system provides default icons + // based on mime type + // Document.COLUMN_SUMMARY is optional additional information about the + // file + } + + @Override + public String getDocumentType(final String documentId) throws FileNotFoundException { + File file = new File(documentId); + if (file.isDirectory()) + return Document.MIME_TYPE_DIR; + // From FileProvider.getType(Uri) + final int lastDot = file.getName().lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = file.getName().substring(lastDot + 1); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + return "application/octet-stream"; + } + + @Override + public void deleteDocument(final String documentId) throws FileNotFoundException { + new File(documentId).delete(); + } + + @Override + public ParcelFileDescriptor openDocument(final String documentId, final String mode, + final CancellationSignal signal) throws FileNotFoundException { + File file = new File(documentId); + final boolean isWrite = (mode.indexOf('w') != -1); + if (isWrite) { + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); + } else { + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + } + } + + @Override + public boolean onCreate() { + return true; + } +} diff --git a/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileChooserActivity.java b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileChooserActivity.java new file mode 100644 index 0000000..724cf1d --- /dev/null +++ b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileChooserActivity.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2013 Paul Burke + * + * 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.ipaulpro.afilechooser; + +import android.app.ActionBar; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentManager.BackStackEntry; +import android.support.v4.app.FragmentManager.OnBackStackChangedListener; +import android.support.v4.app.FragmentTransaction; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import java.io.File; + +/** + * Main Activity that handles the FileListFragments + * + * @version 2013-06-25 + * @author paulburke (ipaulpro) + */ +public class FileChooserActivity extends FragmentActivity implements + OnBackStackChangedListener, FileListFragment.Callbacks { + + public static final String PATH = "path"; + public static final String EXTERNAL_BASE_PATH = Environment + .getExternalStorageDirectory().getAbsolutePath(); + + private static final boolean HAS_ACTIONBAR = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + + private FragmentManager mFragmentManager; + private BroadcastReceiver mStorageListener = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Toast.makeText(context, R.string.storage_removed, Toast.LENGTH_LONG).show(); + finishWithResult(null); + } + }; + + private String mPath; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mFragmentManager = getSupportFragmentManager(); + mFragmentManager.addOnBackStackChangedListener(this); + + if (savedInstanceState == null) { + mPath = EXTERNAL_BASE_PATH; + addFragment(); + } else { + mPath = savedInstanceState.getString(PATH); + } + + setTitle(mPath); + } + + @Override + protected void onPause() { + super.onPause(); + + unregisterStorageListener(); + } + + @Override + protected void onResume() { + super.onResume(); + + registerStorageListener(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putString(PATH, mPath); + } + + @Override + public void onBackStackChanged() { + + int count = mFragmentManager.getBackStackEntryCount(); + if (count > 0) { + BackStackEntry fragment = mFragmentManager.getBackStackEntryAt(count - 1); + mPath = fragment.getName(); + } else { + mPath = EXTERNAL_BASE_PATH; + } + + setTitle(mPath); + if (HAS_ACTIONBAR) + invalidateOptionsMenu(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (HAS_ACTIONBAR) { + boolean hasBackStack = mFragmentManager.getBackStackEntryCount() > 0; + + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(hasBackStack); + actionBar.setHomeButtonEnabled(hasBackStack); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + mFragmentManager.popBackStack(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * Add the initial Fragment with given path. + */ + private void addFragment() { + FileListFragment fragment = FileListFragment.newInstance(mPath); + mFragmentManager.beginTransaction() + .add(android.R.id.content, fragment).commit(); + } + + /** + * "Replace" the existing Fragment with a new one using given path. We're + * really adding a Fragment to the back stack. + * + * @param file The file (directory) to display. + */ + private void replaceFragment(File file) { + mPath = file.getAbsolutePath(); + + FileListFragment fragment = FileListFragment.newInstance(mPath); + mFragmentManager.beginTransaction() + .replace(android.R.id.content, fragment) + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .addToBackStack(mPath).commit(); + } + + /** + * Finish this Activity with a result code and URI of the selected file. + * + * @param file The file selected. + */ + private void finishWithResult(File file) { + if (file != null) { + Uri uri = Uri.fromFile(file); + setResult(RESULT_OK, new Intent().setData(uri)); + finish(); + } else { + setResult(RESULT_CANCELED); + finish(); + } + } + + /** + * Called when the user selects a File + * + * @param file The file that was selected + */ + @Override + public void onFileSelected(File file) { + if (file != null) { + if (file.isDirectory()) { + replaceFragment(file); + } else { + finishWithResult(file); + } + } else { + Toast.makeText(FileChooserActivity.this, R.string.error_selecting_file, + Toast.LENGTH_SHORT).show(); + } + } + + /** + * Register the external storage BroadcastReceiver. + */ + private void registerStorageListener() { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_MEDIA_REMOVED); + registerReceiver(mStorageListener, filter); + } + + /** + * Unregister the external storage BroadcastReceiver. + */ + private void unregisterStorageListener() { + unregisterReceiver(mStorageListener); + } +} diff --git a/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileListAdapter.java b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileListAdapter.java new file mode 100644 index 0000000..3480122 --- /dev/null +++ b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileListAdapter.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 Paul Burke + * + * 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.ipaulpro.afilechooser; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * List adapter for Files. + * + * @version 2013-12-11 + * @author paulburke (ipaulpro) + */ +public class FileListAdapter extends BaseAdapter { + + private final static int ICON_FOLDER = R.drawable.ic_folder; + private final static int ICON_FILE = R.drawable.ic_file; + + private final LayoutInflater mInflater; + + private List mData = new ArrayList(); + + public FileListAdapter(Context context) { + mInflater = LayoutInflater.from(context); + } + + public void add(File file) { + mData.add(file); + notifyDataSetChanged(); + } + + public void remove(File file) { + mData.remove(file); + notifyDataSetChanged(); + } + + public void insert(File file, int index) { + mData.add(index, file); + notifyDataSetChanged(); + } + + public void clear() { + mData.clear(); + notifyDataSetChanged(); + } + + @Override + public File getItem(int position) { + return mData.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getCount() { + return mData.size(); + } + + public List getListItems() { + return mData; + } + + /** + * Set the list items without notifying on the clear. This prevents loss of + * scroll position. + * + * @param data + */ + public void setListItems(List data) { + mData = data; + notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + + if (row == null) + row = mInflater.inflate(R.layout.file, parent, false); + + TextView view = (TextView) row; + + // Get the file at the current position + final File file = getItem(position); + + // Set the TextView as the file name + view.setText(file.getName()); + + // If the item is not a directory, use the file icon + int icon = file.isDirectory() ? ICON_FOLDER : ICON_FILE; + view.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); + + return row; + } + +} \ No newline at end of file diff --git a/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileListFragment.java b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileListFragment.java new file mode 100644 index 0000000..5da363a --- /dev/null +++ b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileListFragment.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 Paul Burke + * + * 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.ipaulpro.afilechooser; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Environment; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.view.View; +import android.widget.ListView; + +import java.io.File; +import java.util.List; + +/** + * Fragment that displays a list of Files in a given path. + * + * @version 2013-12-11 + * @author paulburke (ipaulpro) + */ +public class FileListFragment extends ListFragment implements + LoaderManager.LoaderCallbacks> { + + /** + * Interface to listen for events. + */ + public interface Callbacks { + /** + * Called when a file is selected from the list. + * + * @param file The file selected + */ + public void onFileSelected(File file); + } + + private static final int LOADER_ID = 0; + + private FileListAdapter mAdapter; + private String mPath; + + private Callbacks mListener; + + /** + * Create a new instance with the given file path. + * + * @param path The absolute path of the file (directory) to display. + * @return A new Fragment with the given file path. + */ + public static FileListFragment newInstance(String path) { + FileListFragment fragment = new FileListFragment(); + Bundle args = new Bundle(); + args.putString(FileChooserActivity.PATH, path); + fragment.setArguments(args); + + return fragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + try { + mListener = (Callbacks) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + + " must implement FileListFragment.Callbacks"); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mAdapter = new FileListAdapter(getActivity()); + mPath = getArguments() != null ? getArguments().getString( + FileChooserActivity.PATH) : Environment + .getExternalStorageDirectory().getAbsolutePath(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + setEmptyText(getString(R.string.empty_directory)); + setListAdapter(mAdapter); + setListShown(false); + + getLoaderManager().initLoader(LOADER_ID, null, this); + + super.onActivityCreated(savedInstanceState); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + FileListAdapter adapter = (FileListAdapter) l.getAdapter(); + if (adapter != null) { + File file = (File) adapter.getItem(position); + mPath = file.getAbsolutePath(); + mListener.onFileSelected(file); + } + } + + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new FileLoader(getActivity(), mPath); + } + + @Override + public void onLoadFinished(Loader> loader, List data) { + mAdapter.setListItems(data); + + if (isResumed()) + setListShown(true); + else + setListShownNoAnimation(true); + } + + @Override + public void onLoaderReset(Loader> loader) { + mAdapter.clear(); + } +} diff --git a/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileLoader.java b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileLoader.java new file mode 100644 index 0000000..f8903ac --- /dev/null +++ b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/FileLoader.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2013 Paul Burke + * + * 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.ipaulpro.afilechooser; + +import android.content.Context; +import android.os.FileObserver; +import android.support.v4.content.AsyncTaskLoader; + +import com.ipaulpro.afilechooser.utils.FileUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Loader that returns a list of Files in a given file path. + * + * @version 2013-12-11 + * @author paulburke (ipaulpro) + */ +public class FileLoader extends AsyncTaskLoader> { + + private static final int FILE_OBSERVER_MASK = FileObserver.CREATE + | FileObserver.DELETE | FileObserver.DELETE_SELF + | FileObserver.MOVED_FROM | FileObserver.MOVED_TO + | FileObserver.MODIFY | FileObserver.MOVE_SELF; + + private FileObserver mFileObserver; + + private List mData; + private String mPath; + + public FileLoader(Context context, String path) { + super(context); + this.mPath = path; + } + + @Override + public List loadInBackground() { + + ArrayList list = new ArrayList(); + + // Current directory File instance + final File pathDir = new File(mPath); + + // List file in this directory with the directory filter + final File[] dirs = pathDir.listFiles(FileUtils.sDirFilter); + if (dirs != null) { + // Sort the folders alphabetically + Arrays.sort(dirs, FileUtils.sComparator); + // Add each folder to the File list for the list adapter + for (File dir : dirs) + list.add(dir); + } + + // List file in this directory with the file filter + final File[] files = pathDir.listFiles(FileUtils.sFileFilter); + if (files != null) { + // Sort the files alphabetically + Arrays.sort(files, FileUtils.sComparator); + // Add each file to the File list for the list adapter + for (File file : files) + list.add(file); + } + + return list; + } + + @Override + public void deliverResult(List data) { + if (isReset()) { + onReleaseResources(data); + return; + } + + List oldData = mData; + mData = data; + + if (isStarted()) + super.deliverResult(data); + + if (oldData != null && oldData != data) + onReleaseResources(oldData); + } + + @Override + protected void onStartLoading() { + if (mData != null) + deliverResult(mData); + + if (mFileObserver == null) { + mFileObserver = new FileObserver(mPath, FILE_OBSERVER_MASK) { + @Override + public void onEvent(int event, String path) { + onContentChanged(); + } + }; + } + mFileObserver.startWatching(); + + if (takeContentChanged() || mData == null) + forceLoad(); + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + protected void onReset() { + onStopLoading(); + + if (mData != null) { + onReleaseResources(mData); + mData = null; + } + } + + @Override + public void onCanceled(List data) { + super.onCanceled(data); + + onReleaseResources(data); + } + + protected void onReleaseResources(List data) { + + if (mFileObserver != null) { + mFileObserver.stopWatching(); + mFileObserver = null; + } + } +} \ No newline at end of file diff --git a/aFileChooser/src/main/java/com/ipaulpro/afilechooser/utils/FileUtils.java b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/utils/FileUtils.java new file mode 100644 index 0000000..8320f7a --- /dev/null +++ b/aFileChooser/src/main/java/com/ipaulpro/afilechooser/utils/FileUtils.java @@ -0,0 +1,528 @@ +/* + * Copyright (C) 2007-2008 OpenIntents.org + * + * 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.ipaulpro.afilechooser.utils; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import com.ianhanniballake.localstorage.LocalStorageProvider; + +import java.io.File; +import java.io.FileFilter; +import java.text.DecimalFormat; +import java.util.Comparator; + +/** + * @version 2009-07-03 + * @author Peli + * @version 2013-12-11 + * @author paulburke (ipaulpro) + */ +public class FileUtils { + private FileUtils() {} //private constructor to enforce Singleton pattern + + /** TAG for log messages. */ + static final String TAG = "FileUtils"; + private static final boolean DEBUG = false; // Set to true to enable logging + + public static final String MIME_TYPE_AUDIO = "audio/*"; + public static final String MIME_TYPE_TEXT = "text/*"; + public static final String MIME_TYPE_IMAGE = "image/*"; + public static final String MIME_TYPE_VIDEO = "video/*"; + public static final String MIME_TYPE_APP = "application/*"; + + public static final String HIDDEN_PREFIX = "."; + + /** + * Gets the extension of a file name, like ".png" or ".jpg". + * + * @param uri + * @return Extension including the dot("."); "" if there is no extension; + * null if uri was null. + */ + public static String getExtension(String uri) { + if (uri == null) { + return null; + } + + int dot = uri.lastIndexOf("."); + if (dot >= 0) { + return uri.substring(dot); + } else { + // No extension. + return ""; + } + } + + /** + * @return Whether the URI is a local one. + */ + public static boolean isLocal(String url) { + if (url != null && !url.startsWith("http://") && !url.startsWith("https://")) { + return true; + } + return false; + } + + /** + * @return True if Uri is a MediaStore Uri. + * @author paulburke + */ + public static boolean isMediaUri(Uri uri) { + return "media".equalsIgnoreCase(uri.getAuthority()); + } + + /** + * Convert File into Uri. + * + * @param file + * @return uri + */ + public static Uri getUri(File file) { + if (file != null) { + return Uri.fromFile(file); + } + return null; + } + + /** + * Returns the path only (without file name). + * + * @param file + * @return + */ + public static File getPathWithoutFilename(File file) { + if (file != null) { + if (file.isDirectory()) { + // no file to be split off. Return everything + return file; + } else { + String filename = file.getName(); + String filepath = file.getAbsolutePath(); + + // Construct path without file name. + String pathwithoutname = filepath.substring(0, + filepath.length() - filename.length()); + if (pathwithoutname.endsWith("/")) { + pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1); + } + return new File(pathwithoutname); + } + } + return null; + } + + /** + * @return The MIME type for the given file. + */ + public static String getMimeType(File file) { + + String extension = getExtension(file.getName()); + + if (extension.length() > 0) + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); + + return "application/octet-stream"; + } + + /** + * @return The MIME type for the give Uri. + */ + public static String getMimeType(Context context, Uri uri) { + File file = new File(getPath(context, uri)); + return getMimeType(file); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is {@link LocalStorageProvider}. + * @author paulburke + */ + public static boolean isLocalStorageDocument(Uri uri) { + return LocalStorageProvider.AUTHORITY.equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + * @author paulburke + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + * @author paulburke + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + * @author paulburke + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + * @author paulburke + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + if (DEBUG) + DatabaseUtils.dumpCursor(cursor); + + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders.
+ *
+ * Callers should check whether the path is local before assuming it + * represents a local file. + * + * @param context The context. + * @param uri The Uri to query. + * @see #isLocal(String) + * @see #getFile(Context, Uri) + * @author paulburke + */ + public static String getPath(final Context context, final Uri uri) { + + if (DEBUG) + Log.d(TAG + " File -", + "Authority: " + uri.getAuthority() + + ", Fragment: " + uri.getFragment() + + ", Port: " + uri.getPort() + + ", Query: " + uri.getQuery() + + ", Scheme: " + uri.getScheme() + + ", Host: " + uri.getHost() + + ", Segments: " + uri.getPathSegments().toString() + ); + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // LocalStorageProvider + if (isLocalStorageDocument(uri)) { + // The path is the id + return DocumentsContract.getDocumentId(uri); + } + // ExternalStorageProvider + else if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[] { + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Convert Uri into File, if possible. + * + * @return file A local file that the Uri was pointing to, or null if the + * Uri is unsupported or pointed to a remote resource. + * @see #getPath(Context, Uri) + * @author paulburke + */ + public static File getFile(Context context, Uri uri) { + if (uri != null) { + String path = getPath(context, uri); + if (path != null && isLocal(path)) { + return new File(path); + } + } + return null; + } + + /** + * Get the file size in a human-readable string. + * + * @param size + * @return + * @author paulburke + */ + public static String getReadableFileSize(int size) { + final int BYTES_IN_KILOBYTES = 1024; + final DecimalFormat dec = new DecimalFormat("###.#"); + final String KILOBYTES = " KB"; + final String MEGABYTES = " MB"; + final String GIGABYTES = " GB"; + float fileSize = 0; + String suffix = KILOBYTES; + + if (size > BYTES_IN_KILOBYTES) { + fileSize = size / BYTES_IN_KILOBYTES; + if (fileSize > BYTES_IN_KILOBYTES) { + fileSize = fileSize / BYTES_IN_KILOBYTES; + if (fileSize > BYTES_IN_KILOBYTES) { + fileSize = fileSize / BYTES_IN_KILOBYTES; + suffix = GIGABYTES; + } else { + suffix = MEGABYTES; + } + } + } + return String.valueOf(dec.format(fileSize) + suffix); + } + + /** + * Attempt to retrieve the thumbnail of given File from the MediaStore. This + * should not be called on the UI thread. + * + * @param context + * @param file + * @return + * @author paulburke + */ + public static Bitmap getThumbnail(Context context, File file) { + return getThumbnail(context, getUri(file), getMimeType(file)); + } + + /** + * Attempt to retrieve the thumbnail of given Uri from the MediaStore. This + * should not be called on the UI thread. + * + * @param context + * @param uri + * @return + * @author paulburke + */ + public static Bitmap getThumbnail(Context context, Uri uri) { + return getThumbnail(context, uri, getMimeType(context, uri)); + } + + /** + * Attempt to retrieve the thumbnail of given Uri from the MediaStore. This + * should not be called on the UI thread. + * + * @param context + * @param uri + * @param mimeType + * @return + * @author paulburke + */ + public static Bitmap getThumbnail(Context context, Uri uri, String mimeType) { + if (DEBUG) + Log.d(TAG, "Attempting to get thumbnail"); + + if (!isMediaUri(uri)) { + Log.e(TAG, "You can only retrieve thumbnails for images and videos."); + return null; + } + + Bitmap bm = null; + if (uri != null) { + final ContentResolver resolver = context.getContentResolver(); + Cursor cursor = null; + try { + cursor = resolver.query(uri, null, null, null, null); + if (cursor.moveToFirst()) { + final int id = cursor.getInt(0); + if (DEBUG) + Log.d(TAG, "Got thumb ID: " + id); + + if (mimeType.contains("video")) { + bm = MediaStore.Video.Thumbnails.getThumbnail( + resolver, + id, + MediaStore.Video.Thumbnails.MINI_KIND, + null); + } + else if (mimeType.contains(FileUtils.MIME_TYPE_IMAGE)) { + bm = MediaStore.Images.Thumbnails.getThumbnail( + resolver, + id, + MediaStore.Images.Thumbnails.MINI_KIND, + null); + } + } + } catch (Exception e) { + if (DEBUG) + Log.e(TAG, "getThumbnail", e); + } finally { + if (cursor != null) + cursor.close(); + } + } + return bm; + } + + /** + * File and folder comparator. TODO Expose sorting option method + * + * @author paulburke + */ + public static Comparator sComparator = new Comparator() { + @Override + public int compare(File f1, File f2) { + // Sort alphabetically by lower case, which is much cleaner + return f1.getName().toLowerCase().compareTo( + f2.getName().toLowerCase()); + } + }; + + /** + * File (not directories) filter. + * + * @author paulburke + */ + public static FileFilter sFileFilter = new FileFilter() { + @Override + public boolean accept(File file) { + final String fileName = file.getName(); + // Return files only (not directories) and skip hidden files + return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX); + } + }; + + /** + * Folder (directories) filter. + * + * @author paulburke + */ + public static FileFilter sDirFilter = new FileFilter() { + @Override + public boolean accept(File file) { + final String fileName = file.getName(); + // Return directories only and skip hidden directories + return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX); + } + }; + + /** + * Get the Intent for selecting content to be used in an Intent Chooser. + * + * @return The intent for opening a file with Intent.createChooser() + * @author paulburke + */ + public static Intent createGetContentIntent() { + // Implicitly allow the user to select a particular kind of data + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + // The MIME data type filter + intent.setType("*/*"); + // Only return URIs that can be opened with ContentResolver + intent.addCategory(Intent.CATEGORY_OPENABLE); + return intent; + } +} diff --git a/aFileChooser/src/main/res/drawable-hdpi/ic_chooser.png b/aFileChooser/src/main/res/drawable-hdpi/ic_chooser.png new file mode 100644 index 0000000..da35bf2 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-hdpi/ic_chooser.png differ diff --git a/aFileChooser/src/main/res/drawable-hdpi/ic_file.png b/aFileChooser/src/main/res/drawable-hdpi/ic_file.png new file mode 100644 index 0000000..39ef158 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-hdpi/ic_file.png differ diff --git a/aFileChooser/src/main/res/drawable-hdpi/ic_folder.png b/aFileChooser/src/main/res/drawable-hdpi/ic_folder.png new file mode 100644 index 0000000..b73961c Binary files /dev/null and b/aFileChooser/src/main/res/drawable-hdpi/ic_folder.png differ diff --git a/aFileChooser/src/main/res/drawable-hdpi/ic_provider.png b/aFileChooser/src/main/res/drawable-hdpi/ic_provider.png new file mode 100644 index 0000000..e236c01 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-hdpi/ic_provider.png differ diff --git a/aFileChooser/src/main/res/drawable-mdpi/ic_chooser.png b/aFileChooser/src/main/res/drawable-mdpi/ic_chooser.png new file mode 100644 index 0000000..68b3534 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-mdpi/ic_chooser.png differ diff --git a/aFileChooser/src/main/res/drawable-mdpi/ic_file.png b/aFileChooser/src/main/res/drawable-mdpi/ic_file.png new file mode 100644 index 0000000..2ac7a07 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-mdpi/ic_file.png differ diff --git a/aFileChooser/src/main/res/drawable-mdpi/ic_folder.png b/aFileChooser/src/main/res/drawable-mdpi/ic_folder.png new file mode 100644 index 0000000..727353f Binary files /dev/null and b/aFileChooser/src/main/res/drawable-mdpi/ic_folder.png differ diff --git a/aFileChooser/src/main/res/drawable-mdpi/ic_provider.png b/aFileChooser/src/main/res/drawable-mdpi/ic_provider.png new file mode 100644 index 0000000..5fbbe81 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-mdpi/ic_provider.png differ diff --git a/aFileChooser/src/main/res/drawable-xhdpi/ic_chooser.png b/aFileChooser/src/main/res/drawable-xhdpi/ic_chooser.png new file mode 100644 index 0000000..a2e38a4 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xhdpi/ic_chooser.png differ diff --git a/aFileChooser/src/main/res/drawable-xhdpi/ic_file.png b/aFileChooser/src/main/res/drawable-xhdpi/ic_file.png new file mode 100644 index 0000000..3d9c773 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xhdpi/ic_file.png differ diff --git a/aFileChooser/src/main/res/drawable-xhdpi/ic_folder.png b/aFileChooser/src/main/res/drawable-xhdpi/ic_folder.png new file mode 100644 index 0000000..d539a3a Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xhdpi/ic_folder.png differ diff --git a/aFileChooser/src/main/res/drawable-xhdpi/ic_provider.png b/aFileChooser/src/main/res/drawable-xhdpi/ic_provider.png new file mode 100644 index 0000000..48909ef Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xhdpi/ic_provider.png differ diff --git a/aFileChooser/src/main/res/drawable-xxhdpi/ic_chooser.png b/aFileChooser/src/main/res/drawable-xxhdpi/ic_chooser.png new file mode 100644 index 0000000..0395102 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xxhdpi/ic_chooser.png differ diff --git a/aFileChooser/src/main/res/drawable-xxhdpi/ic_file.png b/aFileChooser/src/main/res/drawable-xxhdpi/ic_file.png new file mode 100644 index 0000000..1102307 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xxhdpi/ic_file.png differ diff --git a/aFileChooser/src/main/res/drawable-xxhdpi/ic_folder.png b/aFileChooser/src/main/res/drawable-xxhdpi/ic_folder.png new file mode 100644 index 0000000..f0735bb Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xxhdpi/ic_folder.png differ diff --git a/aFileChooser/src/main/res/drawable-xxhdpi/ic_provider.png b/aFileChooser/src/main/res/drawable-xxhdpi/ic_provider.png new file mode 100644 index 0000000..2aa8839 Binary files /dev/null and b/aFileChooser/src/main/res/drawable-xxhdpi/ic_provider.png differ diff --git a/aFileChooser/src/main/res/layout/file.xml b/aFileChooser/src/main/res/layout/file.xml new file mode 100644 index 0000000..c07cf49 --- /dev/null +++ b/aFileChooser/src/main/res/layout/file.xml @@ -0,0 +1,20 @@ + + + diff --git a/aFileChooser/src/main/res/values-ca/strings.xml b/aFileChooser/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..df8dc7e --- /dev/null +++ b/aFileChooser/src/main/res/values-ca/strings.xml @@ -0,0 +1,8 @@ + + + + Carpeta buida + S\'ha tret o desmuntat l\'emmagatzematge. + Seleccioneu un fitxer + Error en seleccionar el fitxer + diff --git a/aFileChooser/src/main/res/values-de/strings.xml b/aFileChooser/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..d6f6058 --- /dev/null +++ b/aFileChooser/src/main/res/values-de/strings.xml @@ -0,0 +1,7 @@ + + + Leerer Ordner + Speicher wurde entferntet. + Wähle eine Datei + Fehler beim Öffnen der Datei + \ No newline at end of file diff --git a/aFileChooser/src/main/res/values-es/strings.xml b/aFileChooser/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..005247f --- /dev/null +++ b/aFileChooser/src/main/res/values-es/strings.xml @@ -0,0 +1,8 @@ + + + + Directorio vacío + Se ha retirado o desmontado el almacenamiento. + Seleccione un archivo + Error al seleccionar el archivo + diff --git a/aFileChooser/src/main/res/values-fr/strings.xml b/aFileChooser/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..6077476 --- /dev/null +++ b/aFileChooser/src/main/res/values-fr/strings.xml @@ -0,0 +1,8 @@ + + + + Dossier vide + Le stockage a été enlevé ou démonté. + Sélectionnez un fichier + Erreur lors de la sélection du fichier + diff --git a/aFileChooser/src/main/res/values-ga/strings.xml b/aFileChooser/src/main/res/values-ga/strings.xml new file mode 100644 index 0000000..ee9185d --- /dev/null +++ b/aFileChooser/src/main/res/values-ga/strings.xml @@ -0,0 +1,8 @@ + + + + Comhadlann fholamh + Baineadh amach an gléas stórála nó dínascadh é. + Roghnaigh comhad + Tharla botún fad is a bhí comhad á roghnú + diff --git a/aFileChooser/src/main/res/values-it/strings.xml b/aFileChooser/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..d4a7d36 --- /dev/null +++ b/aFileChooser/src/main/res/values-it/strings.xml @@ -0,0 +1,8 @@ + + + + Directory vuota + Lo spazio di archiviazione è stato rimosso o smontato. + Selezionare un file + Errore nel selezionare il File + diff --git a/aFileChooser/src/main/res/values-ko/strings.xml b/aFileChooser/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..dfe0e7e --- /dev/null +++ b/aFileChooser/src/main/res/values-ko/strings.xml @@ -0,0 +1,8 @@ + + + + 빈 디렉토리 + 저장소가 제거되었습니다. + 파일 선택 + 파일 선택 오류 + diff --git a/aFileChooser/src/main/res/values-pl/strings.xml b/aFileChooser/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..fd0077d --- /dev/null +++ b/aFileChooser/src/main/res/values-pl/strings.xml @@ -0,0 +1,8 @@ + + + + Pusty katalog + Pamięć została usunięta lub odmontowana. + Wybierz plik + Błąd, podczas wybierania pliku + diff --git a/aFileChooser/src/main/res/values-pt-rBR/strings.xml b/aFileChooser/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..4c8f005 --- /dev/null +++ b/aFileChooser/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,8 @@ + + + + Pasta Vazia + Unidade externa removida ou não preparada. + Selecione um Arquivo + Erro ao selecionar o Arquivo + diff --git a/aFileChooser/src/main/res/values-ru/strings.xml b/aFileChooser/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..8467250 --- /dev/null +++ b/aFileChooser/src/main/res/values-ru/strings.xml @@ -0,0 +1,8 @@ + + + + Пустая папка + Storage was removed or unmounted. + Выберите файл + Ошибка при выборе файла + diff --git a/aFileChooser/src/main/res/values-v11/strings.xml b/aFileChooser/src/main/res/values-v11/strings.xml new file mode 100644 index 0000000..9b114a3 --- /dev/null +++ b/aFileChooser/src/main/res/values-v11/strings.xml @@ -0,0 +1,19 @@ + + + + Choose a file + \ No newline at end of file diff --git a/aFileChooser/src/main/res/values-v19/bool.xml b/aFileChooser/src/main/res/values-v19/bool.xml new file mode 100644 index 0000000..5fc0a1d --- /dev/null +++ b/aFileChooser/src/main/res/values-v19/bool.xml @@ -0,0 +1,7 @@ + + + + false + true + + \ No newline at end of file diff --git a/aFileChooser/src/main/res/values/bool.xml b/aFileChooser/src/main/res/values/bool.xml new file mode 100644 index 0000000..5d09580 --- /dev/null +++ b/aFileChooser/src/main/res/values/bool.xml @@ -0,0 +1,7 @@ + + + + true + false + + \ No newline at end of file diff --git a/aFileChooser/src/main/res/values/dimens.xml b/aFileChooser/src/main/res/values/dimens.xml new file mode 100644 index 0000000..a991784 --- /dev/null +++ b/aFileChooser/src/main/res/values/dimens.xml @@ -0,0 +1,20 @@ + + + + 0dp + 16dp + \ No newline at end of file diff --git a/aFileChooser/src/main/res/values/strings.xml b/aFileChooser/src/main/res/values/strings.xml new file mode 100644 index 0000000..4c062df --- /dev/null +++ b/aFileChooser/src/main/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + Empty Directory + Storage was removed or unmounted. + Select a file + Error selecting File + Internal storage + \ No newline at end of file diff --git a/aFileChooser/src/main/res/values/styles.xml b/aFileChooser/src/main/res/values/styles.xml new file mode 100644 index 0000000..7f1e43d --- /dev/null +++ b/aFileChooser/src/main/res/values/styles.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/aFileChooser/src/main/res/xml/mimetypes.xml b/aFileChooser/src/main/res/xml/mimetypes.xml new file mode 100644 index 0000000..062435f --- /dev/null +++ b/aFileChooser/src/main/res/xml/mimetypes.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..127c9f2 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..522f589 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + defaultConfig { + applicationId "ru.spbau.group202.notdeadbydeadline" + minSdkVersion 22 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + multiDexEnabled true + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + lintOptions { + abortOnError false + } +} + +dependencies { + implementation "com.google.code.gson:gson:2.8.0" + implementation group: 'org.jsoup', name: 'jsoup', version: '1.7.2' + implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.0' + implementation group: 'commons-io', name: 'commons-io', version: '2.4' + implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.android.support:support-v4:26.1.0' + implementation 'com.android.support:design:26.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + implementation 'org.jetbrains:annotations-java5:15.0' + implementation 'net.danlew:android.joda:2.9.9.1' + implementation 'com.android.support:multidex:1.0.2' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(":aFileChooser") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.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/src/androidTest/java/ru/spbau/group202/notdeadbydeadline/ExampleInstrumentedTest.java b/app/src/androidTest/java/ru/spbau/group202/notdeadbydeadline/ExampleInstrumentedTest.java new file mode 100644 index 0000000..af8e055 --- /dev/null +++ b/app/src/androidTest/java/ru/spbau/group202/notdeadbydeadline/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package ru.spbau.group202.notdeadbydeadline; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("ru.spbau.group202.notdeadbydeadline", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..66e8ed8 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/ClassDatabaseController.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/ClassDatabaseController.java new file mode 100644 index 0000000..8101249 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/ClassDatabaseController.java @@ -0,0 +1,158 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.Class; +import ru.spbau.group202.notdeadbydeadline.model.WeekParityEnum; + +public class ClassDatabaseController extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "Schedule"; + private static final int DATABASE_VERSION = 1; + private static final String COLUMN_NAME_ID = "ID"; + private static final String COLUMN_NAME_SUBJECT = "SUBJECT"; + private static final String COLUMN_NAME_DAY_OF_WEEK = "DAY_OF_WEEK"; + private static final String COLUMN_NAME_HOUR = "HOUR"; + private static final String COLUMN_NAME_MINUTE = "MINUTE"; + private static final String COLUMN_NAME_WEEK_PARITY = "WEEK_PARITY"; + private static final String COLUMN_NAME_AUDITORIUM = "AUDITORIUM"; + private static final String COLUMN_NAME_TEACHER = "TEACHER"; + + public ClassDatabaseController(@NotNull Context context) { + super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d("Database", "onCreate database"); + db.execSQL("CREATE TABLE " + DATABASE_NAME + " (" + + COLUMN_NAME_ID + " INTEGER PRIMARY KEY, " + + COLUMN_NAME_SUBJECT + " TEXT, " + + COLUMN_NAME_DAY_OF_WEEK + " INTEGER, " + + COLUMN_NAME_HOUR + " INTEGER, " + + COLUMN_NAME_MINUTE + " INTEGER, " + + COLUMN_NAME_WEEK_PARITY + " INTEGER, " + + COLUMN_NAME_AUDITORIUM + " TEXT, " + + COLUMN_NAME_TEACHER + " TEXT" + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldDATABASE_VERSION, int newDATABASE_VERSION) { + db.execSQL("DROP TABLE IF EXISTS " + DATABASE_NAME); + onCreate(db); + } + + @NotNull + private Class getClassByCursor(@NotNull Cursor cursor) { + int id = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_ID)); + String subject = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_SUBJECT)); + int dayOfWeek = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_DAY_OF_WEEK)); + int hour = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_HOUR)); + int minute = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_MINUTE)); + int weekParity = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_WEEK_PARITY)); + String auditorium = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_AUDITORIUM)); + String teacher = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_TEACHER)); + + return new Class(subject, dayOfWeek, hour, minute, + WeekParityEnum.values()[weekParity], auditorium, teacher, id); + } + + public void addClass(@NotNull Class aClass) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_ID, aClass.getId()); + values.put(COLUMN_NAME_SUBJECT, aClass.getSubject()); + values.put(COLUMN_NAME_DAY_OF_WEEK, aClass.getDayOfWeek()); + values.put(COLUMN_NAME_HOUR, aClass.getHour()); + values.put(COLUMN_NAME_MINUTE, aClass.getMinute()); + values.put(COLUMN_NAME_WEEK_PARITY, aClass.getWeekParity().ordinal()); + values.put(COLUMN_NAME_AUDITORIUM, aClass.getAuditorium()); + values.put(COLUMN_NAME_TEACHER, aClass.getTeacher()); + long rowId = database.insert(DATABASE_NAME, null, values); + Log.d("Database", "inserted row number " + rowId); + } + } + + @NotNull + public List getDaySchedule(int dayOfWeek, WeekParityEnum weekParity) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_DAY_OF_WEEK + "=? " + + "AND (" + COLUMN_NAME_WEEK_PARITY + "=? " + " OR " + COLUMN_NAME_WEEK_PARITY + "=?) " + + "ORDER BY " + COLUMN_NAME_HOUR + ", " + COLUMN_NAME_MINUTE; + + String[] selectionArgs = new String[]{String.valueOf(dayOfWeek), + String.valueOf(weekParity.ordinal()), String.valueOf(WeekParityEnum.ALWAYS.ordinal())}; + List daySchedule = new ArrayList<>(); + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + daySchedule.add(getClassByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + return daySchedule; + } + + @NotNull + public List getAllClasses() { + String query = "SELECT * FROM " + DATABASE_NAME; + List classes = new ArrayList<>(); + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + classes.add(getClassByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + return classes; + } + + public void deleteClassById(int id) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + database.delete(DATABASE_NAME, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + public Class getClassById(int id) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_ID + "=?"; + String[] selectionArgs = new String[]{String.valueOf(id)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + cursor.moveToFirst(); + return getClassByCursor(cursor); + } + } + + public void editClassById(@NotNull String subject, int dayOfWeek, int hour, int minute, + @NotNull WeekParityEnum weekParity, @NotNull String auditorium, + @NotNull String teacher, int id) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_SUBJECT, subject); + values.put(COLUMN_NAME_DAY_OF_WEEK, dayOfWeek); + values.put(COLUMN_NAME_HOUR, hour); + values.put(COLUMN_NAME_MINUTE, minute); + values.put(COLUMN_NAME_WEEK_PARITY, weekParity.ordinal()); + values.put(COLUMN_NAME_AUDITORIUM, auditorium); + values.put(COLUMN_NAME_TEACHER, teacher); + database.update(DATABASE_NAME, values, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/Controller.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/Controller.java new file mode 100644 index 0000000..7d2d53f --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/Controller.java @@ -0,0 +1,422 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + +import android.content.Context; +import android.os.Bundle; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.apache.commons.collections4.ListUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + + +import ru.spbau.group202.notdeadbydeadline.model.CreditFormEnum; +import ru.spbau.group202.notdeadbydeadline.model.ScheduleEntry; +import ru.spbau.group202.notdeadbydeadline.model.Homework; +import ru.spbau.group202.notdeadbydeadline.model.Class; +import ru.spbau.group202.notdeadbydeadline.model.StudyMaterial; +import ru.spbau.group202.notdeadbydeadline.model.SubjectCredit; +import ru.spbau.group202.notdeadbydeadline.model.WeekParityEnum; +import ru.spbau.group202.notdeadbydeadline.model.Exam; +import ru.spbau.group202.notdeadbydeadline.model.ExamEnum; +import ru.spbau.group202.notdeadbydeadline.model.utilities.AsyncTaskResult; +import ru.spbau.group202.notdeadbydeadline.model.utilities.ModelUtils; +import ru.spbau.group202.notdeadbydeadline.model.utilities.StudyMaterialsSourceAccessException; +import ru.spbau.group202.notdeadbydeadline.model.utilities.StudyMaterialsUpdatingException; +import ru.spbau.group202.notdeadbydeadline.model.utilities.UnrecognizedCreditFormException; +import ru.spbau.group202.notdeadbydeadline.model.utilities.UrlDownloadingException; +import ru.spbau.group202.notdeadbydeadline.model.utilities.WebStudyMaterialException; + +public class Controller { + private static Controller instance; + private File appDirectory; + private StoredDataController settingsDatabase; + private SubjectDatabaseController subjectDatabase; + private ExamController examController; + private HomeworkController homeworkController; + private ScheduleController scheduleController; + private StudyMaterialController studyMaterialController; + private Set subjectList; + + private Controller(@NotNull Context context) { + homeworkController = new HomeworkController(context); + subjectDatabase = new SubjectDatabaseController(context); + scheduleController = new ScheduleController(context); + examController = new ExamController(context); + studyMaterialController = new StudyMaterialController(context); + settingsDatabase = new StoredDataController(context); + subjectList = new HashSet<>(); + subjectList.addAll(subjectDatabase.getAllSubjects()); + appDirectory = context.getApplicationContext().getFilesDir(); + } + + public static Controller getInstance(Context context) { + if (instance == null) { + instance = new Controller(context.getApplicationContext()); + } + return instance; + } + + public ExamController examController() { + return examController; + } + + public HomeworkController homeworkController() { + return homeworkController; + } + + public ScheduleController scheduleController() { + return scheduleController; + } + + public StudyMaterialController studyMaterialController() { + return studyMaterialController; + } + + @NotNull + public List getSubjectList() { + return new ArrayList<>(subjectList); + } + + public void setWeekPairity(boolean isInversed) { + settingsDatabase.saveWeekPairity(isInversed); + } + + @NotNull + public List calculateProgress(@NotNull String subject) throws UnrecognizedCreditFormException { + SubjectCredit subjectCredit = subjectDatabase.getSubjectCredit(subject); + return subjectCredit.calculateProgress(homeworkController.homeworkDatabase.getPassedHomeworksBySubject(subject), + examController.examDatabase.getExamsBySubject(subject)); + } + + public void setSubjectCreditForm(@NotNull String subject, @NotNull CreditFormEnum credit) { + subjectDatabase.setSubjectCreditForm(subject, credit); + } + + + public class HomeworkController { + private HomeworkDatabaseController homeworkDatabase; + + public HomeworkController(Context context) { + homeworkDatabase = new HomeworkDatabaseController(context); + } + + @NotNull + public List> getHomeworksBySubject(@NotNull String subject) { + return ModelUtils.map(homeworkDatabase.getHomeworksBySubject(subject), ModelUtils.HW_FIELDS_TO_STRING_LIST); + } + + public void addHomework(@NotNull LocalDateTime deadline, @NotNull String subject, + int regularity, String description, String howToSend, + double expectedScore, @NotNull ArrayList materials) { + int id = settingsDatabase.getTotalNumberOfHW(); + Homework homework = new Homework(deadline, subject, regularity, description, + howToSend, expectedScore, id, materials); + homeworkDatabase.addHomework(homework); + settingsDatabase.saveTotalNumberOfHW(++id); + + if (subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + public void deleteHomeworkById(int id) { + homeworkDatabase.deleteHomeworkById(id); + } + + public void setHomeworkScoreById(int id, int score) { + homeworkDatabase.setScoreById(id, score); + } + + public void setHomeworkDeferralById(int id, int deferral) { + homeworkDatabase.setDeferralById(id, deferral); + } + + public void editHomeworkById(int id, @NotNull LocalDateTime deadline, @NotNull String subject, + int regularity, String description, String howToSend, + double expectedScore, @NotNull ArrayList materials) { + homeworkDatabase.editHomeworkById(deadline, subject, regularity, description, howToSend, + expectedScore, id, materials); + if (subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + @NotNull + public Bundle getHomeworkById(int id) { + return homeworkDatabase.getHomeworkById(id).getDeconstructed(); + } + + @NotNull + public List> getDeadlinesByDay(@NotNull LocalDate date) { + return ModelUtils.map(homeworkDatabase.getHomeworksByDay(date), ModelUtils.HW_DEADLINE_FIELDS_TO_STRING_LIST); + } + + public void generateHomeworks() { + LocalDate today = LocalDate.now(); + for (Homework homework : homeworkDatabase.getHomeworksByDay(today)) { + if (homework.getRegularity() != 0) { + int id = settingsDatabase.getTotalNumberOfHW(); + homeworkDatabase.addHomework(homework.generateNewHomeworkById(id)); + settingsDatabase.saveTotalNumberOfHW(++id); + } + } + } + } + + public class ScheduleController { + private ClassDatabaseController classDatabase; + + public ScheduleController(Context context) { + classDatabase = new ClassDatabaseController(context); + } + + @NotNull + public List> getScheduleByDay(LocalDate day) { + WeekParityEnum weekParity = WeekParityEnum.values()[day.getWeekOfWeekyear() % 2]; + if (settingsDatabase.getParityOfWeek()) { + weekParity = weekParity.inverse(); + } + + List classes = classDatabase.getDaySchedule(day.getDayOfWeek() - 1, + weekParity); + List exams = examController.examDatabase.getExamsByDay(day); + List scheduleEntries = ListUtils.union(exams, classes); + Collections.sort(scheduleEntries); + + return ModelUtils.map(scheduleEntries, ModelUtils.SCHEDULE_ENTRY_TO_SCHEDULE_DESCRIPTION); + } + + public void addClass(@NotNull String subject, int dayOfWeek, int hour, + int minute, @NotNull WeekParityEnum weekParity, + @NotNull String auditorium, @NotNull String teacher) { + int id = settingsDatabase.getTotalNumberOfScheduleEntries(); + Class aClass = new Class(subject, dayOfWeek, hour, minute, + weekParity, auditorium, teacher, id); + classDatabase.addClass(aClass); + settingsDatabase.saveTotalNumberOfScheduleEntries(++id); + } + + public void deleteClassById(int id) { + classDatabase.deleteClassById(id); + } + + public void editClassById(int id, @NotNull String subject, int dayOfWeek, + int hour, int minute, @NotNull WeekParityEnum weekParity, + @NotNull String auditorium, @NotNull String teacher) { + classDatabase.editClassById(subject, dayOfWeek, hour, minute, + weekParity, auditorium, teacher, id); + } + + @NotNull + public Bundle getClassById(int id) { + return classDatabase.getClassById(id).getDeconstructed(); + } + } + + public class ExamController { + private ExamDatabaseController examDatabase; + + public ExamController(Context context) { + examDatabase = new ExamDatabaseController(context); + } + + @NotNull + public List> getExamsBySubject(@NotNull String subject) { + return ModelUtils.map(examDatabase.getExamsBySubject(subject), ModelUtils.EXAM_FIELDS_TO_STRING_LIST); + } + + @NotNull + public List> getExamsByDay(@NotNull LocalDate date) { + return ModelUtils.map(examDatabase.getExamsByDay(date), ModelUtils.EXAM_FIELDS_TO_STRING_LIST); + } + + public void addExam(@NotNull LocalDateTime date, @NotNull String subject, + @NotNull ExamEnum examEnum, String description) { + int id = settingsDatabase.getTotalNumberOfWorks(); + Exam exam = new Exam(subject, description, date, examEnum, id); + examDatabase.addExam(exam); + settingsDatabase.saveTotalNumberOfWorks(++id); + + if (subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + public void deleteExamsById(int id) { + examDatabase.deleteExamById(id); + } + + public void setExamAcceptedById(int id, boolean isAccepted) { + examDatabase.setAcceptedById(id, isAccepted); + } + + public void editExamById(int id, @NotNull LocalDateTime date, @NotNull String subject, + @NotNull ExamEnum examEnum, String description) { + examDatabase.editExamById(subject, description, date, examEnum, id); + if (subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + @NotNull + public Bundle getExamById(int id) { + return examDatabase.getExamById(id).getDeconstructed(); + } + } + + public class StudyMaterialController { + private StudyMaterialDatabaseController studyMaterialDatabase; + + public StudyMaterialController(Context context) { + studyMaterialDatabase = new StudyMaterialDatabaseController(context); + } + + public void addUpdatableStudyMaterial(@NotNull String name, @NotNull String subject, int term) + throws WebStudyMaterialException, StudyMaterialsSourceAccessException { + int id = settingsDatabase.getTotalNumberOfStudyMaterials(); + StudyMaterial studyMaterial = new StudyMaterial(name, subject, term, + appDirectory.getAbsolutePath(), 0, id); + File studyMaterialFile = new File(appDirectory.getAbsolutePath(), name); + + if (studyMaterialFile.exists()) { + throw new WebStudyMaterialException("The study material already exists.", + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST.apply(studyMaterial)); + } + updateStudyMaterial(studyMaterial); + studyMaterialDatabase.addStudyMaterial(studyMaterial); + settingsDatabase.saveTotalNumberOfStudyMaterials(++id); + + if (!subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + public void addLocalStudyMaterial(@NotNull String name, @NotNull String subject, + int term, @NotNull String path) { + int id = settingsDatabase.getTotalNumberOfStudyMaterials(); + StudyMaterial studyMaterial = new StudyMaterial(name, subject, term, path, -1, id); + studyMaterialDatabase.addStudyMaterial(studyMaterial); + settingsDatabase.saveTotalNumberOfStudyMaterials(++id); + + if (!subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + @NotNull + public List> getStudyMaterialsBySubject(@NotNull String subject) { + return ModelUtils.map(studyMaterialDatabase.getStudyMaterialsBySubject(subject), + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST); + } + + @NotNull + public List> getStudyMaterialsByTerm(int term) { + return ModelUtils.map(studyMaterialDatabase.getStudyMaterialsByTerm(term), + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST); + } + + @NotNull + public Bundle getStudyMaterialById(int id) { + return studyMaterialDatabase.getStudyMaterialById(id).getDeconstructed(); + } + + public void deleteStudyMaterialById(int id) { + studyMaterialDatabase.deleteStudyMaterialById(id); + } + + public void editStudyMaterialById(int id, @NotNull String subject, int term) { + studyMaterialDatabase.editStudyMaterialById(id, subject, term); + if (!subjectList.add(subject)) { + subjectDatabase.addSubject(subject, CreditFormEnum.NOT_STATED, -1); + } + } + + public void updateStudyMaterials() throws StudyMaterialsUpdatingException, UrlDownloadingException { + StudyMaterialsUpdater studyMaterialsUpdater; + StudyMaterial[] studyMaterials = studyMaterialDatabase.getUpdatableStudyMaterials() + .toArray(new StudyMaterial[0]); + + try { + studyMaterialsUpdater = new StudyMaterialsUpdater(); + studyMaterialsUpdater.execute(studyMaterials); + studyMaterialsUpdater.get(1, TimeUnit.MINUTES); + } catch (Exception exception) { + throw new UrlDownloadingException(); + } + + StudyMaterialsUpdatingException exception = studyMaterialsUpdater.getError(); + if (exception != null) { + throw exception; + } + } + + @NotNull + public List[] getAvailableStudyMaterialsList() throws StudyMaterialsSourceAccessException { + WebStudyMaterialsListGetter webStudyMaterialsListGetter = new WebStudyMaterialsListGetter(); + webStudyMaterialsListGetter.execute(); + AsyncTaskResult[], StudyMaterialsSourceAccessException> result; + + try { + result = webStudyMaterialsListGetter.get(1, TimeUnit.MINUTES); + } catch (Exception exception) { + throw new StudyMaterialsSourceAccessException(); + } + + StudyMaterialsSourceAccessException exception = result.getError(); + if (exception == null) { + return result.getResult(); + } else { + throw exception; + } + } + + @NotNull + public List searchForStudyMaterials(String query) throws UrlDownloadingException { + GoogleSearchRequester googleSearchRequester = new GoogleSearchRequester(); + googleSearchRequester.execute(query, appDirectory.getAbsolutePath()); + AsyncTaskResult, UrlDownloadingException> result; + + try { + result = googleSearchRequester.get(1, TimeUnit.MINUTES); + } catch (Exception exception) { + throw new UrlDownloadingException(); + } + + UrlDownloadingException exception = result.getError(); + if (exception == null) { + return result.getResult(); + } else { + throw exception; + } + + } + + private void updateStudyMaterial(StudyMaterial studyMaterial) + throws StudyMaterialsSourceAccessException, WebStudyMaterialException { + StudyMaterialsUpdater studyMaterialsUpdater; + try { + studyMaterialsUpdater = new StudyMaterialsUpdater(); + studyMaterialsUpdater.execute(studyMaterial); + studyMaterialsUpdater.get(1, TimeUnit.MINUTES); + } catch (Exception exception) { + throw new WebStudyMaterialException("Unable to download study material.", + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST.apply(studyMaterial)); + } + + StudyMaterialsUpdatingException exception = studyMaterialsUpdater.getError(); + if (exception != null) { + throw exception.getErrors().get(0); + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/ExamDatabaseController.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/ExamDatabaseController.java new file mode 100644 index 0000000..e514621 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/ExamDatabaseController.java @@ -0,0 +1,202 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.Exam; +import ru.spbau.group202.notdeadbydeadline.model.ExamEnum; + + +public class ExamDatabaseController extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "Exams"; + private static final int DATABASE_VERSION = 1; + private static final String COLUMN_NAME_ID = "ID"; + private static final String COLUMN_NAME_SUBJECT = "SUBJECT"; + private static final String COLUMN_NAME_YEAR = "YEAR"; + private static final String COLUMN_NAME_MONTH = "MONTH"; + private static final String COLUMN_NAME_DAY = "DAY"; + private static final String COLUMN_NAME_HOUR = "HOUR"; + private static final String COLUMN_NAME_MINUTE = "MINUTE"; + private static final String COLUMN_NAME_DESCRIPTION = "DESCRIPTION"; + private static final String COLUMN_NAME_EXAM_TYPE = "EXAM_TYPE"; + private static final String COLUMN_NAME_IS_ACCEPTED = "IS_ACCEPTED"; + + public ExamDatabaseController(@NotNull Context context) { + super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d("Database", "onCreate database"); + db.execSQL("CREATE TABLE " + DATABASE_NAME + " (" + + COLUMN_NAME_ID + " INTEGER PRIMARY KEY, " + + COLUMN_NAME_SUBJECT + " TEXT, " + + COLUMN_NAME_YEAR + " INTEGER, " + + COLUMN_NAME_MONTH + " INTEGER, " + + COLUMN_NAME_DAY + " INTEGER, " + + COLUMN_NAME_HOUR + " INTEGER, " + + COLUMN_NAME_MINUTE + " INTEGER, " + + COLUMN_NAME_DESCRIPTION + " TEXT, " + + COLUMN_NAME_EXAM_TYPE + " INTEGER, " + + COLUMN_NAME_IS_ACCEPTED + " INTEGER" + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldDATABASE_VERSION, int newDATABASE_VERSION) { + db.execSQL("DROP TABLE IF EXISTS " + DATABASE_NAME); + onCreate(db); + } + + @NotNull + private Exam getExamByCursor(@NotNull Cursor cursor) { + int id = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_ID)); + String subject = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_SUBJECT)); + int year = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_YEAR)); + int month = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_MONTH)); + int day = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_DAY)); + int hour = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_HOUR)); + int minute = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_MINUTE)); + String description = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_DESCRIPTION)); + int examType = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_EXAM_TYPE)); + boolean isAccepted = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_IS_ACCEPTED)) == 1; + + LocalDateTime date = new LocalDateTime(year, month, day, hour, minute); + Exam exam = new Exam(subject, description, date, ExamEnum.values()[examType], id); + exam.setAccepted(isAccepted); + return exam; + } + + public void addExam(@NotNull Exam exam) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_ID, exam.getId()); + values.put(COLUMN_NAME_SUBJECT, exam.getSubject()); + values.put(COLUMN_NAME_YEAR, exam.getYear()); + values.put(COLUMN_NAME_MONTH, exam.getMonth()); + values.put(COLUMN_NAME_DAY, exam.getDay()); + values.put(COLUMN_NAME_HOUR, exam.getHour()); + values.put(COLUMN_NAME_MINUTE, exam.getMinute()); + values.put(COLUMN_NAME_DESCRIPTION, exam.getDescription()); + values.put(COLUMN_NAME_EXAM_TYPE, exam.getExamType().ordinal()); + values.put(COLUMN_NAME_IS_ACCEPTED, exam.isAccepted() ? 1 : 0); + long rowId = database.insert(DATABASE_NAME, null, values); + Log.d("Database", "inserted row number " + rowId); + } + } + + @NotNull + public List getExamsBySubject(@NotNull String subject) { + String query = "SELECT * FROM " + DATABASE_NAME + + " WHERE " + COLUMN_NAME_SUBJECT + "=" + "?"; + List exams = new ArrayList<>(); + String[] selectionArgs = new String[]{String.valueOf(subject)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + exams.add(getExamByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + Collections.sort(exams); + return exams; + } + + @NotNull + public List getAllExams() { + String query = "SELECT * FROM " + DATABASE_NAME; + List exams = new ArrayList<>(); + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + exams.add(getExamByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + return exams; + } + + @NotNull + public List getExamsByDay(LocalDate date) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_YEAR + "=? " + + "AND " + COLUMN_NAME_MONTH + "=? " + "AND " + COLUMN_NAME_DAY + "=?" + + "ORDER BY " + COLUMN_NAME_HOUR + ", " + COLUMN_NAME_MINUTE + " ASC"; + String[] selectionArgs = new String[]{String.valueOf(date.getYear()), + String.valueOf(date.getMonthOfYear()), String.valueOf(date.getDayOfMonth())}; + List exams = new ArrayList<>(); + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + Exam exam = getExamByCursor(cursor); + exams.add(exam); + } while (cursor.moveToNext()); + } + } + + return exams; + } + + public void deleteExamById(int id) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + database.delete(DATABASE_NAME, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + public void setAcceptedById(int id, boolean isAccepted) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_IS_ACCEPTED, isAccepted); + database.update(DATABASE_NAME, values, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + @NotNull + public Exam getExamById(int id) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_ID + "=?"; + String[] selectionArgs = new String[]{String.valueOf(id)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + cursor.moveToFirst(); + return getExamByCursor(cursor); + } + } + + public void editExamById(@NotNull String subject, @NotNull String description, + @NotNull LocalDateTime date, @NotNull ExamEnum examEnum, int id) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_SUBJECT, subject); + values.put(COLUMN_NAME_YEAR, date.getYear()); + values.put(COLUMN_NAME_MONTH, date.getMonthOfYear()); + values.put(COLUMN_NAME_DAY, date.getDayOfMonth()); + values.put(COLUMN_NAME_HOUR, date.getHourOfDay()); + values.put(COLUMN_NAME_MINUTE, date.getMinuteOfHour()); + values.put(COLUMN_NAME_DESCRIPTION, description); + values.put(COLUMN_NAME_EXAM_TYPE, examEnum.ordinal()); + database.update(DATABASE_NAME, values, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/GoogleSearchRequester.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/GoogleSearchRequester.java new file mode 100644 index 0000000..760ccc4 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/GoogleSearchRequester.java @@ -0,0 +1,53 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + + +import android.os.AsyncTask; + +import org.apache.commons.io.FileUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.utilities.AsyncTaskResult; +import ru.spbau.group202.notdeadbydeadline.model.utilities.UrlDownloadingException; + +public class GoogleSearchRequester + extends AsyncTask, UrlDownloadingException>> { + private static final String GOOGLE_SEARCH_URL = "https://www.google.com/search"; + private static final int NUMBER_OF_SEARCH_TRIES = 5; + + @Override + protected AsyncTaskResult, UrlDownloadingException> doInBackground(String... strings) { + ArrayList results = new ArrayList<>(); + String query = strings[0]; + String appDirectory = strings[1]; + String searchURL = GOOGLE_SEARCH_URL + "?q=" + "filetype:pdf " + "+" + + query.replace(" ", "+") + "&num=" + + Integer.toString(NUMBER_OF_SEARCH_TRIES); + + try { + Document html = Jsoup.connect(searchURL).userAgent("Mozilla").timeout(5000).get(); + Elements links = html.select("h3.r > a"); + for (Element link : links) { + String stringUrl = link.attr("href").replace("/url?q=", "").replaceAll("\\.pdf.*", ".pdf"); + URL url = new URL(stringUrl); + File searchResultsFolder = new File(appDirectory + File.separator + "search"); + searchResultsFolder.mkdir(); + File foundMaterial = new File(searchResultsFolder.getAbsolutePath(), link.text()); + FileUtils.copyURLToFile(url, foundMaterial); + results.add(foundMaterial); + } + } catch (IOException exception) { + return new AsyncTaskResult<>(new UrlDownloadingException()); + } + + return new AsyncTaskResult<>(results); + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/HomeworkDatabaseController.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/HomeworkDatabaseController.java new file mode 100644 index 0000000..cbe7057 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/HomeworkDatabaseController.java @@ -0,0 +1,251 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.Homework; + +public class HomeworkDatabaseController extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "Homeworks"; + private static final int DATABASE_VERSION = 1; + private static final String COLUMN_NAME_ID = "ID"; + private static final String COLUMN_NAME_SUBJECT = "SUBJECT"; + private static final String COLUMN_NAME_YEAR = "YEAR"; + private static final String COLUMN_NAME_MONTH = "MONTH"; + private static final String COLUMN_NAME_DAY = "DAY"; + private static final String COLUMN_NAME_HOUR = "HOUR"; + private static final String COLUMN_NAME_MINUTE = "MINUTE"; + private static final String COLUMN_NAME_REGULARITY = "REGULARITY"; + private static final String COLUMN_NAME_EXPECTED_SCORE = "EXPECTED_SCORE"; + private static final String COLUMN_NAME_ACTUAL_SCORE = "ACTUAL_SCORE"; + private static final String COLUMN_NAME_DESCRIPTION = "DESCRIPTION"; + private static final String COLUMN_NAME_HOW_TO_SEND = "HOW_TO_SEND"; + private static final String COLUMN_NAME_DEFERRAL = "DEFERRAL"; + private static final String COLUMN_NAME_MATERIALS = "MATERIALS"; + + public HomeworkDatabaseController(@NotNull Context context) { + super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d("Database", "onCreate database"); + db.execSQL("CREATE TABLE " + DATABASE_NAME + " (" + + COLUMN_NAME_ID + " INTEGER PRIMARY KEY, " + + COLUMN_NAME_SUBJECT + " TEXT, " + + COLUMN_NAME_YEAR + " INTEGER, " + + COLUMN_NAME_MONTH + " INTEGER, " + + COLUMN_NAME_DAY + " INTEGER, " + + COLUMN_NAME_HOUR + " INTEGER, " + + COLUMN_NAME_MINUTE + " INTEGER, " + + COLUMN_NAME_REGULARITY + " INTEGER, " + + COLUMN_NAME_EXPECTED_SCORE + " REAL, " + + COLUMN_NAME_ACTUAL_SCORE + " INTEGER, " + + COLUMN_NAME_DESCRIPTION + " TEXT, " + + COLUMN_NAME_HOW_TO_SEND + " TEXT, " + + COLUMN_NAME_DEFERRAL + " INTEGER, " + + COLUMN_NAME_MATERIALS + " TEXT" + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldDATABASE_VERSION, int newDATABASE_VERSION) { + db.execSQL("DROP TABLE IF EXISTS " + DATABASE_NAME); + onCreate(db); + } + + @NotNull + private Homework getHomeworkByCursor(@NotNull Cursor cursor) { + int id = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_ID)); + String subject = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_SUBJECT)); + int year = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_YEAR)); + int month = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_MONTH)); + int day = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_DAY)); + int hour = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_HOUR)); + int minute = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_MINUTE)); + int regularity = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_REGULARITY)); + double expectedScore = cursor.getDouble(cursor.getColumnIndex(COLUMN_NAME_EXPECTED_SCORE)); + int actualScore = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_ACTUAL_SCORE)); + String description = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_DESCRIPTION)); + String howToSend = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_HOW_TO_SEND)); + int deferral = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_DEFERRAL)); + String gsonMaterials = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_MATERIALS)); + ArrayList materials = new Gson().fromJson(gsonMaterials, + new TypeToken>() { + }.getType()); + + + LocalDateTime deadline = new LocalDateTime(year, month, day, hour, minute); + Homework homework = new Homework(deadline, subject, regularity, description, howToSend, + expectedScore, id, materials); + homework.setActualScore(actualScore); + homework.assignDeferral(deferral); + return homework; + } + + public void addHomework(@NotNull Homework homework) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_ID, homework.getId()); + values.put(COLUMN_NAME_SUBJECT, homework.getSubject()); + values.put(COLUMN_NAME_YEAR, homework.getYear()); + values.put(COLUMN_NAME_MONTH, homework.getMonth()); + values.put(COLUMN_NAME_DAY, homework.getDay()); + values.put(COLUMN_NAME_HOUR, homework.getHour()); + values.put(COLUMN_NAME_MINUTE, homework.getMinute()); + values.put(COLUMN_NAME_REGULARITY, homework.getRegularity()); + values.put(COLUMN_NAME_EXPECTED_SCORE, homework.getExpectedScore()); + values.put(COLUMN_NAME_ACTUAL_SCORE, homework.getActualScore()); + values.put(COLUMN_NAME_DESCRIPTION, homework.getDescription()); + values.put(COLUMN_NAME_HOW_TO_SEND, homework.getHowToSend()); + values.put(COLUMN_NAME_DEFERRAL, homework.getDeferral()); + values.put(COLUMN_NAME_MATERIALS, new Gson().toJson(homework.getMaterials())); + long rowId = database.insert(DATABASE_NAME, null, values); + Log.d("Database", "inserted row number " + rowId); + } + } + + @NotNull + public List getHomeworksBySubject(@NotNull String subject) { + String query = "SELECT * FROM " + DATABASE_NAME + + " WHERE " + COLUMN_NAME_SUBJECT + "=" + "?"; + List homeworks = new ArrayList<>(); + String[] selectionArgs = new String[]{String.valueOf(subject)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + homeworks.add(getHomeworkByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + Collections.sort(homeworks, Homework::compareTo); + return homeworks; + } + + @NotNull + public List getActualHomeworks() { + String query = "SELECT * FROM " + DATABASE_NAME; + List homeworks = new ArrayList<>(); + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + Homework homework = getHomeworkByCursor(cursor); + if(!homework.hasPassed()) { + homeworks.add(homework); + } + } while (cursor.moveToNext()); + } + } + + return homeworks; + } + + @NotNull + public List getPassedHomeworksBySubject(@NotNull String subject) { + List passedHomeworks = new ArrayList<>(); + for (Homework homework : getHomeworksBySubject(subject)) { + if (homework.hasPassed()) { + passedHomeworks.add(homework); + } + } + + return passedHomeworks; + } + + @NotNull + public List getHomeworksByDay(LocalDate date) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_YEAR + "=? " + + "AND " + COLUMN_NAME_MONTH + "=? " + "AND " + COLUMN_NAME_DAY + "=?" + + "ORDER BY " + COLUMN_NAME_HOUR + ", " + COLUMN_NAME_MINUTE + " ASC"; + String[] selectionArgs = new String[]{String.valueOf(date.getYear()), + String.valueOf(date.getMonthOfYear()), String.valueOf(date.getDayOfMonth())}; + List homeworks = new ArrayList<>(); + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + Homework homework = getHomeworkByCursor(cursor); + homeworks.add(homework); + } while (cursor.moveToNext()); + } + } + + return homeworks; + } + + public void deleteHomeworkById(int id) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + database.delete(DATABASE_NAME, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + public void setScoreById(int id, int score) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_ACTUAL_SCORE, score); + database.update(DATABASE_NAME, values, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + public void setDeferralById(int id, int deferral) { + Homework homework = getHomeworkById(id); + deleteHomeworkById(id); + homework.setDeferral(deferral); + addHomework(homework); + } + + @NotNull + public Homework getHomeworkById(int id) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_ID + "=?"; + String[] selectionArgs = new String[]{String.valueOf(id)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + cursor.moveToFirst(); + return getHomeworkByCursor(cursor); + } + } + + public void editHomeworkById(LocalDateTime deadline, @NotNull String subject, int regularity, + @NotNull String description, @NotNull String howToSend, + double expectedScore, int id, @NotNull ArrayList materials) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_SUBJECT, subject); + values.put(COLUMN_NAME_YEAR, deadline.getYear()); + values.put(COLUMN_NAME_MONTH, deadline.getMonthOfYear()); + values.put(COLUMN_NAME_DAY, deadline.getDayOfMonth()); + values.put(COLUMN_NAME_HOUR, deadline.getHourOfDay()); + values.put(COLUMN_NAME_MINUTE, deadline.getMinuteOfHour()); + values.put(COLUMN_NAME_REGULARITY, regularity); + values.put(COLUMN_NAME_EXPECTED_SCORE, expectedScore); + values.put(COLUMN_NAME_DESCRIPTION, description); + values.put(COLUMN_NAME_HOW_TO_SEND, howToSend); + values.put(COLUMN_NAME_MATERIALS, new Gson().toJson(materials)); + database.update(DATABASE_NAME, values, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StoredDataController.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StoredDataController.java new file mode 100644 index 0000000..05c8466 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StoredDataController.java @@ -0,0 +1,74 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + + +import android.content.Context; +import android.content.SharedPreferences; + +import org.jetbrains.annotations.NotNull; + +public class StoredDataController { + private static final String APP_PREFERENCES = "Settings"; + private static final String APP_PREFERENCES_INVERSE_WEEK_PARITY = "INVERSE_WEEK_PARITY"; + private static final String APP_PREFERENCES_TOTAL_NUMBER_OF_HW = "TOTAL_NUMBER_OF_HW"; + private static final String APP_PREFERENCES_TOTAL_NUMBER_OF_SCHEDULE_ENTRIES + = "TOTAL_NUMBER_OF_SCHEDULE_ENTRIES"; + private static final String APP_PREFERENCES_TOTAL_NUMBER_OF_EXAMS = "TOTAL_NUMBER_OF_EXAMS"; + private static final String APP_PREFERENCES_TOTAL_NUMBER_OF_STUDY_MATERIALS + = "TOTAL_NUMBER_OF_STUDY_MATERIALS"; + private SharedPreferences settings; + + public StoredDataController(@NotNull Context context) { + settings = context.getApplicationContext() + .getSharedPreferences(APP_PREFERENCES, Context.MODE_PRIVATE); + } + + public void saveWeekPairity(boolean isInversed) { + SharedPreferences.Editor editor = settings.edit(); + editor.putBoolean(APP_PREFERENCES_INVERSE_WEEK_PARITY, isInversed); + editor.apply(); + } + + public void saveTotalNumberOfHW(int totalNumberOfHW) { + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(APP_PREFERENCES_TOTAL_NUMBER_OF_HW, totalNumberOfHW); + editor.apply(); + } + + public void saveTotalNumberOfScheduleEntries(int totalNumberOfScheduleEntries) { + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(APP_PREFERENCES_TOTAL_NUMBER_OF_SCHEDULE_ENTRIES, totalNumberOfScheduleEntries); + editor.apply(); + } + + public void saveTotalNumberOfWorks(int totalNumberOfWorks) { + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(APP_PREFERENCES_TOTAL_NUMBER_OF_EXAMS, totalNumberOfWorks); + editor.apply(); + } + + public void saveTotalNumberOfStudyMaterials(int totalNumberOfStudyMaterials) { + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(APP_PREFERENCES_TOTAL_NUMBER_OF_STUDY_MATERIALS, totalNumberOfStudyMaterials); + editor.apply(); + } + + public boolean getParityOfWeek() { + return settings.getBoolean(APP_PREFERENCES_INVERSE_WEEK_PARITY, false); + } + + public int getTotalNumberOfHW() { + return settings.getInt(APP_PREFERENCES_TOTAL_NUMBER_OF_HW, 0); + } + + public int getTotalNumberOfScheduleEntries() { + return settings.getInt(APP_PREFERENCES_TOTAL_NUMBER_OF_SCHEDULE_ENTRIES, 0); + } + + public int getTotalNumberOfWorks() { + return settings.getInt(APP_PREFERENCES_TOTAL_NUMBER_OF_EXAMS, 0); + } + + public int getTotalNumberOfStudyMaterials() { + return settings.getInt(APP_PREFERENCES_TOTAL_NUMBER_OF_STUDY_MATERIALS, 0); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StudyMaterialDatabaseController.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StudyMaterialDatabaseController.java new file mode 100644 index 0000000..230b320 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StudyMaterialDatabaseController.java @@ -0,0 +1,160 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.StudyMaterial; + +public class StudyMaterialDatabaseController extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "StudyMaterials"; + private static final int DATABASE_VERSION = 1; + private static final String COLUMN_NAME_ID = "ID"; + private static final String COLUMN_NAME_SUBJECT = "SUBJECT"; + private static final String COLUMN_NAME_TERM = "TERM"; + private static final String COLUMN_NAME_VERSION = "VERSION"; + private static final String COLUMN_NAME_PATH = "PATH"; + private static final String COLUMN_NAME_NAME = "NAME"; + + public StudyMaterialDatabaseController(@NotNull Context context) { + super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d("Database", "onCreate database"); + db.execSQL("CREATE TABLE " + DATABASE_NAME + " (" + + COLUMN_NAME_ID + " INTEGER PRIMARY KEY, " + + COLUMN_NAME_SUBJECT + " TEXT, " + + COLUMN_NAME_TERM + " INTEGER, " + + COLUMN_NAME_VERSION + " INTEGER, " + + COLUMN_NAME_PATH + " TEXT, " + + COLUMN_NAME_NAME + " TEXT" + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldDATABASE_VERSION, int newDATABASE_VERSION) { + db.execSQL("DROP TABLE IF EXISTS " + DATABASE_NAME); + onCreate(db); + } + + @NotNull + private StudyMaterial getStudyMaterialByCursor(@NotNull Cursor cursor) { + int id = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_ID)); + String subject = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_SUBJECT)); + int term = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_TERM)); + int version = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_VERSION)); + String path = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_PATH)); + String name = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_NAME)); + return new StudyMaterial(name, subject, term, path, version, id); + } + + public void addStudyMaterial(@NotNull StudyMaterial studyMaterial) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_ID, studyMaterial.getId()); + values.put(COLUMN_NAME_SUBJECT, studyMaterial.getSubject()); + values.put(COLUMN_NAME_TERM, studyMaterial.getTerm()); + values.put(COLUMN_NAME_VERSION, studyMaterial.getVersion()); + values.put(COLUMN_NAME_PATH, studyMaterial.getPath()); + values.put(COLUMN_NAME_NAME, studyMaterial.getName()); + long rowId = database.insert(DATABASE_NAME, null, values); + Log.d("Database", "inserted row number " + rowId); + } + } + + @NotNull + public List getStudyMaterialsBySubject(@NotNull String subject) { + String query = "SELECT * FROM " + DATABASE_NAME + + " WHERE " + COLUMN_NAME_SUBJECT + "=" + "?"; + List studyMaterials = new ArrayList<>(); + String[] selectionArgs = new String[]{String.valueOf(subject)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + studyMaterials.add(getStudyMaterialByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + return studyMaterials; + } + + @NotNull + public List getStudyMaterialsByTerm(int term) { + String query = "SELECT * FROM " + DATABASE_NAME + + " WHERE " + COLUMN_NAME_TERM + "=" + "?"; + List studyMaterials = new ArrayList<>(); + String[] selectionArgs = new String[]{String.valueOf(term)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + studyMaterials.add(getStudyMaterialByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + return studyMaterials; + } + + public void deleteStudyMaterialById(int id) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + database.delete(DATABASE_NAME, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + @NotNull + public StudyMaterial getStudyMaterialById(int id) { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_ID + "=?"; + String[] selectionArgs = new String[]{String.valueOf(id)}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + cursor.moveToFirst(); + return getStudyMaterialByCursor(cursor); + } + } + + public void editStudyMaterialById(int id, @NotNull String subject, int term) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_SUBJECT, subject); + values.put(COLUMN_NAME_TERM, term); + database.update(DATABASE_NAME, values, COLUMN_NAME_ID + " = ?", + new String[]{String.valueOf(id)}); + } + } + + @NotNull + public List getUpdatableStudyMaterials() { + String query = "SELECT * FROM " + DATABASE_NAME + + " WHERE " + COLUMN_NAME_VERSION + "!=" + "?"; + List studyMaterials = new ArrayList<>(); + String[] selectionArgs = new String[]{"-1"}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + if (cursor != null && cursor.moveToFirst()) { + do { + studyMaterials.add(getStudyMaterialByCursor(cursor)); + } while (cursor.moveToNext()); + } + } + + return studyMaterials; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StudyMaterialsUpdater.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StudyMaterialsUpdater.java new file mode 100644 index 0000000..9771c27 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/StudyMaterialsUpdater.java @@ -0,0 +1,85 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + + +import android.os.AsyncTask; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ru.spbau.group202.notdeadbydeadline.model.StudyMaterial; +import ru.spbau.group202.notdeadbydeadline.model.utilities.ModelUtils; +import ru.spbau.group202.notdeadbydeadline.model.utilities.StudyMaterialsSourceAccessException; +import ru.spbau.group202.notdeadbydeadline.model.utilities.StudyMaterialsUpdatingException; +import ru.spbau.group202.notdeadbydeadline.model.utilities.WebStudyMaterialException; + + +public class StudyMaterialsUpdater extends AsyncTask { + private StudyMaterialsUpdatingException updatingException; + private String urlContent; + private static final String STUDY_MATERIAL_SOURCE = "https://cdkrot.me/"; + + public StudyMaterialsUpdater() throws StudyMaterialsSourceAccessException { + try { + urlContent = IOUtils.toString(new URL(STUDY_MATERIAL_SOURCE)); + } catch (IOException exception) { + throw new StudyMaterialsSourceAccessException(); + } + } + + public StudyMaterialsUpdatingException getError() { + return updatingException; + } + + @Override + protected Void doInBackground(StudyMaterial... studyMaterials) { + ArrayList errors = new ArrayList<>(); + for (StudyMaterial studyMaterial : studyMaterials) { + try { + updateStudyMaterial(studyMaterial); + } catch (WebStudyMaterialException exception) { + errors.add(exception); + } + } + + if (!errors.isEmpty()) { + updatingException = new StudyMaterialsUpdatingException(errors); + } + + return null; + } + + private void updateStudyMaterial(StudyMaterial studyMaterial) throws WebStudyMaterialException { + Pattern urlPattern = Pattern.compile("href=\"(/.+/" + studyMaterial.getName() + + "/(\\d+)\\.pdf)\""); + Matcher urlMatcher = urlPattern.matcher(urlContent); + + if (!urlMatcher.find()) { + throw new WebStudyMaterialException("No such study material at study material's resource.", + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST.apply(studyMaterial)); + } + int newVersion = Integer.valueOf(urlMatcher.group(2)); + if (newVersion > studyMaterial.getVersion()) { + try { + URL studyMaterialUrl = new URL(STUDY_MATERIAL_SOURCE + urlMatcher.group(1)); + File studyMaterialFile = new File(studyMaterial.getPath(), studyMaterial.getName()); + FileUtils.copyURLToFile(studyMaterialUrl, studyMaterialFile); + } catch (MalformedURLException e) { + throw new WebStudyMaterialException("Malformed URL.", + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST.apply(studyMaterial)); + } catch (IOException e) { + throw new WebStudyMaterialException("Unable to download study material.", + ModelUtils.STUDY_MATERIAL_FIELDS_TO_STRING_LIST.apply(studyMaterial)); + } + studyMaterial.setVersion(Integer.valueOf(urlMatcher.group(2))); + } + } + +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/SubjectDatabaseController.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/SubjectDatabaseController.java new file mode 100644 index 0000000..b4576ef --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/SubjectDatabaseController.java @@ -0,0 +1,112 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.CreditByAcceptedHomeworks; +import ru.spbau.group202.notdeadbydeadline.model.CreditByPercent; +import ru.spbau.group202.notdeadbydeadline.model.CreditFormEnum; +import ru.spbau.group202.notdeadbydeadline.model.SubjectCredit; +import ru.spbau.group202.notdeadbydeadline.model.utilities.UnrecognizedCreditFormException; + + +public class SubjectDatabaseController extends SQLiteOpenHelper { + private static final String DATABASE_NAME = "Subjects"; + private static final int DATABASE_VERSION = 1; + private static final String COLUMN_NAME_SUBJECT = "SUBJECT"; + private static final String COLUMN_NAME_CREDIT_FORM = "CREDIT_FORM"; + private static final String COLUMN_NAME_PERCENT_FOR_CREDIT = "PERCENT_FOR_CREDIT"; + + public SubjectDatabaseController(@NotNull Context context) { + super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Log.d("Database", "onCreate database"); + db.execSQL("CREATE TABLE " + DATABASE_NAME + " (" + + COLUMN_NAME_SUBJECT + " TEXT PRIMARY KEY, " + + COLUMN_NAME_CREDIT_FORM + " INTEGER, " + + COLUMN_NAME_PERCENT_FOR_CREDIT + " REAL" + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldDATABASE_VERSION, int newDATABASE_VERSION) { + db.execSQL("DROP TABLE IF EXISTS " + DATABASE_NAME); + onCreate(db); + } + + public void addSubject(@NotNull String subject, @NotNull CreditFormEnum creditForm, double percentForCredit) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_SUBJECT, subject); + values.put(COLUMN_NAME_CREDIT_FORM, creditForm.ordinal()); + values.put(COLUMN_NAME_PERCENT_FOR_CREDIT, percentForCredit); + long rowId = database.insert(DATABASE_NAME, null, values); + Log.d("Database", "inserted row number " + rowId); + } + } + + @NotNull + public List getAllSubjects() { + ArrayList subjects = new ArrayList<>(); + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery("SELECT " + COLUMN_NAME_SUBJECT + + " FROM " + DATABASE_NAME, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + String subject = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_SUBJECT)); + subjects.add(subject); + } while (cursor.moveToNext()); + } + } + + return subjects; + } + + @NotNull + private SubjectCredit getSubjectCreditByCursor(@NotNull Cursor cursor) throws UnrecognizedCreditFormException { + int creditForm = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_CREDIT_FORM)); + double percentForCredit = cursor.getInt(cursor.getColumnIndex(COLUMN_NAME_PERCENT_FOR_CREDIT)); + + switch (CreditFormEnum.values()[creditForm]) { + case BY_PERCENT: + return new CreditByPercent(percentForCredit); + case BY_ACCEPTED_HOMEWORKS: + return new CreditByAcceptedHomeworks(); + case NOT_STATED: + return new SubjectCredit(); + default: + throw new UnrecognizedCreditFormException(); + } + } + + @NotNull + public SubjectCredit getSubjectCredit(@NotNull String subject) throws UnrecognizedCreditFormException { + String query = "SELECT * FROM " + DATABASE_NAME + " WHERE " + COLUMN_NAME_SUBJECT + "=?"; + String[] selectionArgs = new String[]{subject}; + + try (SQLiteDatabase database = this.getReadableDatabase(); + Cursor cursor = database.rawQuery(query, selectionArgs)) { + return getSubjectCreditByCursor(cursor); + } + } + + public void setSubjectCreditForm(@NotNull String subject, @NotNull CreditFormEnum credit) { + try (SQLiteDatabase database = this.getWritableDatabase()) { + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME_CREDIT_FORM, credit.ordinal()); + database.update(DATABASE_NAME, values, COLUMN_NAME_SUBJECT + " = ?", + new String[]{subject}); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/WebStudyMaterialsListGetter.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/WebStudyMaterialsListGetter.java new file mode 100644 index 0000000..2ae5bf0 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/controller/WebStudyMaterialsListGetter.java @@ -0,0 +1,58 @@ +package ru.spbau.group202.notdeadbydeadline.controller; + + +import android.os.AsyncTask; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import ru.spbau.group202.notdeadbydeadline.model.utilities.AsyncTaskResult; +import ru.spbau.group202.notdeadbydeadline.model.utilities.StudyMaterialsSourceAccessException; + + +public class WebStudyMaterialsListGetter + extends AsyncTask[], StudyMaterialsSourceAccessException>> { + private static final String STUDY_MATERIAL_SOURCE = "https://cdkrot.me/"; + + @Override + protected AsyncTaskResult[], StudyMaterialsSourceAccessException> doInBackground(Void... voids) { + @SuppressWarnings("unchecked") + ArrayList[] materialsByTerms = new ArrayList[7]; + for (int term = 0; term < materialsByTerms.length; term++) { + materialsByTerms[term] = new ArrayList<>(); + } + + try { + Document html = Jsoup.connect(STUDY_MATERIAL_SOURCE).get(); + Elements materialsUrl = html.select("a[href$=.pdf]"); + + Pattern urlPattern = Pattern.compile(".+/(.+/.*?term(\\d).+)/\\d+\\.pdf"); + Pattern noTermPattern = Pattern.compile(STUDY_MATERIAL_SOURCE + "(.+)/\\d+\\.pdf"); + for (Element materialUrl : materialsUrl) { + String absUrl = materialUrl.attr("abs:href"); + Matcher urlMatcher = urlPattern.matcher(absUrl); + if (urlMatcher.find()) { + materialsByTerms[Integer.valueOf(urlMatcher.group(2))].add(urlMatcher.group(1)); + } else { + Matcher noTermMatcher = noTermPattern.matcher(absUrl); + if (noTermMatcher.find()) { + materialsByTerms[0].add(noTermMatcher.group(1)); + } + + } + } + } catch (IOException exception) { + return new AsyncTaskResult<>(new StudyMaterialsSourceAccessException()); + } + + return new AsyncTaskResult<>(materialsByTerms); + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Class.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Class.java new file mode 100644 index 0000000..57678c4 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Class.java @@ -0,0 +1,96 @@ +package ru.spbau.group202.notdeadbydeadline.model; + +import android.os.Bundle; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalTime; +import org.joda.time.format.DateTimeFormat; + +import java.util.ArrayList; +import java.util.List; + + +public class Class extends ScheduleEntry { + private String subject; + private String auditorium; + private String teacher; + private LocalTime time; + private int dayOfWeek; + private int id; + private WeekParityEnum weekParity; + + public Class(@NotNull String subject, int dayOfWeek, int hour, int minute, + WeekParityEnum weekParity, String auditorium, String teacher, int id) { + time = new LocalTime(hour, minute); + this.subject = subject; + this.dayOfWeek = dayOfWeek; + this.weekParity = weekParity; + this.auditorium = auditorium; + this.teacher = teacher; + this.id = id; + } + + @NotNull + public Bundle getDeconstructed() { + Bundle bundle = new Bundle(); + bundle.putString("subject", subject); + bundle.putString("auditorium", auditorium); + bundle.putString("teacher", teacher); + bundle.putInt("id", id); + bundle.putInt("dayOfWeek", dayOfWeek); + bundle.putSerializable("weekParity", weekParity); + bundle.putSerializable("time", time); + return bundle; + } + + @NotNull + @Override + protected LocalTime getTime() { + return time; + } + + @NotNull + @Override + public List getScheduleDescription() { + ArrayList classDetails = new ArrayList<>(); + classDetails.add(subject); + classDetails.add(DateTimeFormat.forPattern("HH:mm").print(time)); + classDetails.add(teacher); + classDetails.add(auditorium); + classDetails.add(Integer.toString(id)); + return classDetails; + } + + public int getHour() { + return time.getHourOfDay(); + } + + public int getMinute() { + return time.getMinuteOfHour(); + } + + @NotNull + public String getSubject() { + return subject; + } + + public String getAuditorium() { + return auditorium; + } + + public String getTeacher() { + return teacher; + } + + public int getDayOfWeek() { + return dayOfWeek; + } + + public WeekParityEnum getWeekParity() { + return weekParity; + } + + public int getId() { + return id; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditByAcceptedHomeworks.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditByAcceptedHomeworks.java new file mode 100644 index 0000000..1e5aa4a --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditByAcceptedHomeworks.java @@ -0,0 +1,31 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class CreditByAcceptedHomeworks extends SubjectCredit { + @NotNull + @Override + protected List calculateHomeworkProgress(@NotNull List homeworks) { + int numberOfCheckedHomeworks = 0; + int numberOfAcceptedHomeworks = 0; + + for (Homework homework : homeworks) { + if (homework.getActualScore() != -1 && homework.getExpectedScore() != -1) { + numberOfCheckedHomeworks++; + if (homework.isAccepted()) { + numberOfAcceptedHomeworks++; + } + } + } + double percent = numberOfCheckedHomeworks == 0 ? 1 : (double) numberOfAcceptedHomeworks / numberOfCheckedHomeworks; + String credit = percent == 1.0 ? "Passed class" : "Failed class"; + int numberOfNotAcceptedHomeworks = numberOfCheckedHomeworks - numberOfAcceptedHomeworks; + + return Arrays.asList("by accepted homeworks", Double.toString(percent), credit, + Integer.toString(numberOfNotAcceptedHomeworks)); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditByPercent.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditByPercent.java new file mode 100644 index 0000000..8dc2233 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditByPercent.java @@ -0,0 +1,35 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class CreditByPercent extends SubjectCredit { + private double percentForCredit; + + public CreditByPercent(double percentForCredit) { + this.percentForCredit = percentForCredit; + } + + @NotNull + @Override + protected List calculateHomeworkProgress(@NotNull List homeworks) { + double totalPoints = 0; + double earnedPoints = 0; + + for (Homework homework : homeworks) { + if (homework.getActualScore() != -1 && homework.getExpectedScore() != -1) { + earnedPoints += homework.getActualScore(); + totalPoints += homework.getExpectedScore(); + } + } + + double percent = totalPoints == 0 ? 1 : earnedPoints / totalPoints; + String result = percent >= percentForCredit ? "Class passed" : "Class failed"; + double pointsForCredit = percentForCredit * totalPoints - earnedPoints; + return Arrays.asList("by percent", Double.toString(percent), result, + pointsForCredit < 0 ? "0" : Double.toString(pointsForCredit)); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditFormEnum.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditFormEnum.java new file mode 100644 index 0000000..8ce8b21 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/CreditFormEnum.java @@ -0,0 +1,6 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +public enum CreditFormEnum { + BY_ACCEPTED_HOMEWORKS, BY_PERCENT, NOT_STATED +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Exam.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Exam.java new file mode 100644 index 0000000..654efa4 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Exam.java @@ -0,0 +1,106 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +import android.os.Bundle; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDateTime; +import org.joda.time.LocalTime; +import org.joda.time.format.DateTimeFormat; + +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.model.utilities.ModelUtils; + + +public class Exam extends ScheduleEntry { + private String subject; + private String description; + private LocalDateTime date; + private boolean isAccepted = false; + private ExamEnum examType; + private int id; + + public Exam(@NotNull String subject, String description, @NotNull LocalDateTime date, + @NotNull ExamEnum examType, int id) { + this.subject = subject; + this.description = description; + this.date = date; + this.examType = examType; + this.id = id; + } + + public boolean isAccepted() { + return isAccepted; + } + + public void setAccepted(boolean isAccepted) { + this.isAccepted = isAccepted; + } + + @NotNull + public String getSubject() { + return subject; + } + + public String getDescription() { + return description; + } + + public int getYear() { + return date.getYear(); + } + + public int getMonth() { + return date.getMonthOfYear(); + } + + public int getDay() { + return date.getDayOfMonth(); + } + + public int getHour() { + return date.getHourOfDay(); + } + + public int getMinute() { + return date.getMinuteOfHour(); + } + + @NotNull + public ExamEnum getExamType() { + return examType; + } + + public int getId() { + return id; + } + + public String getFormattedTime() { + return DateTimeFormat.forPattern("HH:mm").print(date); + } + + @Override + @NotNull + public List getScheduleDescription() { + return ModelUtils.EXAM_FIELDS_TO_STRING_LIST.apply(this); + } + + @NotNull + public Bundle getDeconstructed() { + Bundle bundle = new Bundle(); + bundle.putString("subject", subject); + bundle.putString("description", description); + bundle.putSerializable("type", examType); + bundle.putBoolean("isAccepted", isAccepted); + bundle.putInt("id", id); + bundle.putSerializable("date", date); + return bundle; + } + + @NotNull + @Override + protected LocalTime getTime() { + return date.toLocalTime(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/ExamEnum.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/ExamEnum.java new file mode 100644 index 0000000..c463722 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/ExamEnum.java @@ -0,0 +1,17 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +public enum ExamEnum { + TEST() { + public String getDescription() { + return "Test"; + } + }, + FINAL_EXAM() { + public String getDescription() { + return "Exam"; + } + }; + + public abstract String getDescription(); +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Homework.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Homework.java new file mode 100644 index 0000000..01c632a --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/Homework.java @@ -0,0 +1,189 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +import android.os.Bundle; +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDateTime; +import org.joda.time.format.*; + +public class Homework implements Comparable { + private Deadline deadline; + private String subject; + private String description; + private String howToSend; + private int regularity; + private int deferral = 0; + private double expectedScore; + private double actualScore = -1; + private int id; + private ArrayList materials; + + public Homework(LocalDateTime deadline, @NotNull String subject, int regularity, String description, + String howToSend, double expectedScore, int id, @NotNull ArrayList materials) { + this.deadline = new Deadline(deadline); + this.subject = subject; + this.regularity = regularity; + this.description = description; + this.howToSend = howToSend; + this.expectedScore = expectedScore; + this.id = id; + this.materials = materials; + } + + public void setActualScore(double score) { + actualScore = score; + } + + public void setDeferral(int deferral) { + deadline.deadline.plusDays(deferral); + assignDeferral(deferral); + } + + public void assignDeferral(int deferral) { + this.deferral = deferral; + } + + public boolean isAccepted() { + return actualScore >= expectedScore; + } + + @NotNull + public String getFormattedDeadline() { + return deadline.getFormattedDeadline(); + } + + @NotNull + public ArrayList getDetails() { + ArrayList homeworkDetails = new ArrayList<>(); + homeworkDetails.add(getDescription()); + homeworkDetails.add(deadline.getFormattedDeadline()); + homeworkDetails.add(getHowToSend()); + if (getExpectedScore() == -1.0) { + homeworkDetails.add("Not specified"); + } else { + homeworkDetails.add(Double.toString(getExpectedScore())); + } + homeworkDetails.add(Integer.toString(regularity)); + // TODO is the following line necessary + homeworkDetails.addAll(materials); + homeworkDetails.add(Integer.toString(id)); + + return homeworkDetails; + } + + @NotNull + public Bundle getDeconstructed() { + Bundle bundle = new Bundle(); + bundle.putString("subject", subject); + bundle.putString("description", description); + bundle.putString("howToSend", howToSend); + bundle.putInt("id", id); + bundle.putInt("regularity", regularity); + bundle.putInt("deferral", deferral); + bundle.putDouble("expectedScore", expectedScore); + bundle.putDouble("actualScore", actualScore); + bundle.putStringArrayList("materials", materials); + bundle.putSerializable("deadline", deadline.deadline); + return bundle; + } + + public Deadline getDeadline() { + return deadline; + } + + @NotNull + public String getSubject() { + return subject; + } + + public String getDescription() { + return description; + } + + public int getRegularity() { + return regularity; + } + + public String getHowToSend() { + return howToSend; + } + + public double getExpectedScore() { + return expectedScore; + } + + public double getActualScore() { + return actualScore; + } + + public int getYear() { + return deadline.deadline.getYear(); + } + + public int getMonth() { + return deadline.deadline.getMonthOfYear(); + } + + public int getDay() { + return deadline.deadline.getDayOfMonth(); + } + + public int getHour() { + return deadline.deadline.getHourOfDay(); + } + + public int getMinute() { + return deadline.deadline.getMinuteOfHour(); + } + + public int getDeferral() { + return deferral; + } + + public boolean hasPassed() { + return deadline.hasPassed(); + } + + public int getId() { + return id; + } + + public ArrayList getMaterials() { + return materials; + } + + @NotNull + public Homework generateNewHomeworkById(int id) { + LocalDateTime newDeadline = deadline.deadline.minusDays(deferral); + deadline.deadline.plusWeeks(regularity); + return new Homework(newDeadline, subject, regularity, "", howToSend, + -1, id, new ArrayList<>()); + } + + @Override + public int compareTo(@NonNull Homework homework) { + return deadline.deadline.compareTo(homework.deadline.deadline); + } + + public class Deadline { + private LocalDateTime deadline; + + private Deadline(@NotNull LocalDateTime deadline) { + this.deadline = deadline; + } + + private boolean hasPassed() { + return LocalDateTime.now().compareTo(deadline) > 0; + } + + @NotNull + public String getFormattedDeadline() { + DateTimeFormatter formatter = DateTimeFormat.forPattern("dd.MM.yyyy HH:mm"); + return formatter.print(deadline); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/ScheduleEntry.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/ScheduleEntry.java new file mode 100644 index 0000000..98d9ec8 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/ScheduleEntry.java @@ -0,0 +1,19 @@ +package ru.spbau.group202.notdeadbydeadline.model; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalTime; + +import java.util.List; + + +public abstract class ScheduleEntry implements Comparable { + @NotNull + protected abstract LocalTime getTime(); + + @NotNull + public abstract List getScheduleDescription(); + + public int compareTo(@NotNull ScheduleEntry detailedEntry) { + return getTime().compareTo(detailedEntry.getTime()); + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/StudyMaterial.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/StudyMaterial.java new file mode 100644 index 0000000..15791f5 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/StudyMaterial.java @@ -0,0 +1,70 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +import android.os.Bundle; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; + + +public class StudyMaterial { + private String subject; + private String path; + private String name; + private int term; + private int version; + private int id; + + public StudyMaterial(@NotNull String name, @NotNull String subject, int term, + @NotNull String path, int version, int id) { + this.subject = subject; + this.term = term; + this.name = name; + this.path = path; + this.version = version; + this.id = id; + } + + @NotNull + public String getPath() { + return path; + } + + @NotNull + public String getName() { + return name; + } + + public int getTerm() { + return term; + } + + @NotNull + public String getSubject() { + return subject; + } + + public int getId() { + return id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @NotNull + public Bundle getDeconstructed() { + Bundle bundle = new Bundle(); + bundle.putString("subject", subject); + bundle.putString("path", path); + bundle.putString("name", name); + bundle.putInt("id", id); + bundle.putInt("term", term); + return bundle; + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/SubjectCredit.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/SubjectCredit.java new file mode 100644 index 0000000..2411199 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/SubjectCredit.java @@ -0,0 +1,58 @@ +package ru.spbau.group202.notdeadbydeadline.model; + +import org.jetbrains.annotations.NotNull; +import org.apache.commons.collections4.ListUtils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class SubjectCredit { + @NotNull + public List calculateProgress(@NotNull List homeworks, @NotNull List exams) { + return ListUtils.union(calculateHomeworkProgress(homeworks), calculateExamProgress(exams)); + } + + @NotNull + protected List calculateHomeworkProgress(@NotNull List homeworks) { + double totalPoints = 0; + double earnedPoints = 0; + + for (Homework homework : homeworks) { + if (homework.getActualScore() != -1) { + earnedPoints += homework.getActualScore(); + totalPoints += homework.getExpectedScore(); + } + } + + double percent = totalPoints == 0 ? 1 : earnedPoints / totalPoints; + return Arrays.asList("not stated", Double.toString(percent)); + } + + @NotNull + private List calculateExamProgress(@NotNull List exams) { + HashMap totalNumber = new HashMap<>(); + HashMap numberOfPassed = new HashMap<>(); + + for (Exam exam : exams) { + ExamEnum kind = exam.getExamType(); + totalNumber.put(kind, totalNumber.get(kind) + 1); + if (exam.isAccepted()) { + numberOfPassed.put(kind, numberOfPassed.get(kind) + 1); + } + } + + double passedTestsPercent = totalNumber.get(ExamEnum.TEST) == 0 ? 1 : + (double) numberOfPassed.get(ExamEnum.TEST) / totalNumber.get(ExamEnum.TEST); + double passedExamsPercent = totalNumber.get(ExamEnum.FINAL_EXAM) == 0 ? 1 : + (double) numberOfPassed.get(ExamEnum.FINAL_EXAM) / totalNumber.get(ExamEnum.FINAL_EXAM); + String testCredit = passedTestsPercent == 1.0 ? "Passed class" : "Failed class"; + String examsCredit = passedExamsPercent == 1.0 ? "Passed class" : "Failed class"; + int numberOfNotPassedTests = totalNumber.get(ExamEnum.TEST) - numberOfPassed.get(ExamEnum.TEST); + int numberOfNotPassedExams = totalNumber.get(ExamEnum.FINAL_EXAM) - numberOfPassed.get(ExamEnum.FINAL_EXAM); + + return Arrays.asList(Double.toString(passedTestsPercent), testCredit, + Integer.toString(numberOfNotPassedTests), Double.toString(passedExamsPercent), examsCredit, + Integer.toString(numberOfNotPassedExams)); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/WeekParityEnum.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/WeekParityEnum.java new file mode 100644 index 0000000..0eda574 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/WeekParityEnum.java @@ -0,0 +1,22 @@ +package ru.spbau.group202.notdeadbydeadline.model; + + +public enum WeekParityEnum { + ON_EVEN_WEEK() { + public WeekParityEnum inverse() { + return ON_ODD_WEEK; + } + }, + ON_ODD_WEEK() { + public WeekParityEnum inverse() { + return ON_EVEN_WEEK; + } + }, + ALWAYS() { + public WeekParityEnum inverse() { + return ALWAYS; + } + }; + + public abstract WeekParityEnum inverse(); +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/AsyncTaskResult.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/AsyncTaskResult.java new file mode 100644 index 0000000..e8af29f --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/AsyncTaskResult.java @@ -0,0 +1,25 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; + + +public class AsyncTaskResult { + private T result; + private E error; + + public T getResult() { + return result; + } + + public E getError() { + return error; + } + + public AsyncTaskResult(T result) { + this.result = result; + } + + public AsyncTaskResult(E error) { + this.error = error; + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/Function.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/Function.java new file mode 100644 index 0000000..ff4c4cf --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/Function.java @@ -0,0 +1,6 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + +public interface Function { + U apply(T argument); +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/ModelUtils.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/ModelUtils.java new file mode 100644 index 0000000..df806fe --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/ModelUtils.java @@ -0,0 +1,56 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +import ru.spbau.group202.notdeadbydeadline.model.Exam; +import ru.spbau.group202.notdeadbydeadline.model.Homework; +import ru.spbau.group202.notdeadbydeadline.model.ScheduleEntry; +import ru.spbau.group202.notdeadbydeadline.model.StudyMaterial; + +public class ModelUtils { + public static final Function> HW_FIELDS_TO_STRING_LIST = hw -> { + ArrayList fields = new ArrayList<>(); + fields.add(hw.getDescription()); + fields.add(hw.getFormattedDeadline()); + fields.add(hw.getHowToSend()); + if (hw.getExpectedScore() == -1.0) { + fields.add("Not specified"); + } else { + fields.add(Double.toString(hw.getExpectedScore())); + } + fields.add(Integer.toString(hw.getRegularity())); + fields.addAll(hw.getMaterials()); + fields.add(Integer.toString(hw.getId())); + return fields; + }; + + public static final Function> HW_DEADLINE_FIELDS_TO_STRING_LIST = hw -> + Arrays.asList(hw.getSubject(), hw.getDescription(), hw.getFormattedDeadline()); + + public static final Function> EXAM_FIELDS_TO_STRING_LIST = exam -> + Arrays.asList(exam.getSubject(), exam.getFormattedTime(), exam.getExamType().getDescription(), + exam.getDescription(), Integer.toString(exam.getId())); + + public static final Function> STUDY_MATERIAL_FIELDS_TO_STRING_LIST = + material -> Arrays.asList(material.getSubject(), Integer.toString(material.getTerm()), + material.getPath(), material.getName(), Integer.toString(material.getId())); + + public static final Function> SCHEDULE_ENTRY_TO_SCHEDULE_DESCRIPTION = + ScheduleEntry::getScheduleDescription; + + @NotNull + public static List map(@NotNull List list, @NotNull Function function) { + List result = new ArrayList<>(); + for(T element : list) { + result.add(function.apply(element)); + } + return result; + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/StudyMaterialsSourceAccessException.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/StudyMaterialsSourceAccessException.java new file mode 100644 index 0000000..4f96347 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/StudyMaterialsSourceAccessException.java @@ -0,0 +1,5 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + +public class StudyMaterialsSourceAccessException extends Exception { +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/StudyMaterialsUpdatingException.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/StudyMaterialsUpdatingException.java new file mode 100644 index 0000000..9f18786 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/StudyMaterialsUpdatingException.java @@ -0,0 +1,16 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + +import java.util.List; + +public class StudyMaterialsUpdatingException extends Exception { + private List exceptions; + + public StudyMaterialsUpdatingException(List exceptions) { + this.exceptions = exceptions; + } + + public List getErrors() { + return exceptions; + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/UnrecognizedCreditFormException.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/UnrecognizedCreditFormException.java new file mode 100644 index 0000000..8e2fab6 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/UnrecognizedCreditFormException.java @@ -0,0 +1,5 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + +public class UnrecognizedCreditFormException extends Exception { +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/UrlDownloadingException.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/UrlDownloadingException.java new file mode 100644 index 0000000..60a0830 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/UrlDownloadingException.java @@ -0,0 +1,5 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + +public class UrlDownloadingException extends Exception { +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/WebStudyMaterialException.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/WebStudyMaterialException.java new file mode 100644 index 0000000..d05846c --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/model/utilities/WebStudyMaterialException.java @@ -0,0 +1,16 @@ +package ru.spbau.group202.notdeadbydeadline.model.utilities; + + +import java.util.List; + +public class WebStudyMaterialException extends Exception { + private List studyMaterial; + + public WebStudyMaterialException(String message, List studyMaterial) { + super(message); + } + + public List getStudyMaterial() { + return studyMaterial; + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/AddHomeworkActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/AddHomeworkActivity.java new file mode 100644 index 0000000..81a60b1 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/AddHomeworkActivity.java @@ -0,0 +1,532 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TimePicker; +import android.widget.Toast; + +import com.ipaulpro.afilechooser.utils.FileUtils; + +import org.joda.time.LocalDateTime; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.R; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.AbstractDatePicker; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.AbstractTimePicker; + +public class AddHomeworkActivity extends AppCompatActivity { + + private static final String TAG = "AddHomeworkActivity"; + private static final int REQUEST_CHOOSER = 1234; + public static final HomeworkFieldsAccumulator HFA = new HomeworkFieldsAccumulator(); + private static boolean isSetTime = false; + private static boolean isSetDate = false; + private static boolean isSetSubject = false; + private static boolean isSetDescription = false; + private static boolean isSetExpectedScore = false; + private static boolean isSetHowToSend = false; + private static boolean isSetRegularity = false; + + private static Bundle homeworkEntry; + + private int id = -1; + + public void processSubject() { + + final List source = Controller.getInstance(this).getSubjectList(); + + final AutoCompleteTextView actv = findViewById(R.id.getSubjectACTV); + actv.setAdapter(new ArrayAdapter<>(this, + android.R.layout.simple_dropdown_item_1line, source)); + + + HFA.storeSubject(actv.getText().toString()); + + if (id != -1 && !isSetSubject) { + actv.setText(homeworkEntry.getString("subject")); + } + + actv.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (!source.contains(actv.getText().toString())) { + source.add(actv.getText().toString()); + } + HFA.storeSubject((actv.getText().toString())); + + + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) + || (actionId == EditorInfo.IME_ACTION_DONE)) { + Log.e("TAG", "Done pressed"); + } + return false; + } + }); + + actv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + //HFA.storeSubject((actv.getText().toString())); + if (!source.contains(actv.getText().toString())) { + source.add(actv.getText().toString()); + } + + View view1 = getCurrentFocus(); + if (view1 != null) { + InputMethodManager inputManager = + (InputMethodManager) getSystemService( + Context.INPUT_METHOD_SERVICE); + + if (inputManager != null) { + inputManager.hideSoftInputFromWindow(view1.getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + + } + } + }); + } + + public void getSubject() { + final AutoCompleteTextView actv = findViewById(R.id.getSubjectACTV); + + HFA.storeSubject(actv.getText().toString()); + } + + public void getDescription() { + final EditText editText = findViewById(R.id.getDescriptionEditText); + + HFA.storeDescription(editText.getText().toString()); + + if (id != -1 && !isSetDescription) { + editText.setText(homeworkEntry.getString("description")); + } + } + + public void processExpectedScore() { + final EditText editText = findViewById(R.id.expectedScore); + + if (id != -1 && !isSetExpectedScore) { + double score = homeworkEntry.getDouble("expectedScore"); + if (score != -1) { + editText.setText(String.format(Locale.getDefault(), + "%s", homeworkEntry.getDouble("expectedScore"))); + } else { + editText.setText(getResources().getString(R.string.not_specified)); + } + } + + String expectedScore = editText.getText().toString(); + if (isParseableDouble(expectedScore)) { + HFA.storeExpectedScore(Double.parseDouble(expectedScore)); + } + } + + public void getExpectedScore() { + final EditText editText = findViewById(R.id.expectedScore); + + String expectedScore = editText.getText().toString(); + if (isParseableDouble(expectedScore)) { + HFA.storeExpectedScore(Double.parseDouble(expectedScore)); + } else { + HFA.storeExpectedScore(-1.0); + } + } + + public void processHowToSend() { + final EditText editText = findViewById(R.id.submitWayEditText); + + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) + || (actionId == EditorInfo.IME_ACTION_DONE)) { + Log.d(TAG, "Done pressed"); + } + return false; + } + }); + + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + HFA.storeHowToSend(editText.getText().toString()); + + + View view1 = getCurrentFocus(); + if (view1 != null) { + InputMethodManager inputManager = + (InputMethodManager) getSystemService( + Context.INPUT_METHOD_SERVICE); + if (inputManager != null) { + inputManager.hideSoftInputFromWindow(view1.getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + } + } + return false; + } + }); + + + if (id != -1 && !isSetHowToSend) { + editText.setText(homeworkEntry.getString("howToSend")); + } + } + + public void getHowToSend() { + final EditText editText = findViewById(R.id.submitWayEditText); + + HFA.storeHowToSend(editText.getText().toString()); + } + + public void setTime(View view) { + TimePickerFragment timePickerFragment = new TimePickerFragment(); + if (id != -1 && !isSetTime) { + LocalDateTime deadline = (LocalDateTime) homeworkEntry.getSerializable("deadline"); + if (deadline != null) { + timePickerFragment.setValues(deadline.getHourOfDay(), + deadline.getMinuteOfHour()); + } + } + + timePickerFragment.show(getSupportFragmentManager(), "timePicker"); + } + + public void setDate(View view) { + DatePickerFragment datePickerFragment = new DatePickerFragment(); + if (id != -1 && !isSetDate) { + LocalDateTime deadline = (LocalDateTime) homeworkEntry.getSerializable("deadline"); + if (deadline != null) { + datePickerFragment.setValues(deadline.getYear(), + deadline.getMonthOfYear() - 1, + deadline.getDayOfMonth()); + } + } + + datePickerFragment.show(getSupportFragmentManager(), "datePicker"); + } + + public void processRegularity() { + EditText editText = findViewById(R.id.setRegularityEditText); + + if (id != -1 && !isSetRegularity) { + editText.setText(String.format(Locale.getDefault(), + "%d", homeworkEntry.getInt("regularity"))); + } + + String regularity = editText.getText().toString(); + if (isParseableInteger(regularity)) { + HFA.storeRegularity(Integer.parseInt(regularity)); + } + } + + public void getRegularity() { + EditText editText = findViewById(R.id.setRegularityEditText); + String regularity = editText.getText().toString(); + if (regularity.isEmpty()) { + HFA.storeRegularity(0); + } else if (isParseableInteger(regularity)) { + HFA.storeRegularity(Integer.parseInt(regularity)); + } + } + + public void setFiles(View view) { + Intent getContentIntent = FileUtils.createGetContentIntent(); + + Intent intent = Intent.createChooser(getContentIntent, "Select a file"); + startActivityForResult(intent, REQUEST_CHOOSER); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CHOOSER: + if (resultCode == RESULT_OK) { + + final Uri uri = data.getData(); + String path; + + // Get the File path from the Uri + path = FileUtils.getPath(this, uri); + + /*// Alternatively, use FileUtils.getFile(Context, Uri) + if (path != null && FileUtils.isLocal(path)) { + File file = new File(path); + path = file.getAbsolutePath(); + }*/ + + HFA.storeMaterials(path); + } + break; + } + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_add_homework); + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + HFA.clear(); + + id = getIntent().getIntExtra("id", -1); + + if (id != -1) { + TextView header = findViewById(R.id.addNewHWHeader); + header.setText(getResources().getString(R.string.edit_hw_entry)); + homeworkEntry = Controller.getInstance(this).homeworkController().getHomeworkById(id); + + ArrayList files = homeworkEntry.getStringArrayList("materials"); + if (files != null) { + for (String file : files) { + HFA.storeMaterials(file); + } + } + + } + + processSubject(); + getDescription(); + processExpectedScore(); + processHowToSend(); + processRegularity(); + + Button addHomeworkButton = findViewById(R.id.finishButton); + addHomeworkButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + getDescription(); + processSubject(); + getSubject(); + getExpectedScore(); + getHowToSend(); + getRegularity(); + + if (id == -1 && !HFA.isValidForAdding()) { + Toast.makeText(getApplicationContext(), + "Fill 'subject' and input correct date", + Toast.LENGTH_LONG).show(); + } else if (id != -1 && !HFA.isValidForEditing()) { + Toast.makeText(getApplicationContext(), + "Fill 'subject'", + Toast.LENGTH_LONG).show(); + } else { + if (id == -1) { + HFA.addNewHomework(AddHomeworkActivity.this); + } else { + HFA.editHomework(id, AddHomeworkActivity.this); + } + HFA.clear(); + finish(); + } + } + }); + } + + private boolean isParseableDouble(String string) { + try { + Double.parseDouble(string); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + private boolean isParseableInteger(String string) { + try { + Integer.parseInt(string); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static class HomeworkFieldsAccumulator { + private String subject; + private String description; + private int year; + private int month; + private int day; + private int hour; + private int minutes; + private double expectedScore = Double.MIN_VALUE; + private int regularity = 0; + private String howToSend; + private ArrayList materials = new ArrayList<>(); + + public void storeSubject(String subject) { + if (this.subject != null && !this.subject.equals(subject)) { + isSetSubject = true; + } + this.subject = subject == null ? "" : subject; + } + + public void storeDescription(String description) { + if (this.description != null && !this.description.equals(description)) { + isSetDescription = true; + } + this.description = description == null ? "" : description; + } + + public void storeExpectedScore(double expectedScore) { + if (this.expectedScore != expectedScore) { + isSetExpectedScore = true; + } + this.expectedScore = expectedScore; + } + + public void storeDate(int year, int month, int day) { + this.year = year; + this.month = month; + this.day = day; + } + + public void storeTime(int hour, int minutes) { + this.hour = hour; + this.minutes = minutes; + } + + public void storeRegularity(int regularity) { + if (this.regularity != regularity) { + isSetRegularity = true; + } + this.regularity = regularity; + } + + public void storeHowToSend(String howToSend) { + if (this.howToSend != null && !this.howToSend.equals(howToSend)) { + isSetHowToSend = true; + } + this.howToSend = howToSend == null ? "" : howToSend; + } + + public void storeMaterials(String filepath) { + materials.add(filepath); + } + + public void addNewHomework(Context context) { + + if (description == null) { + description = " "; + } + + if (howToSend == null) { + howToSend = " "; + } + + Controller.getInstance(context).homeworkController() + .addHomework(new LocalDateTime(year, month, day, hour, minutes), subject, + regularity, description, howToSend, expectedScore, materials); + } + + public void editHomework(int id, Context context) { + if (description == null) { + description = " "; + } + + if (howToSend == null) { + howToSend = " "; + } + + if (!isSetDate) { + LocalDateTime deadline = (LocalDateTime) homeworkEntry.getSerializable("deadline"); + if (deadline != null) { + year = deadline.getYear(); + month = deadline.getMonthOfYear(); + day = deadline.getDayOfMonth(); + } + } + + if (!isSetTime) { + LocalDateTime deadline = (LocalDateTime) homeworkEntry.getSerializable("deadline"); + if (deadline != null) { + hour = deadline.getHourOfDay(); + minutes = deadline.getMinuteOfHour(); + } + } + + Controller.getInstance(context).homeworkController() + .editHomeworkById(id, new LocalDateTime(year, month, day, hour, minutes), + subject, regularity, description, howToSend, expectedScore, materials); + + } + + public boolean isValidForAdding() { + return subject != null && isSetDate && isSetTime; + } + + public boolean isValidForEditing() { + return subject.trim().length() > 0; + } + + public void clear() { + subject = null; + description = null; + howToSend = null; + expectedScore = Double.MIN_VALUE; + year = 0; + month = 0; + day = 0; + hour = 0; + minutes = 0; + regularity = 0; + materials.clear(); + + isSetTime = false; + isSetDate = false; + isSetExpectedScore = false; + isSetSubject = false; + isSetDescription = false; + isSetHowToSend = false; + isSetRegularity = false; + } + + } + + public static class TimePickerFragment extends AbstractTimePicker { + + @Override + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + isSetTime = true; + HFA.storeTime(hourOfDay, minute); + } + + } + + public static class DatePickerFragment extends AbstractDatePicker { + @Override + public void onDateSet(DatePicker view, int year, int month, int day) { + isSetDate = true; + HFA.storeDate(year, month + 1, day); + } + } + + +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/AddScheduleEntryActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/AddScheduleEntryActivity.java new file mode 100644 index 0000000..1980cde --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/AddScheduleEntryActivity.java @@ -0,0 +1,357 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Context; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.PopupMenu; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.TimePicker; +import android.widget.Toast; + +import org.joda.time.LocalTime; + +import ru.spbau.group202.notdeadbydeadline.R; +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.model.WeekParityEnum; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.AbstractTimePicker; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.WeekDayEnum; + +public class AddScheduleEntryActivity extends AppCompatActivity { + + private static final String TAG = "AddSchEntryActivity"; + private static final ScheduleEntryFieldsAccumulator SEFA = new ScheduleEntryFieldsAccumulator(); + private static boolean isSetTime = false; + private static boolean isSetSubject = false; + private static boolean isSetTeacher = false; + private static boolean isSetAuditorium = false; + private static boolean isSetParity = false; + + private int id; + private Bundle scheduleEntry; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_add_schedule_entry); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + SEFA.clear(); + + id = getIntent().getIntExtra("id", -1); + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(id == -1 ? + getResources().getString(R.string.add_schedule_entry_header) + : getResources().getString(R.string.edit_schedule_entry)); + } + + if (id != -1) { + scheduleEntry = Controller.getInstance(this).scheduleController().getClassById(id); + SEFA.dayOfWeek = scheduleEntry.getInt("dayOfWeek"); + LocalTime localTime = (LocalTime) scheduleEntry.getSerializable("time"); + if (localTime != null) { + SEFA.hour = localTime.getHourOfDay(); + SEFA.minute = localTime.getMinuteOfHour(); + } + } + + TextView header = findViewById(R.id.addNewSEHeader); + header.setText(id == -1 ? getResources().getString(R.string + .add_schedule_entry_header) : + getResources().getString(R.string + .edit_schedule_entry)); + + getSubject(); + getAuditorium(); + getTeacher(); + getParity(); + + Button addSEbutton = findViewById(R.id.scheduleFinishButton); + addSEbutton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + getSubject(); + getAuditorium(); + getTeacher(); + getParity(); + + if (id == -1 && SEFA.isValidForAddingSE()) { + SEFA.addScheduleEntry(AddScheduleEntryActivity.this); + SEFA.clear(); + finish(); + } else if (id != -1 && SEFA.isValidForEditing()){ + SEFA.editScheduleEntry(id, AddScheduleEntryActivity.this); + SEFA.clear(); + finish(); + } else { + Toast.makeText(getApplicationContext(), + "Fill subject, weekday and time", + Toast.LENGTH_LONG).show(); + } + } + }); + + } + + public void setWeekDay(View view) { + Button button = findViewById(R.id.scheduleSetWeekDayButton); + PopupMenu popup = new PopupMenu(AddScheduleEntryActivity.this, button); + popup.getMenuInflater() + .inflate(R.menu.week_day_menu, popup.getMenu()); + //registering popup with OnMenuItemClickListener + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + SEFA.storeWeekDay(item.getTitle().toString());// item.getTitle() + return true; + } + }); + + popup.show(); //showing popup menu + } + + public void scheduleSetTime(View view) { + TimePickerFragment timePickerFragment = new TimePickerFragment(); + if (id != -1 && !isSetTime) { + LocalTime localTime = (LocalTime) scheduleEntry.getSerializable("time"); + if (localTime != null) { + timePickerFragment.setValues(localTime.getHourOfDay(), + localTime.getMinuteOfHour()); + } + } + + + timePickerFragment.show(getSupportFragmentManager(), "timePicker"); + } + + public void getSubject() { + EditText editText = findViewById(R.id.scheduleGetSubjectET); + + SEFA.storeSubject(editText.getText().toString()); + + if (id != -1 && !isSetSubject) { + editText.setText(scheduleEntry.getString("subject")); + } + + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + SEFA.storeSubject((editText.getText().toString())); + + + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) + || (actionId == EditorInfo.IME_ACTION_DONE)) { + Log.e("TAG", "Done pressed"); + } + return false; + } + }); + } + + public void getTeacher() { + EditText editText = findViewById(R.id.scheduleTeacherET); + + SEFA.storeTeacher(editText.getText().toString()); + + if (id != -1 && !isSetTeacher) { + editText.setText(scheduleEntry.getString("teacher")); + } + + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + SEFA.storeTeacher((editText.getText().toString())); + + + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) + || (actionId == EditorInfo.IME_ACTION_DONE)) { + Log.e("TAG", "Done pressed"); + } + return false; + } + }); + } + + public void getAuditorium() { + EditText editText = findViewById(R.id.scheduleAuditoriumET); + + SEFA.storeAuditorium(editText.getText().toString()); + + if (id != -1 && !isSetAuditorium) { + editText.setText(scheduleEntry.getString("auditorium")); + } + + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + SEFA.storeAuditorium((editText.getText().toString())); + + + if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) + || (actionId == EditorInfo.IME_ACTION_DONE)) { + Log.e("TAG", "Done pressed"); + } + return false; + } + }); + } + + public void getParity() { + String parity[] = {"even weeks", "odd weeks", "every week"}; + ArrayAdapter adapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, parity); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + Spinner spinner = findViewById(R.id.weekParitySpinner); + spinner.setAdapter(adapter); + spinner.setPrompt("Week Parity"); + if (id == -1) { + spinner.setSelection(2); + } else { + WeekParityEnum entryParity = isSetParity ? SEFA.parity : (WeekParityEnum) scheduleEntry + .getSerializable("weekParity"); + if (entryParity != null) { + switch (entryParity) { + case ALWAYS: + spinner.setSelection(2); + break; + case ON_EVEN_WEEK: + spinner.setSelection(0); + break; + case ON_ODD_WEEK: + spinner.setSelection(1); + break; + } + } + + } + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, + int position, long id) { + String text = parent.getItemAtPosition(position).toString(); + switch (text) { + case "even weeks": + SEFA.storeParity(WeekParityEnum.ON_EVEN_WEEK); + break; + case "odd weeks": + SEFA.storeParity(WeekParityEnum.ON_ODD_WEEK); + break; + case "every week": + SEFA.storeParity(WeekParityEnum.ALWAYS); + break; + default: + Log.e(TAG,"wrong week parity type"); + } + } + @Override + public void onNothingSelected(AdapterView arg0) { + } + }); + } + + + public static class ScheduleEntryFieldsAccumulator{ + private String subject = null; + private String teacher = null; + private String auditorium = null; + private String weekDay = null; + private WeekParityEnum parity; + private int hour; + private int minute; + private int dayOfWeek; // used for editing + + public void storeWeekDay(String weekDay) { + this.weekDay = weekDay; + } + + public void storeSubject(String subject) { + if (this.subject != null && !this.subject.equals(subject)) { + isSetSubject = true; + } + this.subject = subject; + } + + public void storeAuditorium(String auditorium) { + if (this.auditorium != null && !this.auditorium.equals(auditorium)) { + isSetAuditorium = true; + } + this.auditorium = auditorium; + } + + public void storeTeacher(String teacher) { + if (this.teacher != null && !this.teacher.equals(teacher)) { + isSetTeacher = true; + } + this.teacher = teacher; + } + + public void storeTime(int hour, int minute) { + this.hour = hour; + this.minute = minute; + } + + public void storeParity(WeekParityEnum parity) { + if (this.parity != null && !this.parity.equals(parity)) { + isSetParity = true; + } + this.parity = parity; + } + + public boolean isValidForAddingSE() { + return subject != null && weekDay != null && isSetTime; + } + + public boolean isValidForEditing() { + return subject != null && subject.trim().length() > 0; + } + + public void addScheduleEntry(Context context) { + Controller.getInstance(context).scheduleController().addClass(subject, + WeekDayEnum.valueOf(weekDay).ordinal(), hour, minute, + parity, auditorium, teacher); + } + + public void editScheduleEntry(int id, Context context) { + Controller.getInstance(context).scheduleController().editClassById(id, subject, + weekDay == null ? dayOfWeek : + WeekDayEnum.valueOf(weekDay).ordinal(), hour, minute, + parity, auditorium, teacher); + } + + public void clear() { + subject = null; + teacher = null; + auditorium = null; + weekDay = null; + hour = 0; + minute = 0; + + isSetParity = false; + isSetTeacher = false; + isSetAuditorium = false; + isSetSubject = false; + isSetTime = false; + } + + } + + public static class TimePickerFragment extends AbstractTimePicker { + @Override + public void onTimeSet(TimePicker view, int hourOfDay, int minute) { + isSetTime = true; + SEFA.storeTime(hourOfDay, minute); + } + } + +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/DeadlinesActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/DeadlinesActivity.java new file mode 100644 index 0000000..0702ab2 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/DeadlinesActivity.java @@ -0,0 +1,283 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TableLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import org.joda.time.LocalDate; +import org.joda.time.DateTimeConstants; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.R; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.ListViewUtility; + +public class DeadlinesActivity extends AppCompatActivity + implements NavigationView.OnNavigationItemSelectedListener { + + private static final String TAG = "DeadlinesActivity"; + + private LocalDate localDate; + + private void outputDeadlineByDay( int dayNumber ) { + + List> deadlinesDetails; + + LocalDate weekDay = localDate.withDayOfWeek(dayNumber); + Controller.getInstance(this).homeworkController().generateHomeworks(); + deadlinesDetails = Controller.getInstance(this).homeworkController() + .getDeadlinesByDay(weekDay); + + ArrayList formattedDeadlines = new ArrayList<>(); + for (List deadlineDetails : deadlinesDetails) { + SpannableStringBuilder stringBuilder = + new SpannableStringBuilder(deadlineDetails.get(2) + .split("\\s+")[1]); + + + int position = stringBuilder.length(); + stringBuilder.append(" "); + stringBuilder.append(deadlineDetails.get(0)); + stringBuilder.setSpan(new StyleSpan(Typeface.BOLD), + position, stringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append("\n"); + stringBuilder.append(deadlineDetails.get(1)); + + formattedDeadlines.add(stringBuilder); + } + + ListView lv; + switch (dayNumber) { + case DateTimeConstants.MONDAY: + lv = findViewById(R.id.deadlinesMondayList); + break; + case DateTimeConstants.TUESDAY: + lv = findViewById(R.id.deadlinesTuesdayList); + break; + case DateTimeConstants.WEDNESDAY: + lv = findViewById(R.id.deadlinesWednesdayList); + break; + case DateTimeConstants.THURSDAY: + lv = findViewById(R.id.deadlinesThursdayList); + break; + case DateTimeConstants.FRIDAY: + lv = findViewById(R.id.deadlinesFridayList); + break; + case DateTimeConstants.SATURDAY: + lv = findViewById(R.id.deadlinesSaturdayList); + break; + case DateTimeConstants.SUNDAY: + lv = findViewById(R.id.deadlinesSundayList); + break; + default: + Log.e(TAG, "Wrong dayNumber parameter in OutputDeadlineByDay"); + return; + } + + ArrayAdapter adapter = new ArrayAdapter<>(this, + R.layout.custom_deadline_listview_entry, + formattedDeadlines); + lv.setAdapter(adapter); + } + + private void setListViewsHeightAllDays() { + ListView monday = findViewById(R.id.deadlinesMondayList); + ListView tuesday = findViewById(R.id.deadlinesTuesdayList); + ListView wednesday = findViewById(R.id.deadlinesWednesdayList); + ListView thursday = findViewById(R.id.deadlinesThursdayList); + ListView friday = findViewById(R.id.deadlinesFridayList); + ListView saturday = findViewById(R.id.deadlinesSaturdayList); + + ListViewUtility.setTwoListViewsHeight(monday, thursday); + ListViewUtility.setTwoListViewsHeight(tuesday, friday); + ListViewUtility.setTwoListViewsHeight(wednesday, saturday); + } + + private void outputDeadlines() { + + final TableLayout table = findViewById(R.id.deadlinesTableLayout); + table.setColumnShrinkable(0, true); + table.setColumnShrinkable(1, true); + + for (int i = 1; i <= 7; ++i) + outputDeadlineByDay(i); + + setListViewsHeightAllDays(); + } + + private void setHeaders() { + + DateTimeFormatter formatter = DateTimeFormat.forPattern("dd.MM.yyyy"); + + String monday = getResources().getString(R.string.monday_for_deadlines, + formatter.print(localDate.withDayOfWeek(1))); + String tuesday = getResources().getString(R.string.tuesday_for_deadlines, + formatter.print(localDate.withDayOfWeek(2))); + String wednesday = getResources().getString(R.string.wednesday_for_deadlines, + formatter.print(localDate.withDayOfWeek(3))); + String thursday = getResources().getString(R.string.thursday_for_deadlines, + formatter.print(localDate.withDayOfWeek(4))); + String friday = getResources().getString(R.string.friday_for_deadlines, + formatter.print(localDate.withDayOfWeek(5))); + String saturday = getResources().getString(R.string.saturday_for_deadlines, + formatter.print(localDate.withDayOfWeek(6))); + String sunday = getResources().getString(R.string.sunday_for_deadlines, + formatter.print(localDate.withDayOfWeek(7))); + + TextView tv = findViewById(R.id.deadlinesMondayHeader); + tv.setText(monday); + tv = findViewById(R.id.deadlinesTuesdayHeader); + tv.setText(tuesday); + tv = findViewById(R.id.deadlinesWednesdayHeader); + tv.setText(wednesday); + tv = findViewById(R.id.deadlinesThursdayHeader); + tv.setText(thursday); + tv = findViewById(R.id.deadlinesFridayHeader); + tv.setText(friday); + tv = findViewById(R.id.deadlinesSaturdayHeader); + tv.setText(saturday); + tv = findViewById(R.id.deadlinesSundayHeader); + tv.setText(sunday); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_deadlines); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + setTitle(getString(R.string.deadlineHeader)); + + localDate = (LocalDate) getIntent().getSerializableExtra("date"); + + setHeaders(); + outputDeadlines(); + + Button nextWeekButton = findViewById(R.id.nextWeekButton); + nextWeekButton.setText(R.string.next_week); + nextWeekButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + localDate = localDate.plusDays(7); + setHeaders(); + outputDeadlines(); + } + }); + + Button button = findViewById(R.id.previousWeekButton); + button.setText(getResources().getString(R.string.previous_week)); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + localDate = localDate.minusDays(7); + setHeaders(); + outputDeadlines(); + } + }); + } + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.deadlines, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(@NotNull MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + if (id == R.id.nav_main) { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + } else if (id == R.id.nav_deadlines) { + Intent intent = new Intent(this, DeadlinesActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_homework) { + Intent intent = new Intent(this, HomeworkActivity.class); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_schedule) { + Intent intent = new Intent(this, ScheduleActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + + } /*else if (id == R.id.nav_studymaterials) { + Intent intent = new Intent(this, StudyMaterialsActivity.class); + startActivityForResult(intent, 1); + }*/ + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + outputDeadlines(); + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/DisplayHomeworkActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/DisplayHomeworkActivity.java new file mode 100644 index 0000000..646eccc --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/DisplayHomeworkActivity.java @@ -0,0 +1,115 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.PopupMenu; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.R; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.AttachmentsDialogFragment; + + +public class DisplayHomeworkActivity extends AppCompatActivity { + + + private void outputHomeworksBySubject(String subject) { + Controller.getInstance(this).homeworkController().generateHomeworks(); + List> formattedHomeworksDetails = + Controller.getInstance(this).homeworkController().getHomeworksBySubject(subject); + + ListView homeworksListView = findViewById(R.id.homeworksListView); + HomeworkListViewAdapter adapter1 = new HomeworkListViewAdapter(this, formattedHomeworksDetails); + homeworksListView.setAdapter(adapter1); + } + + private void processOnLongTapHomework() { + ListView homeworksListView = findViewById(R.id.homeworksListView); + homeworksListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + PopupMenu popup = new PopupMenu(DisplayHomeworkActivity.this, view); + popup.getMenuInflater() + .inflate(R.menu.homework_listview_item_menu, popup.getMenu()); + //registering popup with OnMenuItemClickListener + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + if (item.getTitle().toString().equals(getResources() + .getString(R.string.lv_entry_edit))) { + List detailedEntryList = (List) parent.getItemAtPosition(position); + int id = Integer.parseInt(detailedEntryList.get(detailedEntryList.size() - 1)); + Intent intent = new Intent(DisplayHomeworkActivity.this, AddHomeworkActivity.class); + intent.putExtra("id", id); + startActivityForResult(intent, 1); + return true; + } else if (item.getTitle().toString().equals(getResources() + .getString(R.string.lv_entry_delete))) { + List detailedEntryList = (List) parent.getItemAtPosition(position); + Controller.getInstance(DisplayHomeworkActivity.this).homeworkController() + .deleteHomeworkById(Integer.parseInt(detailedEntryList.get(detailedEntryList.size() - 1))); + outputHomeworks(); + return true; + } else if (item.getTitle().toString().equals(getResources() + .getString(R.string.lv_entry_open_attached))) { + List detailedEntryList = (List) parent.getItemAtPosition(position); + AttachmentsDialogFragment cdf = new AttachmentsDialogFragment(); + Bundle homeworkEntry = Controller.getInstance(DisplayHomeworkActivity.this).homeworkController() + .getHomeworkById(Integer.parseInt(detailedEntryList + .get(detailedEntryList.size() - 1))); + ArrayList src = homeworkEntry.getStringArrayList("materials"); + cdf.setFilepaths(src); + cdf.show(getSupportFragmentManager(), "files"); + } + + return false; + } + }); + + popup.show(); //showing popup menu + return true; + } + }); + } + + + + private void outputHomeworks() { + String subject = getIntent().getStringExtra("SUBJECT_NAME"); + setTitle(subject); + + outputHomeworksBySubject(subject); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + outputHomeworks(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_display_homework); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + String subject = getIntent().getStringExtra("SUBJECT_NAME"); + setTitle(subject); + + //outputHomeworksBySubject(subject); + outputHomeworks(); + processOnLongTapHomework(); + } + +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/HomeworkActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/HomeworkActivity.java new file mode 100644 index 0000000..7629120 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/HomeworkActivity.java @@ -0,0 +1,146 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.view.View; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDate; + +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.R; + +public class HomeworkActivity extends AppCompatActivity + implements NavigationView.OnNavigationItemSelectedListener { + + private void displaySubjects() { + List subjects = Controller.getInstance(this).getSubjectList(); + + final ListView lv = findViewById(R.id.subjectListView); + ArrayAdapter adapter = new ArrayAdapter<>(this, + R.layout.custom_textview_for_bigger_listview, subjects); + lv.setAdapter(adapter); + lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Intent intent = new Intent(getApplicationContext(), DisplayHomeworkActivity.class); + intent.putExtra("SUBJECT_NAME", (String) lv.getItemAtPosition(position)); + startActivity(intent); + } + }); + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + displaySubjects(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_homework); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + FloatingActionButton fab = findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getApplicationContext(), AddHomeworkActivity.class); + startActivityForResult(intent, 1); + } + }); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + setTitle("Homeworks"); + + displaySubjects(); + } + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.homework, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(@NotNull MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + + if (id == R.id.nav_main) { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + } else if (id == R.id.nav_deadlines) { + Intent intent = new Intent(this, DeadlinesActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_homework) { + Intent intent = new Intent(this, HomeworkActivity.class); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_schedule) { + Intent intent = new Intent(this, ScheduleActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + + }/* else if (id == R.id.nav_studymaterials) { + Intent intent = new Intent(this, StudyMaterialsActivity.class); + startActivityForResult(intent, 1); + }*/ + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/HomeworkListViewAdapter.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/HomeworkListViewAdapter.java new file mode 100644 index 0000000..c93c707 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/HomeworkListViewAdapter.java @@ -0,0 +1,103 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.R; + +import static android.graphics.Typeface.BOLD; + +public class HomeworkListViewAdapter extends BaseAdapter { + private LayoutInflater lInflater; + private List> detailedEntries; + + HomeworkListViewAdapter(Context context, + List> detailedEntries) { + this.detailedEntries = detailedEntries; + lInflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public int getCount() { + return detailedEntries.size(); + } + + @Override + public Object getItem(int position) { + return detailedEntries.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + @SuppressWarnings("unchecked") + public View getView(int position, View convertView, ViewGroup parent) { + + View view = convertView; + if (view == null) { + view = lInflater.inflate(R.layout.custom_homework_listview_item, parent, false); + } + + ArrayList detailedEntry = (ArrayList) getItem(position); + + String deadlinesField = "\nDeadline: "; + String descriptionFiled = "\nDescription: "; + String submitField = "\nSubmit: "; + String expectedScoreField = "\nExpected Score: "; + String regularityField = "\nRegularity: "; + + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(descriptionFiled); + stringBuilder.setSpan(new StyleSpan(BOLD), + 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append(detailedEntry.get(0)); + stringBuilder.append(deadlinesField); + stringBuilder.setSpan(new StyleSpan(BOLD), + stringBuilder.length() - deadlinesField.length(), + stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append(detailedEntry.get(1)); + stringBuilder.append(submitField); + stringBuilder.setSpan(new StyleSpan(BOLD), + stringBuilder.length() - submitField.length(), + stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append(detailedEntry.get(2)); + stringBuilder.append(expectedScoreField); + stringBuilder.setSpan(new StyleSpan(BOLD), + stringBuilder.length() - expectedScoreField.length(), + stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append(detailedEntry.get(3)); + stringBuilder.append(regularityField); + stringBuilder.setSpan(new StyleSpan(BOLD), + stringBuilder.length() - regularityField.length(), + stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (detailedEntry.get(4).trim().equals("0")) { + stringBuilder.append("none"); + } else if (detailedEntry.get(4).trim().equals("1")) { + stringBuilder.append(detailedEntry.get(4)); + stringBuilder.append(" week"); + } else { + stringBuilder.append(detailedEntry.get(4)); + stringBuilder.append(" weeks"); + } + + + ((TextView) view.findViewById(R.id.list_item_hw)).setText(stringBuilder); + + return view; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/MainActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/MainActivity.java new file mode 100644 index 0000000..77c36f8 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/MainActivity.java @@ -0,0 +1,232 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import net.danlew.android.joda.JodaTimeAndroid; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.R; + +public class MainActivity extends AppCompatActivity + implements NavigationView.OnNavigationItemSelectedListener { + + private void outputCurrentDate() { + LocalDateTime currentDate = LocalDateTime.now(); + + StringBuilder dateStringBuilder = + new StringBuilder(Integer.toString(currentDate.getDayOfMonth())); + dateStringBuilder.append("\n"); + dateStringBuilder.append(currentDate.monthOfYear().getAsText()); + dateStringBuilder.append("\n"); + int pos = dateStringBuilder.length(); + dateStringBuilder.append(currentDate.dayOfWeek().getAsText()); + String dateString = dateStringBuilder.toString(); + + SpannableString date = new SpannableString(dateString); + date.setSpan(new RelativeSizeSpan(3f), 0, 2, 0); + date.setSpan(new StyleSpan(Typeface.ITALIC), pos, dateString.length(), 0); + + TextView tv = findViewById(R.id.currentDate); + tv.setText(date); + tv.setFocusable(false); + } + + private void outputDeadlines() { + Controller.getInstance(this).homeworkController().generateHomeworks(); + List> deadlinesDetails = Controller.getInstance(this).homeworkController() + .getDeadlinesByDay(LocalDate.now()); + + + LocalDateTime ldt = LocalDateTime.now().plusDays(1); + deadlinesDetails.addAll(Controller.getInstance(this).homeworkController() + .getDeadlinesByDay(ldt.toLocalDate())); + + + ArrayList formattedDeadlines = new ArrayList<>(); + for (List deadlineDetails : deadlinesDetails) { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder( + deadlineDetails.get(2)); + DateTimeFormatter formatter = DateTimeFormat.forPattern("dd.MM.yyyy HH:mm"); + LocalDateTime deadline = formatter.parseLocalDateTime(deadlineDetails.get(2)); + LocalDate deadlineDate = new LocalDate(deadline); + + if (deadlineDate.compareTo(LocalDate.now()) <= 0 + && deadline.compareTo(LocalDateTime.now()) < 0) { + stringBuilder.setSpan(new ForegroundColorSpan(android.graphics.Color.rgb(172, 7, 7)), + 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + int position = stringBuilder.length(); + stringBuilder.append(" "); + stringBuilder.append(deadlineDetails.get(0)); + stringBuilder.setSpan(new StyleSpan(Typeface.BOLD), + position, stringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (!deadlineDetails.get(1).isEmpty()) { + stringBuilder.append("\n"); + stringBuilder.append(deadlineDetails.get(1)); + } + + formattedDeadlines.add(stringBuilder); + } + + ListView lv = findViewById(R.id.deadlinesList2); + ArrayAdapter adapter = new ArrayAdapter<>(this, + R.layout.custom_mainscreen_listview_entry, + formattedDeadlines); + lv.setAdapter(adapter); + } + + private void outputTodaySchedule() { + LocalDate localDate = LocalDate.now(); + List> scheduleDetails = Controller.getInstance(this).scheduleController() + .getScheduleByDay(localDate); + /*List> scheduleDetails = Controller.ScheduleController.getScheduleByDayOfWeek(localDate.getDayOfWeek() - 1, + WeekParityEnum.values()[localDate.getWeekOfWeekyear() % 2]);*/ + + ArrayList formattedSchedule = new ArrayList<>(); + for (int i = 0; i < scheduleDetails.size(); i++) { + List schDetails = scheduleDetails.get(i); + SpannableStringBuilder stringBuilder = + new SpannableStringBuilder(schDetails.get(1)); + + stringBuilder.append(" "); + int position = stringBuilder.length(); + stringBuilder.append(schDetails.get(0)); + stringBuilder.setSpan(new StyleSpan(Typeface.BOLD), + position, stringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append(", \n"); + stringBuilder.append(schDetails.get(2)); + stringBuilder.append(", "); + stringBuilder.append(schDetails.get(3)); + + formattedSchedule.add(stringBuilder); + } + + ListView lv = findViewById(R.id.scheduleListViewMainScreen); + ArrayAdapter adapter = new ArrayAdapter<>(this, + R.layout.custom_mainscreen_listview_entry, + formattedSchedule); + lv.setAdapter(adapter); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + JodaTimeAndroid.init(this); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + outputCurrentDate(); + outputDeadlines(); + outputTodaySchedule(); + } + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(@NotNull MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + if (id == R.id.nav_main) { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + } else if (id == R.id.nav_deadlines) { + Intent intent = new Intent(this, DeadlinesActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_homework) { + Intent intent = new Intent(this, HomeworkActivity.class); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_schedule) { + Intent intent = new Intent(this, ScheduleActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + } /*else if (id == R.id.nav_studymaterials) { + Intent intent = new Intent(this, StudyMaterialsActivity.class); + startActivityForResult(intent, 1); + }*/ + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + outputDeadlines(); + outputTodaySchedule(); + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/ScheduleActivity.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/ScheduleActivity.java new file mode 100644 index 0000000..535dc5c --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/ScheduleActivity.java @@ -0,0 +1,328 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.widget.PopupMenu; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.util.Log; +import android.view.View; +import android.support.design.widget.NavigationView; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TableLayout; +import android.widget.TextView; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.DateTimeConstants; +import org.joda.time.LocalDate; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.controller.Controller; +import ru.spbau.group202.notdeadbydeadline.R; +import ru.spbau.group202.notdeadbydeadline.ui.utilities.ListViewUtility; + +public class ScheduleActivity extends AppCompatActivity + implements NavigationView.OnNavigationItemSelectedListener { + + private static final String TAG = "ScheduleActivity"; + + private LocalDate localDate; + + private void outputScheduleByDay(int dayNumber) { + List> scheduleDetails = Controller.getInstance(this).scheduleController() + .getScheduleByDay(localDate.plusDays(dayNumber)); + //List> scheduleDetails = Controller.ScheduleController.getScheduleByDayOfWeek(dayNumber, + //WeekParityEnum.values()[localDate.getWeekOfWeekyear() % 2]); + + /*ArrayList formattedSchedule = new ArrayList<>(); + for (int i = 0; i < scheduleDetails.size(); i++) { + List schDetails = scheduleDetails.get(i); + SpannableStringBuilder stringBuilder = + new SpannableStringBuilder(schDetails.get(1)); + + stringBuilder.append(" "); + int position = stringBuilder.length(); + stringBuilder.append(schDetails.get(0)); + stringBuilder.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), + position, stringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.append(", \n"); + stringBuilder.append(schDetails.get(2)); + stringBuilder.append(", "); + stringBuilder.append(schDetails.get(3)); + + formattedSchedule.add(stringBuilder); + }*/ + + ListView lv; + switch ((localDate.getDayOfWeek() + dayNumber) % 7) { + case DateTimeConstants.MONDAY: + lv = findViewById(R.id.scheduleMondayList); + break; + case DateTimeConstants.TUESDAY: + lv = findViewById(R.id.scheduleTuesdayList); + break; + case DateTimeConstants.WEDNESDAY: + lv = findViewById(R.id.scheduleWednesdayList); + break; + case DateTimeConstants.THURSDAY: + lv = findViewById(R.id.scheduleThursdayList); + break; + case DateTimeConstants.FRIDAY: + lv = findViewById(R.id.scheduleFridayList); + break; + case DateTimeConstants.SATURDAY: + lv = findViewById(R.id.scheduleSaturdayList); + break; + case DateTimeConstants.SUNDAY: + return; + default: + Log.e(TAG, "Wrong dayNumber parameter in OutputScheduleByDay"); + return; + } + + ScheduleListViewAdapter adapter = new ScheduleListViewAdapter(this, scheduleDetails); + lv.setAdapter(adapter); + } + + private void setHeaders() { + + String monday = getResources().getString(R.string.monday); + String tuesday = getResources().getString(R.string.tuesday); + String wednesday = getResources().getString(R.string.wednesday); + String thursday = getResources().getString(R.string.thursday); + String friday = getResources().getString(R.string.friday); + String saturday = getResources().getString(R.string.saturday); + + TextView tv = findViewById(R.id.scheduleMondayHeader); + tv.setText(monday); + tv = findViewById(R.id.scheduleTuesdayHeader); + tv.setText(tuesday); + tv = findViewById(R.id.scheduleWednesdayHeader); + tv.setText(wednesday); + tv = findViewById(R.id.scheduleThursdayHeader); + tv.setText(thursday); + tv = findViewById(R.id.scheduleFridayHeader); + tv.setText(friday); + tv = findViewById(R.id.scheduleSaturdayHeader); + tv.setText(saturday); + } + + private void setListViewsHeightAllDays() { + ListView monday = findViewById(R.id.scheduleMondayList); + ListView tuesday = findViewById(R.id.scheduleTuesdayList); + ListView wednesday = findViewById(R.id.scheduleWednesdayList); + ListView thursday = findViewById(R.id.scheduleThursdayList); + ListView friday = findViewById(R.id.scheduleFridayList); + ListView saturday = findViewById(R.id.scheduleSaturdayList); + + ListViewUtility.setTwoListViewsHeight(monday, thursday); + ListViewUtility.setTwoListViewsHeight(tuesday, friday); + ListViewUtility.setTwoListViewsHeight(wednesday, saturday); + } + + private void outputSchedule() { + final TableLayout table = findViewById(R.id.scheduleTableLayout); + table.setColumnShrinkable(0, true); + table.setColumnShrinkable(1, true); + + for (int i = 0; i < 7; ++i) + outputScheduleByDay(i); + + setListViewsHeightAllDays(); + } + + private void processOnLongTapScheduleEntry(int listViewId) { + ListView scheduleListView = findViewById(listViewId); + scheduleListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + PopupMenu popup = new PopupMenu(ScheduleActivity.this, view); + popup.getMenuInflater() + .inflate(R.menu.schedule_listview_item_menu, popup.getMenu()); + //registering popup with OnMenuItemClickListener + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + if (item.getTitle().toString().equals(getResources() + .getString(R.string.lv_entry_edit))) { + // TODO call edit (which is yet nonexistent) + List detailedEntryList = (List) parent.getItemAtPosition(position); + Intent intent = new Intent(ScheduleActivity.this, + AddScheduleEntryActivity.class); + int id = Integer.parseInt(detailedEntryList + .get(detailedEntryList.size() - 1)); + intent.putExtra("id", id); + startActivityForResult(intent, 1); + + return true; + } else if (item.getTitle().toString().equals(getResources() + .getString(R.string.lv_entry_delete))) { + List detailedEntryList = (List) parent.getItemAtPosition(position); + Controller.getInstance(ScheduleActivity.this).scheduleController() + .deleteClassById(Integer + .parseInt(detailedEntryList.get(detailedEntryList.size() - 1))); + outputSchedule(); + return true; + } + + return false; + } + }); + + popup.show(); //showing popup menu + return true; + } + }); + + } + + private void processListviewLongTapForAllWeekdays() { + processOnLongTapScheduleEntry(R.id.scheduleMondayList); + processOnLongTapScheduleEntry(R.id.scheduleTuesdayList); + processOnLongTapScheduleEntry(R.id.scheduleWednesdayList); + processOnLongTapScheduleEntry(R.id.scheduleThursdayList); + processOnLongTapScheduleEntry(R.id.scheduleFridayList); + processOnLongTapScheduleEntry(R.id.scheduleSaturdayList); + } + + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_schedule); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + FloatingActionButton fab = findViewById(R.id.addScheduleEntryButton); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(getApplicationContext(), AddScheduleEntryActivity.class); + startActivityForResult(intent, 1); + } + }); + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( + this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); + drawer.addDrawerListener(toggle); + toggle.syncState(); + + NavigationView navigationView = findViewById(R.id.nav_view); + navigationView.setNavigationItemSelectedListener(this); + + localDate = (LocalDate) getIntent().getSerializableExtra("date"); + + setTitle("Schedule"); + setHeaders(); + outputSchedule(); + + Button nextWeekButton = findViewById(R.id.scheduleNextWeekButton); + nextWeekButton.setText(R.string.next_week); + nextWeekButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + localDate = localDate.plusDays(7); + setHeaders(); + outputSchedule(); + } + }); + + Button button = findViewById(R.id.schedulePreviousWeekButton); + button.setText(getResources().getString(R.string.previous_week)); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + localDate = localDate.minusDays(7); + setHeaders(); + outputSchedule(); + } + }); + + processListviewLongTapForAllWeekdays(); + } + + @Override + public void onBackPressed() { + DrawerLayout drawer = findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.schedule, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean onNavigationItemSelected(@NotNull MenuItem item) { + // Handle navigation view item clicks here. + int id = item.getItemId(); + + if (id == R.id.nav_main) { + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + } else if (id == R.id.nav_deadlines) { + Intent intent = new Intent(this, DeadlinesActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_homework) { + Intent intent = new Intent(this, HomeworkActivity.class); + startActivityForResult(intent, 1); + } else if (id == R.id.nav_schedule) { + Intent intent = new Intent(this, ScheduleActivity.class); + intent.putExtra("date", new LocalDate()); + startActivityForResult(intent, 1); + } /*else if (id == R.id.nav_studymaterials) { + Intent intent = new Intent(this, StudyMaterialsActivity.class); + startActivityForResult(intent, 1); + }*/ + + DrawerLayout drawer = findViewById(R.id.drawer_layout); + drawer.closeDrawer(GravityCompat.START); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + outputSchedule(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/ScheduleListViewAdapter.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/ScheduleListViewAdapter.java new file mode 100644 index 0000000..8d82a5c --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/ScheduleListViewAdapter.java @@ -0,0 +1,79 @@ +package ru.spbau.group202.notdeadbydeadline.ui; + +import android.content.Context; +import android.graphics.Typeface; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.group202.notdeadbydeadline.R; + +public class ScheduleListViewAdapter extends BaseAdapter { + + private LayoutInflater lInflater; + private List> detailedEntries; + + ScheduleListViewAdapter(Context context, + List> detailedEntries) { + this.detailedEntries = detailedEntries; + lInflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + @Override + public int getCount() { + return detailedEntries.size(); + } + + @Override + public Object getItem(int position) { + return detailedEntries.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + @SuppressWarnings("unchecked") + public View getView(int position, View convertView, ViewGroup parent) { + + View view = convertView; + if (view == null) { + //view = lInflater.inflate(R.layout.custom_schedule_listview_item, parent, false); + view = lInflater.inflate(R.layout.custom_deadline_listview_entry, parent, false); + } + + ArrayList detailedEntry = (ArrayList) getItem(position); + + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(detailedEntry.get(1)); + stringBuilder.append(" "); + int pos = stringBuilder.length(); + stringBuilder.append(detailedEntry.get(0)); + stringBuilder.setSpan(new StyleSpan(Typeface.BOLD), + pos, stringBuilder.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (!detailedEntry.get(2).isEmpty() || !detailedEntry.get(3).isEmpty()) { + stringBuilder.append(", \n"); + if (!detailedEntry.get(2).isEmpty()) { + stringBuilder.append(detailedEntry.get(2)); + stringBuilder.append(", "); + } + stringBuilder.append(detailedEntry.get(3)); + } + + + ((TextView) view.findViewById(android.R.id.text2)).setText(stringBuilder); + + return view; + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AbstractDatePicker.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AbstractDatePicker.java new file mode 100644 index 0000000..b51bbbc --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AbstractDatePicker.java @@ -0,0 +1,39 @@ +package ru.spbau.group202.notdeadbydeadline.ui.utilities; + +import android.app.DatePickerDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalDate; + +public abstract class AbstractDatePicker extends DialogFragment + implements DatePickerDialog.OnDateSetListener { + private int year; + private int month; + private int day; + + public AbstractDatePicker() { + + // Use the current date as the default date in the picker + LocalDate localDate = new LocalDate(); + year = localDate.getYear(); + month = localDate.getMonthOfYear() - 1; + day = localDate.getDayOfMonth(); + } + + public void setValues(int year, int month, int day) { + this.year = year; + this.month = month; + this.day = day; + } + + @NotNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + // Create a new instance of DatePickerDialog and return it + return new DatePickerDialog(getActivity(), this, year, month, day); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AbstractTimePicker.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AbstractTimePicker.java new file mode 100644 index 0000000..f9c9584 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AbstractTimePicker.java @@ -0,0 +1,39 @@ +package ru.spbau.group202.notdeadbydeadline.ui.utilities; + +import android.app.Dialog; +import android.app.TimePickerDialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.text.format.DateFormat; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.LocalTime; + +public abstract class AbstractTimePicker extends DialogFragment + implements TimePickerDialog.OnTimeSetListener { + + private int hour; + private int minute; + + public AbstractTimePicker() { + // Use the current time as the default values for the picker + LocalTime localTime = new LocalTime(); + hour = localTime.getHourOfDay(); + minute = localTime.getMinuteOfHour(); + } + + public void setValues(int hour, int minute) { + this.hour = hour; + this.minute = minute; + } + + @NotNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + + // Create a new instance of TimePickerDialog and return it + return new TimePickerDialog(getActivity(), this, hour, minute, + DateFormat.is24HourFormat(getActivity())); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AttachmentsDialogFragment.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AttachmentsDialogFragment.java new file mode 100644 index 0000000..939a31a --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/AttachmentsDialogFragment.java @@ -0,0 +1,99 @@ +package ru.spbau.group202.notdeadbydeadline.ui.utilities; + +import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.OpenableColumns; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.webkit.MimeTypeMap; +import android.widget.Toast; + +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import static org.apache.commons.io.FileUtils.getFile; + +public class AttachmentsDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { + + private ArrayList filepaths = new ArrayList<>(); + + public void setFilepaths(ArrayList filepaths) { + this.filepaths = filepaths; + } + + private String parseFilename(String filename) { + File f = new File(filename); + return f.getName(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + String[] paths = new String[filepaths.size()]; + paths = filepaths.toArray(paths); + for (int i = 0; i < paths.length; i++) { + paths[i] = parseFilename(paths[i]); + } + + builder.setTitle("Choose file to open") + .setItems(paths, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + openFile(filepaths.get(which)); + Toast.makeText(getActivity(), + "Chosen file: " + filepaths.get(which), + Toast.LENGTH_SHORT).show(); + } + }); + + return builder.create(); + } + + private void openFile(String filepath) { + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + File file = new File(filepath); + + String extension = FilenameUtils.getExtension(filepath); + switch (extension) { + case "jpg": + case "png": + case "gif": + intent.setDataAndType(Uri.fromFile(file), "image/*"); + break; + case "pdf": + intent.setDataAndType(Uri.fromFile(file), "application/pdf"); + break; + case "txt": + intent.setDataAndType(Uri.fromFile(file), "text/plain"); + break; + case "doc": // TODO does this even work + intent.setDataAndType(Uri.fromFile(file), "application/msword"); + break; + case "docx": // TODO does this even work + intent.setDataAndType(Uri.fromFile(file), "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + break; + case "html": + intent.setDataAndType(Uri.fromFile(file), "text/html"); + break; + } + startActivity(intent); + } + + + @Override + public void onClick(DialogInterface dialog, int which) { + + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/ListViewUtility.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/ListViewUtility.java new file mode 100644 index 0000000..a4398a2 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/ListViewUtility.java @@ -0,0 +1,39 @@ +package ru.spbau.group202.notdeadbydeadline.ui.utilities; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListAdapter; +import android.widget.ListView; + +public class ListViewUtility { + + // set listView2 height based on listView1 height + private static void setListViewHeightBasedOnChildren(ListView listView1, + ListView listView2) { + ListAdapter listAdapter = listView1.getAdapter(); + if (listAdapter == null) { + // pre-condition + return; + } + int totalHeight = 0; + for (int i = 0; i < listAdapter.getCount(); i++) { + View listItem = listAdapter.getView(i, null, listView1); + listItem.measure(0, 0); + totalHeight += listItem.getMeasuredHeight(); + } + + ViewGroup.LayoutParams params = listView2.getLayoutParams(); + params.height = totalHeight + (listView2.getDividerHeight() * (listAdapter.getCount() - 1)); + listView2.setLayoutParams(params); + } + + public static void setTwoListViewsHeight(ListView listView1, ListView listView2) { + if (listView1.getAdapter().getCount() > listView2.getAdapter().getCount()) { + setListViewHeightBasedOnChildren(listView1, listView2); + setListViewHeightBasedOnChildren(listView1, listView1); + } else { + setListViewHeightBasedOnChildren(listView2, listView2); + setListViewHeightBasedOnChildren(listView2, listView1); + } + } +} diff --git a/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/WeekDayEnum.java b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/WeekDayEnum.java new file mode 100644 index 0000000..aeec4b7 --- /dev/null +++ b/app/src/main/java/ru/spbau/group202/notdeadbydeadline/ui/utilities/WeekDayEnum.java @@ -0,0 +1,5 @@ +package ru.spbau.group202.notdeadbydeadline.ui.utilities; + +public enum WeekDayEnum { + Monday, Tuesday, Wednesday, Thursday, Friday, Saturday; +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/button_border.xml b/app/src/main/res/drawable/button_border.xml new file mode 100644 index 0000000..3b82370 --- /dev/null +++ b/app/src/main/res/drawable/button_border.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_note_add_white_24dp.xml b/app/src/main/res/drawable/ic_note_add_white_24dp.xml new file mode 100644 index 0000000..750127b --- /dev/null +++ b/app/src/main/res/drawable/ic_note_add_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 0000000..6d81870 --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_homework.xml b/app/src/main/res/layout/activity_add_homework.xml new file mode 100644 index 0000000..d3eb924 --- /dev/null +++ b/app/src/main/res/layout/activity_add_homework.xml @@ -0,0 +1,158 @@ + + + + + + + +