diff --git a/Lambdas/Lists/List/src/List.java b/Lambdas/Lists/List/src/List.java index fd73808..30cd7da 100644 --- a/Lambdas/Lists/List/src/List.java +++ b/Lambdas/Lists/List/src/List.java @@ -85,9 +85,6 @@ public class List { public void setUiPosition(Integer uiPosition) { this.uiPosition = uiPosition; - - public ItemEntry[] getEntries() { - return entries.toArray(new ItemEntry[entries.size()]); } public void addItemEntry(ItemEntry entry) { diff --git a/Lambdas/Lists/ListShare/src/ListShare.java b/Lambdas/Lists/ListShare/src/ListShare.java index 151d842..60e3fde 100644 --- a/Lambdas/Lists/ListShare/src/ListShare.java +++ b/Lambdas/Lists/ListShare/src/ListShare.java @@ -1,16 +1,57 @@ -package com.example.listify.data; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; public class ListShare { Integer listID; String shareWithEmail; + Integer permissionLevel; + Integer uiPosition; ArrayList other; public ListShare(ResultSet listRow) throws SQLException { this.listID = listRow.getInt("listID"); this.shareWithEmail = listRow.getString("userID"); + this.permissionLevel = listRow.getInt("permissionLevel"); + this.uiPosition = listRow.getInt("uiPosition"); other = new ArrayList<>(); } + @Override + public String toString() { + return "ListShare{" + + "listID=" + listID + + ", shareWithEmail='" + shareWithEmail + '\'' + + ", permissionLevel=" + permissionLevel + + ", uiPosition=" + uiPosition + + ", other=" + other + + '}'; + } + + public Integer getPermissionLevel() { + return permissionLevel; + } + + public void setPermissionLevel(Integer permissionLevel) { + this.permissionLevel = permissionLevel; + } + + public Integer getUiPosition() { + return uiPosition; + } + + public void setUiPosition(Integer uiPosition) { + this.uiPosition = uiPosition; + } + + public ArrayList getOther() { + return other; + } + + public void setOther(ArrayList other) { + this.other = other; + } + public Integer getListID() { return listID; } diff --git a/Lambdas/Lists/ListShare/src/ListShareDeleter.java b/Lambdas/Lists/ListShare/src/ListShareDeleter.java deleted file mode 100644 index 7ec0076..0000000 --- a/Lambdas/Lists/ListShare/src/ListShareDeleter.java +++ /dev/null @@ -1,65 +0,0 @@ -import java.security.AccessControlException; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - -public class ListShareDeleter implements CallHandler { - private final Connection connection; - private final String cognitoID; - - private final String GET_LIST_ACCESS = "SELECT * FROM List WHERE (owner = ? AND listID = ?);"; - private final String REMOVE_SHAREE = "DELETE FROM ListSharee WHERE listID = ? AND user = ?;"; - - public ListShareDeleter(Connection connection, String cognitoID) { - this.connection = connection; - this.cognitoID = cognitoID; - } - - @Override - public Object conductAction(Map bodyMap, HashMap queryMap, String cognitoID) throws SQLException { - Integer listID = Integer.parseInt(queryMap.get("id")); - - InvokeRequest invokeRequest = new InvokeRequest(); - invokeRequest.setFunctionName("UserGET"); - invokeRequest.setPayload("{" + - " \"body\": {" + - " \"emailToCheck\": \"" + bodyMap.get("shareWithEmail").toString() + "\"" + - " }," + - " \"params\": {" + - " \"querystring\": {" + - " }" + - " }," + - " \"context\": {" + - " \"sub\": \"not used\"" + - " }" + - "}"); - InvokeResult invokeResult = AWSLambdaClientBuilder.defaultClient().invoke(invokeRequest); - - String shareeID = new String(invokeResult.getPayload().array()).replace("\"", ""); - - //Ensure that the user who is unsharing a list is the owner of that list - PreparedStatement accessCheck = connection.prepareStatement(GET_LIST_ACCESS); - accessCheck.setString(1, cognitoID); - accessCheck.setInt(2, listID); - - ResultSet userLists = accessCheck.executeQuery(); - - //User does not own the list; unshare attempt fails - if (!userLists.next()) { - throw new AccessControlException("User does not have access to list"); - } - - //Unshare the list with the specified sharee - PreparedStatement unshareList = connection.prepareStatement(REMOVE_SHAREE); - unshareList.setInt(1, listID); - unshareList.setInt(2, shareeID); - - cleanAccess.executeUpdate(); - connection.commit(); - - return null; - } -} diff --git a/Lambdas/Lists/ListShare/src/ListShareGetter.java b/Lambdas/Lists/ListShare/src/ListShareGetter.java index 009f0d6..273b8cd 100644 --- a/Lambdas/Lists/ListShare/src/ListShareGetter.java +++ b/Lambdas/Lists/ListShare/src/ListShareGetter.java @@ -2,7 +2,6 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -21,7 +20,7 @@ public class ListShareGetter implements CallHandler{ public Object conductAction(Map bodyMap, HashMap queryMap, String cognitoID) throws SQLException { Integer listID = Integer.parseInt(queryMap.get("id")); - PreparedStatement getList = connection.prepareStatement(GET_LIST); + PreparedStatement getList = connection.prepareStatement(GET_LISTS); getList.setInt(1, listID); ResultSet getListResults = getList.executeQuery(); diff --git a/Lambdas/Lists/Picture/src/Picture.java b/Lambdas/Lists/Picture/src/Picture.java new file mode 100644 index 0000000..f4e5fae --- /dev/null +++ b/Lambdas/Lists/Picture/src/Picture.java @@ -0,0 +1,34 @@ +import java.sql.ResultSet; +import java.sql.SQLException; + +public class Picture { + String base64EncodedImage; + + public Picture(ResultSet rs) { + try { + this.base64EncodedImage = rs.getString("base64image"); + } catch (SQLException throwables) { + throwables.printStackTrace(); + this.base64EncodedImage = null; + } + } + + public Picture(String base64EncodedImage) { + this.base64EncodedImage = base64EncodedImage; + } + + @Override + public String toString() { + return "Picture{" + + "base64EncodedImage='" + base64EncodedImage + '\'' + + '}'; + } + + public String getBase64EncodedImage() { + return base64EncodedImage; + } + + public void setBase64EncodedImage(String base64EncodedImage) { + this.base64EncodedImage = base64EncodedImage; + } +} diff --git a/Lambdas/Lists/Picture/src/PictureGET.java b/Lambdas/Lists/Picture/src/PictureGET.java new file mode 100644 index 0000000..e5787dd --- /dev/null +++ b/Lambdas/Lists/Picture/src/PictureGET.java @@ -0,0 +1,12 @@ +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import java.util.Map; + +public class PictureGET implements RequestHandler, Object> { + + public Object handleRequest(Map inputMap, Context unfilled) { + return BasicHandler.handleRequest(inputMap, unfilled, PictureGetter.class); + } + +} \ No newline at end of file diff --git a/Lambdas/Lists/Picture/src/PictureGetter.java b/Lambdas/Lists/Picture/src/PictureGetter.java new file mode 100644 index 0000000..136e926 --- /dev/null +++ b/Lambdas/Lists/Picture/src/PictureGetter.java @@ -0,0 +1,34 @@ +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class PictureGetter implements CallHandler { + private final Connection connection; + private final String cognitoID; + + private final String GET_ITEM = "SELECT * FROM Pictures WHERE cognitoID = ?;"; + + public PictureGetter(Connection connection, String cognitoID) { + this.connection = connection; + this.cognitoID = cognitoID; + } + + @Override + public Object conductAction(Map bodyMap, HashMap queryMap, String cognitoID) throws SQLException { + PreparedStatement statement = connection.prepareStatement(GET_ITEM); + if (!queryMap.get("id").toString().equals("profile")) { + throw new IllegalArgumentException("Only profile pictures are currently supported."); + } + statement.setString(1, cognitoID); + System.out.println(statement); + ResultSet queryResults = statement.executeQuery(); + queryResults.first(); + System.out.println(queryResults); + Picture retrievedPicture = new Picture(queryResults); +// System.out.println(retrievedPicture); + return retrievedPicture; + } +} \ No newline at end of file diff --git a/Lambdas/Lists/ListShare/src/ListShareDELETE.java b/Lambdas/Lists/Picture/src/PicturePUT.java similarity index 57% rename from Lambdas/Lists/ListShare/src/ListShareDELETE.java rename to Lambdas/Lists/Picture/src/PicturePUT.java index 784a725..43fc914 100644 --- a/Lambdas/Lists/ListShare/src/ListShareDELETE.java +++ b/Lambdas/Lists/Picture/src/PicturePUT.java @@ -3,10 +3,9 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import java.util.Map; -public class ListShareDELETE implements RequestHandler, Object> { +public class PicturePUT implements RequestHandler, Object> { public Object handleRequest(Map inputMap, Context unfilled) { - return BasicHandler.handleRequest(inputMap, unfilled, ListShareDeleter.class); + return BasicHandler.handleRequest(inputMap, unfilled, PicturePutter.class); } - } diff --git a/Lambdas/Lists/Picture/src/PicturePutter.java b/Lambdas/Lists/Picture/src/PicturePutter.java new file mode 100644 index 0000000..fadff15 --- /dev/null +++ b/Lambdas/Lists/Picture/src/PicturePutter.java @@ -0,0 +1,28 @@ +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class PicturePutter implements CallHandler { + + private Connection connection; + private String cognitoID; + + public PicturePutter(Connection connection, String cognitoID) { + this.connection = connection; + this.cognitoID = cognitoID; + } + + final private String STORE_PICTURE_SQL = "REPLACE INTO Pictures(cognitoID, base64image) VALUES(?, ?);"; + + public Object conductAction(Map bodyMap, HashMap queryString, String cognitoID) throws SQLException { + PreparedStatement storePicture = connection.prepareStatement(STORE_PICTURE_SQL); + storePicture.setString(1, cognitoID); + storePicture.setString(2, bodyMap.get("base64EncodedImage").toString()); + System.out.println(storePicture); + storePicture.executeUpdate(); + connection.commit(); + return null; + } +} \ No newline at end of file diff --git a/Listify/app/build.gradle b/Listify/app/build.gradle index a14ee50..7038265 100644 --- a/Listify/app/build.gradle +++ b/Listify/app/build.gradle @@ -53,4 +53,6 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.8.1' implementation 'com.crystal:crystalrangeseekbar:1.1.3' implementation 'com.chauthai.swipereveallayout:swipe-reveal-layout:1.4.1' + implementation "androidx.cardview:cardview:1.0.0" + } \ No newline at end of file diff --git a/Listify/app/src/main/AndroidManifest.xml b/Listify/app/src/main/AndroidManifest.xml index 7389dd5..cf117a8 100644 --- a/Listify/app/src/main/AndroidManifest.xml +++ b/Listify/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ package="com.example.listify"> + + + + + + + + + profilePictureReceiver = new SynchronousReceiver<>(); + ImageView profilePictureView = navigationView.getHeaderView(0).findViewById(R.id.imageViewProfilePicture); + try { + requestor.getObject("profile", Picture.class, profilePictureReceiver); + profilePictureView.setImageURI(Uri.fromFile(saveImage(profilePictureReceiver.await().getBase64EncodedImage()))); + } catch (Exception e) { + e.printStackTrace(); + } + profilePictureView.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); +// builder.setView(navigationView.getHeaderView(0)); + builder.setTitle("Change picture"); + builder.setMessage("Please select a method to add a new profile picture."); + builder.setCancelable(true); + builder.setPositiveButton("Take picture", (dialog, which) -> { + Intent takePicture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File imageFileLocation = getOutputImageFile(); + Log.i("Profile Picture", "New image file at " + imageFileLocation.getAbsolutePath()); + newImageFileLocation = imageFileLocation; + Uri imageUri = FileProvider.getUriForFile( + MainActivity.this, + BuildConfig.APPLICATION_ID + ".provider", + imageFileLocation); + takePicture.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); + startActivityForResult(takePicture, CAMERA_CAPTURE); + }); + builder.setNegativeButton("Select picture", (dialog, which) -> { + Intent pickPhoto = new Intent(Intent.ACTION_PICK, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + startActivityForResult(pickPhoto, IMAGE_SELECT); + }); + builder.setNeutralButton("Cancel", (dialog, which) -> { + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } + }); + mAppBarConfiguration = new AppBarConfiguration.Builder( R.id.nav_home, R.id.nav_profile, R.id.nav_logout) .setDrawerLayout(drawer) @@ -176,6 +240,144 @@ public class MainActivity extends AppCompatActivity implements CreateListDialogF }); } + //From: https://stackoverflow.com/questions/30005815/convert-encoded-base64-image-to-file-object-in-android + private File saveImage(final String imageData) throws IOException { + final byte[] imgBytesData = android.util.Base64.decode(imageData, + android.util.Base64.DEFAULT); + + final File file = File.createTempFile("profilePicture", null, this.getCacheDir()); + final FileOutputStream fileOutputStream; + try { + fileOutputStream = new FileOutputStream(file); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( + fileOutputStream); + try { + bufferedOutputStream.write(imgBytesData); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + try { + bufferedOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return file; + } + + protected void onActivityResult (int requestCode, + int resultCode, + Intent data) { + Uri selectedImage = null; + Properties configs = new Properties(); + try { + configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); + switch (requestCode){ + case CAMERA_CAPTURE: + Log.i("Profile Picture", "Pulling image file at " + this.newImageFileLocation.getAbsolutePath()); + selectedImage = Uri.fromFile(this.newImageFileLocation); + try { + requestor.putObject(new Picture(fileToString(this.newImageFileLocation))); + } catch (JSONException | IOException jsonException) { + jsonException.printStackTrace(); + } + + break; + case IMAGE_SELECT: + if ((data == null) || (data.getData() == null)) { + return; + } + selectedImage = data.getData(); + try { + requestor.putObject(new Picture(fileToString(new File(getRealPathFromUri(this, selectedImage))))); + } catch (JSONException | IOException exception) { + exception.printStackTrace(); + } + break; + } + + MainActivity.super.onActivityResult(requestCode, resultCode, data); + if (selectedImage == null) { + return; + } + NavigationView navigationView = findViewById(R.id.nav_view); + + ImageView profilePicture = navigationView.getHeaderView(0).findViewById(R.id.imageViewProfilePicture); + profilePicture.setImageURI(selectedImage); + + } + + //From: https://stackoverflow.com/questions/20028319/how-to-convert-content-media-external-images-media-y-to-file-storage-sdc + private static String getRealPathFromUri(Context context, Uri contentUri) { + Cursor cursor = null; + try { + String[] proj = { MediaStore.Images.Media.DATA }; + cursor = context.getContentResolver().query(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + //From: https://stackoverflow.com/questions/27784230/convert-a-file-100mo-in-base64-on-android + private String fileToString(File toStringify) throws IOException { + InputStream inputStream = new FileInputStream(toStringify.getAbsolutePath()); + byte[] buffer = new byte[8192]; + int bytesRead; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + Base64OutputStream output64 = new Base64OutputStream(output, Base64.DEFAULT); + try { + while ((bytesRead = inputStream.read(buffer)) != -1) { + output64.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + e.printStackTrace(); + } + output64.close(); + + return output.toString(); + } + + //getOutputImageFile from https://developer.android.com/guide/topics/media/camera + private static File getOutputImageFile(){ + // To be safe, you should check that the SDCard is mounted + // using Environment.getExternalStorageState() before doing this. + + File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES), "MyCameraApp"); + // This location works best if you want the created images to be shared + // between applications and persist after your app has been uninstalled. + + // Create the storage directory if it does not exist + if (! mediaStorageDir.exists()){ + if (! mediaStorageDir.mkdirs()){ + Log.d("File creation", "failed to create directory"); + return null; + } + } + + // Create a media file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + File mediaFile = new File(mediaStorageDir.getPath() + File.separator + + "IMG_"+ timeStamp + ".jpg"); + Log.i("File creation", mediaFile.toString()); + return mediaFile; + } + @Override public boolean onSupportNavigateUp() { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); @@ -214,5 +416,8 @@ public class MainActivity extends AppCompatActivity implements CreateListDialogF Toast.makeText(this, "An error occurred", Toast.LENGTH_LONG).show(); e.printStackTrace(); } + } + + } \ No newline at end of file diff --git a/Listify/app/src/main/java/com/example/listify/data/ListShare.java b/Listify/app/src/main/java/com/example/listify/data/ListShare.java index 50765cb..3958df8 100644 --- a/Listify/app/src/main/java/com/example/listify/data/ListShare.java +++ b/Listify/app/src/main/java/com/example/listify/data/ListShare.java @@ -11,6 +11,7 @@ public class ListShare { String shareWithEmail; final ListShare[] other; Integer permissionLevel; + Integer uiPosition; private static final Map keysToPerms; static { @@ -24,11 +25,17 @@ public class ListShare { keysToPerms = Collections.unmodifiableMap(keysToPermsTemp); } - public ListShare(Integer listID, String shareWithEmail, Integer permissionLevel, ListShare[] other) { + public ListShare(Integer listID, String shareWithEmail, Integer permissionLevel, Integer uiPosition, ListShare[] other) { this.listID = listID; this.shareWithEmail = shareWithEmail; this.permissionLevel = permissionLevel; this.other = other; + this.uiPosition = uiPosition; + } + + public ListShare(Integer listID, String shareWithEmail, String permissionsRaw, Integer uiPosition, ListShare[] other) { + this(listID, shareWithEmail, permissionsRaw, other); + this.uiPosition = uiPosition; } public ListShare(Integer listID, String shareWithEmail, String permissionsRaw, ListShare[] other) { @@ -42,6 +49,7 @@ public class ListShare { permissionLevel *= keytoPermEntry.getKey(); } } + this.uiPosition = -1; } @Override diff --git a/Listify/app/src/main/java/com/example/listify/data/Picture.java b/Listify/app/src/main/java/com/example/listify/data/Picture.java new file mode 100644 index 0000000..59578cb --- /dev/null +++ b/Listify/app/src/main/java/com/example/listify/data/Picture.java @@ -0,0 +1,24 @@ +package com.example.listify.data; + +public class Picture { + String base64EncodedImage; + + public Picture(String base64EncodedImage) { + this.base64EncodedImage = base64EncodedImage; + } + + @Override + public String toString() { + return "Picture{" + + "base64EncodedImage='" + base64EncodedImage + '\'' + + '}'; + } + + public String getBase64EncodedImage() { + return base64EncodedImage; + } + + public void setBase64EncodedImage(String base64EncodedImage) { + this.base64EncodedImage = base64EncodedImage; + } +} diff --git a/Listify/app/src/main/res/layout/nav_header_main.xml b/Listify/app/src/main/res/layout/nav_header_main.xml index 3f4a0fa..4014ebb 100644 --- a/Listify/app/src/main/res/layout/nav_header_main.xml +++ b/Listify/app/src/main/res/layout/nav_header_main.xml @@ -1,8 +1,9 @@ - - - - - - - - - - - - - + + + - - - - - + + \ No newline at end of file diff --git a/Listify/app/src/main/res/raw/ic_launcher_round.png b/Listify/app/src/main/res/raw/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/Listify/app/src/main/res/raw/ic_launcher_round.png differ diff --git a/Listify/app/src/main/res/xml/file_paths.xml b/Listify/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..3197349 --- /dev/null +++ b/Listify/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file