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 5b77371..2dabe96 100644 --- a/Listify/app/src/main/AndroidManifest.xml +++ b/Listify/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ package="com.example.listify"> + + + + + + + + + storeHeaderIndex = new HashMap<>(); DecimalFormat df = new DecimalFormat("0.00"); + List selectedList; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list); - final int LIST_ID = (int) getIntent().getSerializableExtra("listID"); - final String LIST_NAME = (String) getIntent().getSerializableExtra("listName"); - setTitle(LIST_NAME); + selectedList = (List) getIntent().getSerializableExtra("selectedList"); + setTitle(selectedList.getName()); Properties configs = new Properties(); try { @@ -76,7 +78,7 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { e.printStackTrace(); } requestor = new Requestor(am, configs.getProperty("apiKey")); - requestor.getObject(Integer.toString(LIST_ID), List.class, this); + requestor.getObject(Integer.toString(selectedList.getListID()), List.class, this); listView = findViewById(R.id.listView); myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages); @@ -158,7 +160,7 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { } requestor = new Requestor(am, configs.getProperty("apiKey")); - requestor.getObject(Integer.toString(LIST_ID), List.class, ListPage.this); + requestor.getObject(Integer.toString(selectedList.getListID()), List.class, ListPage.this); } }); } @@ -166,16 +168,16 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { @Override public boolean onCreateOptionsMenu(Menu menu) { //Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.list, menu); - - //return super.onCreateOptionsMenu(menu); + + MenuItem renameItem = menu.findItem(R.id.action_rename_list); renameItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - Toast.makeText(ListPage.this, "Rename List", Toast.LENGTH_SHORT).show(); + RenameListDialogFragment renameListDialog = new RenameListDialogFragment(); + renameListDialog.show(getSupportFragmentManager(), "Rename List"); return false; } }); @@ -193,7 +195,24 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { duplicateItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - Toast.makeText(ListPage.this, "Duplicate List", Toast.LENGTH_SHORT).show(); + + ListDuplicate duplicate = new ListDuplicate(selectedList.getListID(), String.format("%s copy", selectedList.getName())); + + Properties configs = new Properties(); + try { + configs = AuthManager.loadProperties(ListPage.this, "android.resource://" + getPackageName() + "/raw/auths.json"); + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + + requestor = new Requestor(am, configs.getProperty("apiKey")); + try { + requestor.postObject(duplicate); + } catch (JSONException e) { + e.printStackTrace(); + } + + Toast.makeText(ListPage.this, "List duplicated", Toast.LENGTH_SHORT).show(); return false; } }); @@ -202,6 +221,24 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { exportItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { + StringBuilder listContents = new StringBuilder(); + + for (int i = 0; i < pNames.size(); i++) { + if (Integer.parseInt(pQuantity.get(i)) == -1) { + listContents.append(String.format("\n%s\n", pNames.get(i))); + } else { + listContents.append(String.format(" %s of %s\n", pQuantity.get(i), pNames.get(i))); + } + } + + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, listContents.toString()); + sendIntent.setType("text/plain"); + + Intent shareIntent = Intent.createChooser(sendIntent, null); + startActivity(shareIntent); + Toast.makeText(ListPage.this, "Export List", Toast.LENGTH_SHORT).show(); return false; } @@ -213,22 +250,16 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { @Override public void acceptDelivery(Object delivered) { // Clear out old values - runOnUiThread(new Runnable() { - @Override - public void run() { - pNames.clear(); - pStores.clear(); - pPrices.clear(); - pQuantity.clear(); - pImages.clear(); - totalPriceByStore.clear(); - storeID2Name.clear(); - storeHeaderIndex.clear(); - pListItemPair.clear(); - totalPrice = 0; - tvTotalPrice.setText(String.format("$%.2f", totalPrice)); - } - }); + pNames.clear(); + pStores.clear(); + pPrices.clear(); + pQuantity.clear(); + pImages.clear(); + totalPriceByStore.clear(); + storeID2Name.clear(); + storeHeaderIndex.clear(); + pListItemPair.clear(); + totalPrice = 0; List list = (List) delivered; @@ -326,9 +357,44 @@ public class ListPage extends AppCompatActivity implements Requestor.Receiver { }); } + runOnUiThread(new Runnable() { + @Override + public void run() { + tvTotalPrice.setText(String.format("$%.2f", totalPrice)); + } + }); + refreshList.setRefreshing(false); } + @Override + public void sendRenameListName(String name) { + selectedList.setName(name); + + Properties configs = new Properties(); + try { + configs = AuthManager.loadProperties(ListPage.this, "android.resource://" + getPackageName() + "/raw/auths.json"); + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + + requestor = new Requestor(am, configs.getProperty("apiKey")); + try { + requestor.putObject(selectedList); + } catch (Exception e) { + e.printStackTrace(); + } + + + runOnUiThread(new Runnable() { + @Override + public void run() { + setTitle(name); + Toast.makeText(ListPage.this, "List Renamed", Toast.LENGTH_SHORT).show(); + } + }); + } + class MyAdapter extends ArrayAdapter { Context context; ArrayList pNames; diff --git a/Listify/app/src/main/java/com/example/listify/MainActivity.java b/Listify/app/src/main/java/com/example/listify/MainActivity.java index 8053dd0..7bbaf0c 100644 --- a/Listify/app/src/main/java/com/example/listify/MainActivity.java +++ b/Listify/app/src/main/java/com/example/listify/MainActivity.java @@ -1,15 +1,26 @@ package com.example.listify; +import android.app.AlertDialog; +import android.content.Context; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; +import android.provider.MediaStore; +import android.util.Base64; +import android.util.Base64OutputStream; import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.core.content.FileProvider; import androidx.drawerlayout.widget.DrawerLayout; import androidx.navigation.NavController; import androidx.navigation.Navigation; @@ -17,15 +28,15 @@ import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; import com.amplifyframework.auth.AuthException; import com.example.listify.data.List; -import com.example.listify.data.ListDuplicate; -import com.example.listify.data.ListReposition; -import com.example.listify.data.SearchHistory; +import com.example.listify.data.Picture; import com.example.listify.ui.LoginPage; import com.google.android.material.navigation.NavigationView; import org.json.JSONException; -import java.io.IOException; +import java.io.*; +import java.text.SimpleDateFormat; import java.time.Instant; +import java.util.Date; import java.util.Properties; import static com.example.listify.SplashActivity.showSplash; @@ -33,6 +44,9 @@ import static com.example.listify.SplashActivity.showSplash; public class MainActivity extends AppCompatActivity implements CreateListDialogFragment.OnNewListListener { private AppBarConfiguration mAppBarConfiguration; public static AuthManager am = new AuthManager(); + private File newImageFileLocation = null; + private final int CAMERA_CAPTURE = 1; + private final int IMAGE_SELECT = 2; @Override public void onBackPressed() { @@ -151,12 +165,62 @@ public class MainActivity extends AppCompatActivity implements CreateListDialogF } //------------------------------------------------------------------------------------------// - setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); + + TextView emailView = navigationView.getHeaderView(0).findViewById(R.id.textViewEmailSidebar); + emailView.setText(am.getEmail()); + 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")); + SynchronousReceiver 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/RenameListDialogFragment.java b/Listify/app/src/main/java/com/example/listify/RenameListDialogFragment.java new file mode 100644 index 0000000..1b82b6d --- /dev/null +++ b/Listify/app/src/main/java/com/example/listify/RenameListDialogFragment.java @@ -0,0 +1,71 @@ +package com.example.listify; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + + +public class RenameListDialogFragment extends DialogFragment { + + public interface OnRenameListListener { + void sendRenameListName(String name); + } + + public OnRenameListListener onRenameListListener; + + EditText etRenameListName; + + public RenameListDialogFragment() {} + + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + // Get the layout inflater + LayoutInflater inflater = requireActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + View root = inflater.inflate(R.layout.dialog_rename_list, null); + builder.setView(root) + // Add action buttons + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + onRenameListListener.sendRenameListName(etRenameListName.getText().toString()); + } + }) + .setNegativeButton("cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + RenameListDialogFragment.this.getDialog().cancel(); + } + }); + + etRenameListName = (EditText) root.findViewById(R.id.et_renamed_list_name); + + return builder.create(); + } + + // Required to extend DialogFragment + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + onRenameListListener = (OnRenameListListener) getTargetFragment(); + if (onRenameListListener == null) { + onRenameListListener = (OnRenameListListener) getActivity(); + } + } catch (ClassCastException e) { + Log.e("CreateListDialogFragment", "onAttach: ClassCastException: " + e.getMessage()); + } + } +} diff --git a/Listify/app/src/main/java/com/example/listify/adapter/ShareeSwipeableAdapter.java b/Listify/app/src/main/java/com/example/listify/adapter/ShareeSwipeableAdapter.java index ac8ee0e..0c5327b 100644 --- a/Listify/app/src/main/java/com/example/listify/adapter/ShareeSwipeableAdapter.java +++ b/Listify/app/src/main/java/com/example/listify/adapter/ShareeSwipeableAdapter.java @@ -79,7 +79,7 @@ public class ShareeSwipeableAdapter extends BaseAdapter { holder.swipeLayout = (SwipeRevealLayout)convertView.findViewById(R.id.swipe_layout); holder.frontView = convertView.findViewById(R.id.front_layout); holder.deleteList = convertView.findViewById(R.id.delete_list); - holder.shareList = convertView.findViewById(R.id.share_list); +// holder.shareList = convertView.findViewById(R.id.share_list); holder.textView = (TextView) convertView.findViewById(R.id.shopping_list_name); convertView.setTag(holder); @@ -101,12 +101,12 @@ public class ShareeSwipeableAdapter extends BaseAdapter { } }); - holder.shareList.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - - } - }); +// holder.shareList.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// +// } +// }); holder.frontView.setOnClickListener(new View.OnClickListener() { @Override diff --git a/Listify/app/src/main/java/com/example/listify/adapter/ShoppingListsSwipeableAdapter.java b/Listify/app/src/main/java/com/example/listify/adapter/ShoppingListsSwipeableAdapter.java index 9716ec1..1ecf2b6 100644 --- a/Listify/app/src/main/java/com/example/listify/adapter/ShoppingListsSwipeableAdapter.java +++ b/Listify/app/src/main/java/com/example/listify/adapter/ShoppingListsSwipeableAdapter.java @@ -80,7 +80,7 @@ public class ShoppingListsSwipeableAdapter extends BaseAdapter { holder.swipeLayout = (SwipeRevealLayout)convertView.findViewById(R.id.swipe_layout); holder.frontView = convertView.findViewById(R.id.front_layout); holder.deleteList = convertView.findViewById(R.id.delete_list); - holder.shareList = convertView.findViewById(R.id.share_list); +// holder.shareList = convertView.findViewById(R.id.share_list); holder.listName = (TextView) convertView.findViewById(R.id.shopping_list_name); holder.itemCount = (TextView) convertView.findViewById(R.id.shopping_list_item_count); @@ -101,7 +101,11 @@ public class ShoppingListsSwipeableAdapter extends BaseAdapter { holder.listName.setText(curList.getName()); } - holder.itemCount.setText(String.format("%d items", curList.getEntries().length)); + if (curList.getEntries() != null) { + holder.itemCount.setText(String.format("%d items", curList.getEntries().length)); + } else { + holder.itemCount.setText("0 items"); + } holder.deleteList.setOnClickListener(new View.OnClickListener() { @Override @@ -139,9 +143,8 @@ public class ShoppingListsSwipeableAdapter extends BaseAdapter { public void onClick(View v) { Intent listPage = new Intent(activity, ListPage.class); - // Send the list ID and list name - listPage.putExtra("listID", curList.getListID()); - listPage.putExtra("listName", curList.getName()); + // Send the selected list + listPage.putExtra("selectedList", curList); activity.startActivity(listPage); } @@ -154,7 +157,7 @@ public class ShoppingListsSwipeableAdapter extends BaseAdapter { SwipeRevealLayout swipeLayout; View frontView; View deleteList; - View shareList; +// View shareList; TextView listName; TextView itemCount; } diff --git a/Listify/app/src/main/java/com/example/listify/data/List.java b/Listify/app/src/main/java/com/example/listify/data/List.java index 47f3971..79fc8d1 100644 --- a/Listify/app/src/main/java/com/example/listify/data/List.java +++ b/Listify/app/src/main/java/com/example/listify/data/List.java @@ -1,8 +1,9 @@ package com.example.listify.data; +import java.io.Serializable; import java.util.Arrays; -public class List { +public class List implements Serializable { Integer listID; String name; String owner; diff --git a/Listify/app/src/main/java/com/example/listify/data/ListEntry.java b/Listify/app/src/main/java/com/example/listify/data/ListEntry.java index e482079..66720e8 100644 --- a/Listify/app/src/main/java/com/example/listify/data/ListEntry.java +++ b/Listify/app/src/main/java/com/example/listify/data/ListEntry.java @@ -1,6 +1,8 @@ package com.example.listify.data; -public class ListEntry { +import java.io.Serializable; + +public class ListEntry implements Serializable { Integer listID; Integer productID; Integer quantity; 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/dialog_rename_list.xml b/Listify/app/src/main/res/layout/dialog_rename_list.xml new file mode 100644 index 0000000..55ca2e1 --- /dev/null +++ b/Listify/app/src/main/res/layout/dialog_rename_list.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file 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/layout/shopping_lists_swipeable_name_item.xml b/Listify/app/src/main/res/layout/shopping_lists_swipeable_name_item.xml index 9ecf6db..df2c9e7 100644 --- a/Listify/app/src/main/res/layout/shopping_lists_swipeable_name_item.xml +++ b/Listify/app/src/main/res/layout/shopping_lists_swipeable_name_item.xml @@ -17,12 +17,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - + + + + + + + + + \ No newline at end of file