diff --git a/app/MyHyvesBookPlusStagram/app/src/main/AndroidManifest.xml b/app/MyHyvesBookPlusStagram/app/src/main/AndroidManifest.xml index 2284c0c..23c8670 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/AndroidManifest.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/AndroidManifest.xml @@ -26,6 +26,7 @@ @@ -34,7 +35,9 @@ - + diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/CameraFragment.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/CameraFragment.java index 3bdc1b5..58511ba 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/CameraFragment.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/CameraFragment.java @@ -1,13 +1,10 @@ package nl.myhyvesbookplus.tagram; import android.app.Activity; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Camera; import android.hardware.Camera.PictureCallback; -import android.media.Image; -import android.net.Uri; import android.os.Bundle; import android.app.Fragment; import android.support.design.widget.FloatingActionButton; @@ -19,91 +16,50 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.LinearLayout; import android.widget.RelativeLayout; import nl.myhyvesbookplus.tagram.controller.PostUploader; import nl.myhyvesbookplus.tagram.model.BitmapPost; -/** - * A simple {@link Fragment} subclass. - * Activities that contain this fragment must implement the - * {@link CameraFragment.OnFragmentInteractionListener} interface - * to handle interaction events. - * Use the {@link CameraFragment#newInstance} factory method to - * create an instance of this fragment. - */ public class CameraFragment extends Fragment implements PostUploader.PostUploadListener{ - // TODO: Rename parameter arguments, choose names that match private static final String TAG = "CameraFragment"; - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - private static final String ARG_PARAM1 = "param1"; - private static final String ARG_PARAM2 = "param2"; - - // TODO: Rename and change types of parameters - private String mParam1; - private String mParam2; - - private OnFragmentInteractionListener mListener; - private Camera mCamera; private CameraPreview mPreview; private Bitmap mPhoto; private int facing = Camera.CameraInfo.CAMERA_FACING_BACK; - public CameraFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * - * @param param1 Parameter 1. - * @param param2 Parameter 2. - * @return A new instance of fragment CameraFragment. - */ - // TODO: Rename and change types and number of parameters - public static CameraFragment newInstance(String param1, String param2) { - CameraFragment fragment = new CameraFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - mParam1 = getArguments().getString(ARG_PARAM1); - mParam2 = getArguments().getString(ARG_PARAM2); - } - } + /* Required empty public constructor */ + public CameraFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment - ((AppCompatActivity)getActivity()).getSupportActionBar().hide(); - getActivity().findViewById(R.id.content).setPadding(0,0,0,0); - final View view = inflater.inflate(R.layout.fragment_camera, container, false); + final RelativeLayout filterButtons = (RelativeLayout) view.findViewById(R.id.filter_buttons); + final RelativeLayout mCameraLayout = (RelativeLayout) view.findViewById(R.id.camera_preview); + final LinearLayout commentBox = (LinearLayout) view.findViewById(R.id.comment_box); + final ImageButton pictureButton = (ImageButton) view.findViewById(R.id.picture_button); + final ImageButton switchButton = (ImageButton) view.findViewById(R.id.switch_camera_button); + + // Hide the action bar + ((AppCompatActivity)getActivity()).getSupportActionBar().hide(); + mCamera = getCameraInstance(facing); mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera); - final RelativeLayout filterButtons = (RelativeLayout) view.findViewById(R.id.filter_buttons); - final RelativeLayout mCameraLayout = (RelativeLayout) view.findViewById(R.id.camera_preview); mCameraLayout.addView(mPreview); - // Draw buttons over preview - view.findViewById(R.id.picture_button).bringToFront(); - view.findViewById(R.id.switch_camera_button).bringToFront(); + // Draw initial buttons over preview + pictureButton.bringToFront(); + switchButton.bringToFront(); filterButtons.bringToFront(); - (view.findViewById(R.id.switch_camera_button)).setOnClickListener(new View.OnClickListener() { + /* Upon pressing the switch camera facing button: */ + switchButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switchFacing(); @@ -114,12 +70,13 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera); mCameraLayout.addView(mPreview); - view.findViewById(R.id.picture_button).bringToFront(); - view.findViewById(R.id.switch_camera_button).bringToFront(); + pictureButton.bringToFront(); + switchButton.bringToFront(); } }); - (view.findViewById(R.id.picture_button)).setOnClickListener(new View.OnClickListener() { + /* Upon pressing the take photo button: */ + pictureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCamera.takePicture(null, null, new PictureCallback() { @@ -141,17 +98,19 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL } }); + /* Upon pressing the upload button: */ (view.findViewById(R.id.upload_button)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - view.findViewById(R.id.comment_box).setClickable(true); - view.findViewById(R.id.comment_box).setVisibility(View.VISIBLE); - view.findViewById(R.id.comment_box).bringToFront(); - view.findViewById(R.id.filter_buttons).setVisibility(View.GONE); + commentBox.setClickable(true); + commentBox.setVisibility(View.VISIBLE); + commentBox.bringToFront(); + filterButtons.setVisibility(View.GONE); ((FloatingActionButton)view.findViewById(R.id.upload_button)).hide(); } }); + /* Upon pressing the enter button on the virtual keyboard: */ (view.findViewById(R.id.comment_submit)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -175,13 +134,15 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera); mCameraLayout.addView(mPreview); - view.findViewById(R.id.picture_button).bringToFront(); - view.findViewById(R.id.switch_camera_button).bringToFront(); + pictureButton.bringToFront(); + switchButton.bringToFront(); mCameraLayout.removeView(view.findViewById(R.id.pic_preview)); + hideKeyboard(); } }); + /* Upon pressing the cancel button: */ (view.findViewById(R.id.comment_cancel)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -199,13 +160,15 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera); mCameraLayout.addView(mPreview); - view.findViewById(R.id.picture_button).bringToFront(); - view.findViewById(R.id.switch_camera_button).bringToFront(); + pictureButton.bringToFront(); + switchButton.bringToFront(); mCameraLayout.removeView(view.findViewById(R.id.pic_preview)); + hideKeyboard(); } }); + /* Upon pressing the left arrow filter change button: */ (view.findViewById(R.id.filter_left)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -224,6 +187,7 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL } }); + /* Upon pressing the right arrow filter change button: */ (view.findViewById(R.id.filter_right)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -242,58 +206,31 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL } }); - (view.findViewById(R.id.comment_text)).setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus) { - hideKeyboard(v); - } - } - }); - return view; } - public void hideKeyboard(View view) { - InputMethodManager inputMethodManager =(InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - // TODO: Rename method, update argument and hook method into UI event - public void onButtonPressed(Uri uri) { - if (mListener != null) { - mListener.onFragmentInteraction(uri); - } - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (context instanceof OnFragmentInteractionListener) { - mListener = (OnFragmentInteractionListener) context; - } else { - throw new RuntimeException(context.toString() - + " must implement OnFragmentInteractionListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; + /** + * Hides keyboard after submit, upload or cancel button gets pressed. + */ + public void hideKeyboard() { + ((InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE)) + .toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } + /** + * Restores the action bar when exiting the fragment. + */ @Override public void onDestroyView() { super.onDestroyView(); - - int padding = 16; - float scale = getResources().getDisplayMetrics().density; - int dp = (int) (padding * scale + 0.5f); ((AppCompatActivity)getActivity()).getSupportActionBar().show(); - getActivity().findViewById(R.id.content).setPadding(dp,dp,dp,dp); } + /** + * Start the camera. + * @param facing The direction in which the camera should be initialized (back by default). + * @return the result of the opened camera, if successful. + */ public static Camera getCameraInstance(int facing) { Camera c = null; try { @@ -304,13 +241,27 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL return c; } + + /** + * Switch between front facing camera and the back camera. + */ public void switchFacing() { if (facing == Camera.CameraInfo.CAMERA_FACING_FRONT) facing = Camera.CameraInfo.CAMERA_FACING_BACK; else facing = Camera.CameraInfo.CAMERA_FACING_FRONT; +// TODO +// facing = +// facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? +// Camera.CameraInfo.CAMERA_FACING_BACK : +// Camera.CameraInfo.CAMERA_FACING_FRONT; } + /** + * Change which buttons are visible during the different stages on the camera fragment. + * + * @param view The current view upon which the buttons need to be placed or removed. + */ public void switchButtons(View view) { FloatingActionButton upload = (FloatingActionButton) view.findViewById(R.id.upload_button); ImageButton picButton = (ImageButton) view.findViewById(R.id.picture_button); @@ -335,34 +286,8 @@ public class CameraFragment extends Fragment implements PostUploader.PostUploadL } } - @Override - public void onPause() { - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - } - @Override public void PostUploadComplete(Boolean success) { } - - /** - * This interface must be implemented by activities that contain this - * fragment to allow an interaction in this fragment to be communicated - * to the activity and potentially other fragments contained in that - * activity. - *

- * See the Android Training lesson Communicating with Other Fragments for more information. - */ - public interface OnFragmentInteractionListener { - // TODO: Update argument type and name - void onFragmentInteraction(Uri uri); - } - } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/LoginActivity.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/LoginActivity.java index 46befe8..438db9f 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/LoginActivity.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/LoginActivity.java @@ -181,7 +181,7 @@ public class LoginActivity extends AppCompatActivity implements View.OnClickList * @param passwordString the entered password */ protected void logIn(String emailString, String passwordString) { - progressDialog = ProgressDialog.show(LoginActivity.this, getString(R.string.please_wait), "Logging in", true, false); + progressDialog = ProgressDialog.show(LoginActivity.this, getString(R.string.please_wait), getString(R.string.logging_in), true, false); mAuth.signInWithEmailAndPassword(emailString, passwordString) .addOnCompleteListener(this, new OnCompleteListener() { @@ -211,7 +211,7 @@ public class LoginActivity extends AppCompatActivity implements View.OnClickList * @param password the entered password */ protected void registerUser(String email, String password) { - this.progressDialog = ProgressDialog.show(LoginActivity.this, getString(R.string.please_wait), "Registering", true, false); + this.progressDialog = ProgressDialog.show(LoginActivity.this, getString(R.string.please_wait), getString(R.string.registering), true, false); mAuth.createUserWithEmailAndPassword(email, password) .addOnCompleteListener(this, new OnCompleteListener() { @Override diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/MainActivity.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/MainActivity.java index 8772d76..993d195 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/MainActivity.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/MainActivity.java @@ -20,9 +20,10 @@ import nl.myhyvesbookplus.tagram.controller.PostUploader; import nl.myhyvesbookplus.tagram.controller.ProfilePictureUploader; public class MainActivity extends AppCompatActivity implements - CameraFragment.OnFragmentInteractionListener, ProfilePictureUploader.ProfilePictureUpdatedListener, - DownloadClass.PostDownloadListener, PostUploader.PostUploadListener { + DownloadClass.PostDownloadListener, + PostUploader.PostUploadListener { + final static private String TAG = "MainScreen"; FirebaseAuth mAuth; @@ -89,11 +90,6 @@ public class MainActivity extends AppCompatActivity implements finish(); } - @Override - public void onFragmentInteraction(Uri uri) { - - } - public void logOutOnClick(View view) { FirebaseAuth.getInstance().signOut(); goToLogin(); @@ -107,7 +103,6 @@ public class MainActivity extends AppCompatActivity implements @Override public void ProfilePictureUpdated(Boolean success) { - Log.d(TAG, "ProfilePictureUpdated: Ja ik luister naar je!"); FragmentManager man = getFragmentManager(); ProfileFragment frag = (ProfileFragment) man.findFragmentById(R.id.content); FragmentTransaction transaction = man.beginTransaction(); @@ -122,11 +117,13 @@ public class MainActivity extends AppCompatActivity implements public void PostDownloaded() { FragmentManager fragmentManager = getFragmentManager(); Fragment frag = fragmentManager.findFragmentById(R.id.content); - Log.d(TAG, "PostDownloaded: " + R.id.content); + + if (frag instanceof ProfileFragment) { ((ProfileFragment) frag).startList(); } else if (frag instanceof TimelineFragment) { +// ((TimelineFragment) frag).progressDialog.dismiss(); ((TimelineFragment) frag).startList(); } } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java index 357876a..e73a816 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileAdapter.java @@ -1,15 +1,22 @@ package nl.myhyvesbookplus.tagram; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import com.bumptech.glide.Glide; import com.firebase.ui.storage.images.FirebaseImageLoader; @@ -33,6 +40,7 @@ public class ProfileAdapter extends BaseAdapter { private TextView comment; private TextView nietSlechts; private ImageView photo; + private Animator mCurrentAnimator; ProfileAdapter(Context context, ArrayList data) { mContext = context; @@ -61,19 +69,163 @@ public class ProfileAdapter extends BaseAdapter { View newRowView = findViews(rowView); UriPost post = (UriPost) getItem(position); comment.setText(post.getComment()); - StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); + final StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); Glide.with(mContext) .using(new FirebaseImageLoader()) .load(ref) .into(photo); + photo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomImageFromThumb(photo, ref); + } + }); + return newRowView; } - protected View findViews(View rowView) { + private View findViews(View rowView) { comment = (TextView) rowView.findViewById(R.id.comment_timeline_profile); nietSlechts = (TextView) rowView.findViewById(R.id.niet_slecht_count_profile); photo = (ImageView) rowView.findViewById(R.id.timeline_image_profile); return rowView; } + + /** + * https://developer.android.com/training/animation/zoom.html + * "Zooms" in a thumbnail view by assigning the high resolution image to a hidden "zoomed-in" + * image view and animating its bounds to fit the entire activity content area. + * + * @param thumbView The thumbnail view to zoom in. + * @param imageRef The high-resolution version of the image represented by the thumbnail. + */ + private void zoomImageFromThumb(final View thumbView, StorageReference imageRef) { + // If there's an animation in progress, cancel it immediately and proceed with this one. + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Load the high-resolution "zoomed-in" image. + final ImageView hiddenView = (ImageView) ((MainActivity) mContext).findViewById(R.id.expanded_image_profile); + + Glide.with(mContext) + .using(new FirebaseImageLoader()) + .load(imageRef) + .into(hiddenView); + + // Calculate the starting and ending bounds for the zoomed-in image. This step + // involves lots of math. Yay, math. + final Rect startBounds = new Rect(); + final Rect finalBounds = new Rect(); + final Point globalOffset = new Point(); + + // The start bounds are the global visible rectangle of the thumbnail, and the + // final bounds are the global visible rectangle of the container view. Also + // set the container view's offset as the origin for the bounds, since that's + // the origin for the positioning animation properties (X, Y). + thumbView.getGlobalVisibleRect(startBounds); + ((MainActivity) mContext).findViewById(R.id.relative_layout_timeline_profile).getGlobalVisibleRect(finalBounds, globalOffset); + startBounds.offset(-globalOffset.x, -globalOffset.y); + finalBounds.offset(-globalOffset.x, -globalOffset.y); + + // Adjust the start bounds to be the same aspect ratio as the final bounds using the + // "center crop" technique. This prevents undesirable stretching during the animation. + // Also calculate the start scaling factor (the end scaling factor is always 1.0). + float startScale; + if ((float) finalBounds.width() / finalBounds.height() + > (float) startBounds.width() / startBounds.height()) { + // Extend start bounds horizontally + startScale = (float) startBounds.height() / finalBounds.height(); + float startWidth = startScale * finalBounds.width(); + float deltaWidth = (startWidth - startBounds.width()) / 2; + startBounds.left -= deltaWidth; + startBounds.right += deltaWidth; + } else { + // Extend start bounds vertically + startScale = (float) startBounds.width() / finalBounds.width(); + float startHeight = startScale * finalBounds.height(); + float deltaHeight = (startHeight - startBounds.height()) / 2; + startBounds.top -= deltaHeight; + startBounds.bottom += deltaHeight; + } + + // Hide the thumbnail and show the zoomed-in view. When the animation begins, + // it will position the zoomed-in view in the place of the thumbnail. + thumbView.setAlpha(0f); + hiddenView.setVisibility(View.VISIBLE); + + // Set the pivot point for SCALE_X and SCALE_Y transformations to the top-left corner of + // the zoomed-in view (the default is the center of the view). + hiddenView.setPivotX(0f); + hiddenView.setPivotY(0f); + + // Construct and run the parallel animation of the four translation and scale properties + // (X, Y, SCALE_X, and SCALE_Y). + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left, + finalBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top, + finalBounds.top)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_X, startScale, 1f)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_Y, startScale, 1f)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + + // Upon clicking the zoomed-in image, it should zoom back down to the original bounds + // and show the thumbnail instead of the expanded image. + final float startScaleFinal = startScale; + hiddenView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Animate the four positioning/sizing properties in parallel, back to their + // original values. + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_X, startScaleFinal)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_Y, startScaleFinal)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + } + }); + } } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileFragment.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileFragment.java index 4f33ef2..43493f4 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileFragment.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/ProfileFragment.java @@ -9,7 +9,6 @@ import android.os.Environment; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.content.FileProvider; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,6 +16,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.ListView; +import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.bumptech.glide.Glide; @@ -38,41 +38,56 @@ import static android.app.Activity.RESULT_OK; public class ProfileFragment extends Fragment implements View.OnClickListener { static final int REQUEST_TAKE_PHOTO = 1; + ProgressDialog progressDialog; - /// Views, buttons and other protected declarations /// + /* Views, buttons and other protected and private inits */ protected Button changePwdButton; protected ImageButton profilePicButton; protected StorageReference httpsReference; protected TextView profileName; protected ImageView profilePicture; protected FirebaseUser user; - protected File photoFile = null; + protected File photoFile; + private ListView listView; private DownloadClass downloadClass; + private View headerInflater; + private View timeLineInflater; + private ProgressBar progressBar; - ProgressDialog progressDialog; - - /// Required empty public constructor /// - + /* Required empty public constructor */ public ProfileFragment() {} + /** + * Overridden onCreate which initializes a user and sets the default photoFile to null. + * @param savedInstanceState The standard return of the onCreate method. + */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); user = FirebaseAuth.getInstance().getCurrentUser(); + photoFile = null; } /** - * Assigns all views and buttons. + * Assigns all views and buttons for the header. */ - protected void findViews(View view) { - profilePicButton = (ImageButton) view.findViewById(R.id.profile_pic_button); - profilePicture = (ImageView) view.findViewById(R.id.imageView_profile_picture); - profileName = (TextView) view.findViewById(R.id.profile_name); - changePwdButton = (Button) view.findViewById(R.id.change_psw_button); + protected void findHeaderViews() { + profilePicButton = (ImageButton) headerInflater.findViewById(R.id.profile_pic_button); + profilePicture = (ImageView) headerInflater.findViewById(R.id.imageView_profile_picture); + profileName = (TextView) headerInflater.findViewById(R.id.profile_name); + changePwdButton = (Button) headerInflater.findViewById(R.id.change_psw_button); bindOnClick(); } + /** + * Assign the ListView and add the header to it. + */ + protected void findTimelineViews() { + listView = (ListView) timeLineInflater.findViewById(R.id.list); + listView.addHeaderView(headerInflater); + } + /** * Bind the buttons to their listeners. */ @@ -81,19 +96,27 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { changePwdButton.setOnClickListener(this); } - /// Page setup /// - + /** + * Overridden onCreateView which serves as a fragment content creator. + * Checks for user data to be displayed. + * + * @param inflater The inflater used for the fragment. + * @param container The container which holds this fragment. + * @param savedInstanceState The state which was provided by onCreate. + * @return the timeLineInflater View which is required for the ListView to be updated. + */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View viewTimeline = inflater.inflate(R.layout.fragment_profile_timeline, container, false); + timeLineInflater = inflater.inflate(R.layout.fragment_profile_timeline, container, false); + headerInflater = inflater.inflate(R.layout.fragment_profile_header, listView, false); + progressBar = (ProgressBar) timeLineInflater.findViewById(R.id.progressbar_timeline); + progressBar.setVisibility(View.VISIBLE); + findHeaderViews(); + findTimelineViews(); - - listView = (ListView) viewTimeline.findViewById(R.id.listview_profile); - View viewHeader = inflater.inflate(R.layout.fragment_profile_header, listView, false); - findViews(viewHeader); - listView.addHeaderView(viewHeader); + profilePicture.invalidate(); if (user != null) { if(user.getPhotoUrl() != null) { @@ -109,22 +132,19 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { Glide.with(this).using(new FirebaseImageLoader()).load(httpsReference).into(profilePicture); } - profilePicture.invalidate(); - downloadClass = new DownloadClass(getActivity()); downloadClass.getPostsFromServer(); - - return viewTimeline; + return timeLineInflater; } /** * Called when a view has been clicked. * - * @param v The view that was clicked. + * @param view The view that was clicked. */ @Override - public void onClick(View v) { - switch (v.getId()) { + public void onClick(View view) { + switch (view.getId()) { case R.id.profile_pic_button: profilePicOnClick(); break; @@ -139,13 +159,13 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { */ private void profilePicOnClick() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - /* Ensure that there's a camera activity to handle the intent */ + if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { - /* Create the File where the photo should go */ try { photoFile = createImageFile(); } catch (IOException ex) { - Toast.makeText(getActivity(), getString(R.string.image_save_error), Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), getString(R.string.image_save_error), + Toast.LENGTH_LONG).show(); } if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(getActivity(), @@ -157,13 +177,18 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { } } + /** + * Start display of the list; uses an adapter and listener in the main activity. + */ public void startList() { - ProfileAdapter adapter = new ProfileAdapter(getActivity(), downloadClass.getmList()); - listView.setAdapter(adapter); + ProfileAdapter adapter = new ProfileAdapter(getActivity(), downloadClass.getOwnPosts()); + listView.setAdapter(adapter); + progressBar.setVisibility(View.GONE); } /** * Grabs the image just taken by the built-in camera and pushes this image to the user account. + * * @param requestCode The code which corresponds to REQUEST_TAKE_PHOTO. Used as indicator. * @param resultCode Code should be RESULT_OK to allow camera to proceed. * @param data The image data from the camera. @@ -177,8 +202,13 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { } } + /** + * Create the file which the camera requires to save a proper quality picture to. + * + * @return The new file. + * @throws IOException when insufficient permission or storage available. + */ private File createImageFile() throws IOException { - // Create an image file name String imageFileName = "JPEG_" + user.getUid(); File storageDir = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES); return File.createTempFile( @@ -188,9 +218,6 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { ); } - - - // TODO Make this function into its own class for modularity. /** * Performs password reset action. */ @@ -206,8 +233,8 @@ public class ProfileFragment extends Fragment implements View.OnClickListener { Toast.LENGTH_SHORT).show(); } }); - } else { - // TODO Add code here for when there is no currently active user. } } } + + diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java index 8282574..ed92a63 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimeLineAdapter.java @@ -1,16 +1,23 @@ package nl.myhyvesbookplus.tagram; import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; import android.support.annotation.NonNull; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import com.bumptech.glide.Glide; import com.firebase.ui.storage.images.FirebaseImageLoader; @@ -35,6 +42,7 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl private Context mContext; private ArrayList mData; private DatabaseReference mRef; + private Animator mCurrentAnimator; TimeLineAdapter(Context context, ArrayList data) { mContext = context; @@ -62,11 +70,11 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl public View getView(final int position, View convertView, ViewGroup parent) { View rowView = mInflater.inflate(R.layout.list_item_timeline, parent, false); -// TextView userName = (TextView) rowView.findViewById(R.id.username_timeline); + TextView userName = (TextView) rowView.findViewById(R.id.username_timeline); TextView comment = (TextView) rowView.findViewById(R.id.comment_timeline); final TextView nietSlechts = (TextView) rowView.findViewById(R.id.niet_slecht_count); TextView dateTime = (TextView) rowView.findViewById(R.id.timeline_date); - ImageView photo = (ImageView) rowView.findViewById(R.id.timeline_image); + final ImageView photo = (ImageView) rowView.findViewById(R.id.timeline_image); final ImageButton nietSlechtButton = (ImageButton) rowView.findViewById(R.id.niet_slecht_button); final UriPost post = (UriPost) getItem(position); @@ -91,12 +99,18 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl dateTime.setText(post.getDate().toString()); - StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); + final StorageReference ref = FirebaseStorage.getInstance().getReferenceFromUrl(post.getUri()); Glide.with(mContext) .using(new FirebaseImageLoader()) .load(ref) .into(photo); + photo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + zoomImageFromThumb(photo, ref); + } + }); return rowView; } @@ -119,4 +133,140 @@ public class TimeLineAdapter extends BaseAdapter implements AdapterView.OnItemCl Log.d(TAG, "onItemClick: rowNumber! "+ position); } + /** + * https://developer.android.com/training/animation/zoom.html + * "Zooms" in a thumbnail view by assigning the high resolution image to a hidden "zoomed-in" + * image view and animating its bounds to fit the entire activity content area. + * + * @param thumbView The thumbnail view to zoom in. + * @param imageRef The high-resolution version of the image represented by the thumbnail. + */ + private void zoomImageFromThumb(final View thumbView, StorageReference imageRef) { + // If there's an animation in progress, cancel it immediately and proceed with this one. + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Load the high-resolution "zoomed-in" image. + final ImageView hiddenView = (ImageView) ((MainActivity) mContext).findViewById(R.id.expanded_image); + + Glide.with(mContext) + .using(new FirebaseImageLoader()) + .load(imageRef) + .into(hiddenView); + + // Calculate the starting and ending bounds for the zoomed-in image. This step + // involves lots of math. Yay, math. + final Rect startBounds = new Rect(); + final Rect finalBounds = new Rect(); + final Point globalOffset = new Point(); + + // The start bounds are the global visible rectangle of the thumbnail, and the + // final bounds are the global visible rectangle of the container view. Also + // set the container view's offset as the origin for the bounds, since that's + // the origin for the positioning animation properties (X, Y). + thumbView.getGlobalVisibleRect(startBounds); + ((MainActivity) mContext).findViewById(R.id.relative_layout_timeline).getGlobalVisibleRect(finalBounds, globalOffset); + startBounds.offset(-globalOffset.x, -globalOffset.y); + finalBounds.offset(-globalOffset.x, -globalOffset.y); + + // Adjust the start bounds to be the same aspect ratio as the final bounds using the + // "center crop" technique. This prevents undesirable stretching during the animation. + // Also calculate the start scaling factor (the end scaling factor is always 1.0). + float startScale; + if ((float) finalBounds.width() / finalBounds.height() + > (float) startBounds.width() / startBounds.height()) { + // Extend start bounds horizontally + startScale = (float) startBounds.height() / finalBounds.height(); + float startWidth = startScale * finalBounds.width(); + float deltaWidth = (startWidth - startBounds.width()) / 2; + startBounds.left -= deltaWidth; + startBounds.right += deltaWidth; + } else { + // Extend start bounds vertically + startScale = (float) startBounds.width() / finalBounds.width(); + float startHeight = startScale * finalBounds.height(); + float deltaHeight = (startHeight - startBounds.height()) / 2; + startBounds.top -= deltaHeight; + startBounds.bottom += deltaHeight; + } + + // Hide the thumbnail and show the zoomed-in view. When the animation begins, + // it will position the zoomed-in view in the place of the thumbnail. + thumbView.setAlpha(0f); + hiddenView.setVisibility(View.VISIBLE); + + // Set the pivot point for SCALE_X and SCALE_Y transformations to the top-left corner of + // the zoomed-in view (the default is the center of the view). + hiddenView.setPivotX(0f); + hiddenView.setPivotY(0f); + + // Construct and run the parallel animation of the four translation and scale properties + // (X, Y, SCALE_X, and SCALE_Y). + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left, + finalBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top, + finalBounds.top)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_X, startScale, 1f)) + .with(ObjectAnimator.ofFloat(hiddenView, View.SCALE_Y, startScale, 1f)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + + // Upon clicking the zoomed-in image, it should zoom back down to the original bounds + // and show the thumbnail instead of the expanded image. + final float startScaleFinal = startScale; + hiddenView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mCurrentAnimator != null) { + mCurrentAnimator.cancel(); + } + + // Animate the four positioning/sizing properties in parallel, back to their + // original values. + AnimatorSet set = new AnimatorSet(); + set + .play(ObjectAnimator.ofFloat(hiddenView, View.X, startBounds.left)) + .with(ObjectAnimator.ofFloat(hiddenView, View.Y, startBounds.top)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_X, startScaleFinal)) + .with(ObjectAnimator + .ofFloat(hiddenView, View.SCALE_Y, startScaleFinal)); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + thumbView.setAlpha(1f); + hiddenView.setVisibility(View.GONE); + mCurrentAnimator = null; + } + }); + set.start(); + mCurrentAnimator = set; + } + }); + } } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimelineFragment.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimelineFragment.java index 3139be0..8259b44 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimelineFragment.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/TimelineFragment.java @@ -1,38 +1,101 @@ package nl.myhyvesbookplus.tagram; import android.app.Fragment; +import android.app.ProgressDialog; import android.os.Bundle; +import android.os.Handler; +import android.support.v4.widget.SwipeRefreshLayout; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AbsListView; import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.Toast; import nl.myhyvesbookplus.tagram.controller.DownloadClass; public class TimelineFragment extends Fragment { + /* Some protected and private inits */ private ListView listView; private DownloadClass downloadClass; + private ProgressBar progressBar; - public TimelineFragment() { - // Required empty public constructor + /* Required empty public constructor */ + public TimelineFragment() {} + + /** + * Overridden onCreate which also starts a progress dialog for the posts being downloaded. + * @param savedInstanceState The standard return of the onCreate method. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// progressDialog = ProgressDialog.show(getActivity(), getString(R.string.please_wait), getString(R.string.downloading_posts), false, false); } + /** + * Overridden onCreateView method which creates the ListView and contains a possible refresh + * functionality (swipe down page for result). + * + * https://www.survivingwithandroid.com/2014/05/android-swiperefreshlayout-tutorial-2.html + * Above reference was largely copied from. + * @param inflater The inflater used for the fragment. + * @param container The container which holds this fragment. + * @param savedInstanceState The state which was provided by onCreate. + * @return the timeLineInflater View which is required for the ListView to be updated. + */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_timeline, container, false); - listView = (ListView) view.findViewById(R.id.list); + View timeLineInflater = inflater.inflate(R.layout.fragment_timeline, container, false); + listView = (ListView) timeLineInflater.findViewById(R.id.list); + final SwipeRefreshLayout swipeView = (SwipeRefreshLayout) timeLineInflater.findViewById(R.id.swipe); + progressBar = (ProgressBar) timeLineInflater.findViewById(R.id.progressbar_timeline); + progressBar.setVisibility(View.VISIBLE); + + swipeView.setEnabled(false); downloadClass = new DownloadClass(getActivity()); downloadClass.getPostsFromServer(); - return view; + swipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + downloadClass.getPostsFromServer(); + Toast.makeText(getActivity(), R.string.refreshing, + Toast.LENGTH_LONG).show(); + swipeView.setRefreshing(true); + ( new Handler()).postDelayed(new Runnable() { + @Override + public void run() { + swipeView.setRefreshing(false); + } + }, 1000); + } + }); + + listView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView absListView, int i) { + } + + @Override + public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + swipeView.setEnabled(firstVisibleItem == 0); + } + }); + return timeLineInflater; } + /** + * Start display of the list; uses an adapter and listener in the main activity. + */ public void startList() { TimeLineAdapter adapter = new TimeLineAdapter(getActivity(), downloadClass.getmList()); listView.setAdapter(adapter); + progressBar.setVisibility(View.GONE); } } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/controller/DownloadClass.java b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/controller/DownloadClass.java index da610b7..50cbf40 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/controller/DownloadClass.java +++ b/app/MyHyvesBookPlusStagram/app/src/main/java/nl/myhyvesbookplus/tagram/controller/DownloadClass.java @@ -47,6 +47,7 @@ public class DownloadClass { mList.add(tempPost); } Collections.reverse(mList); + mListener.PostDownloaded(); } @@ -63,13 +64,14 @@ public class DownloadClass { public ArrayList getOwnPosts() { String currentUid = FirebaseAuth.getInstance().getCurrentUser().getUid(); - ArrayList posts = new ArrayList(); + ArrayList posts = new ArrayList<>(); for (UriPost post : mList) { if (post.getPoster().equals(currentUid)) { posts.add(post); } } + return posts; } diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/drawable/niet_slecht.png b/app/MyHyvesBookPlusStagram/app/src/main/res/drawable/niet_slecht.png new file mode 100644 index 0000000..73d7117 Binary files /dev/null and b/app/MyHyvesBookPlusStagram/app/src/main/res/drawable/niet_slecht.png differ diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_camera.xml b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_camera.xml index 9815205..9470c2a 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_camera.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_camera.xml @@ -36,14 +36,6 @@ android:background="@android:color/transparent" android:src="@drawable/ic_switch_camera_black_24dp"/> - - - - - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml index fe7a080..0fcfcef 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/fragment_timeline.xml @@ -1,23 +1,43 @@ - - - + + - - + + + + + - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline.xml b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline.xml index b77ece9..a3f0338 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline.xml @@ -8,13 +8,11 @@ android:layout_width="match_parent" android:layout_height="15dp" /> - + + - - + android:gravity="center_horizontal" + android:orientation="horizontal"> + - + android:layout_width="150dp" + android:layout_height="40dp" + android:scaleType="centerInside" + android:background="@android:color/transparent" + android:src="@drawable/niet_slecht"/> + diff --git a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline_profile.xml b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline_profile.xml index 965ec97..65623ef 100644 --- a/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline_profile.xml +++ b/app/MyHyvesBookPlusStagram/app/src/main/res/layout/list_item_timeline_profile.xml @@ -18,26 +18,29 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" - android:text="Hallo Ik ben een comment!" /> + android:text="@string/comment_placeholder" /> + -