/** * Copyright (C) 2016 eBusiness Information * * This file is part of OSM Contributor. * * OSM Contributor is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OSM Contributor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OSM Contributor. If not, see <http://www.gnu.org/licenses/>. */ package io.jawg.osmcontributor.ui.activities; import android.Manifest; import android.app.ProgressDialog; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; import android.support.v4.content.FileProvider; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.GridView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.facebook.drawee.view.SimpleDraweeView; import com.flickr4java.flickr.photos.Size; import com.github.scribejava.core.model.Verb; import com.yarolegovich.lovelydialog.LovelyStandardDialog; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import io.jawg.osmcontributor.OsmTemplateApplication; import io.jawg.osmcontributor.R; import io.jawg.osmcontributor.flickr.event.PhotosFoundEvent; import io.jawg.osmcontributor.flickr.oauth.FlickrOAuth; import io.jawg.osmcontributor.flickr.oauth.OAuthRequest; import io.jawg.osmcontributor.flickr.rest.FlickrPhotoClient; import io.jawg.osmcontributor.flickr.rest.FlickrUploadClient; import io.jawg.osmcontributor.flickr.rest.asynctask.GetFlickrPhotos; import io.jawg.osmcontributor.flickr.util.FlickrPhotoUtils; import io.jawg.osmcontributor.flickr.util.FlickrUploadUtils; import io.jawg.osmcontributor.flickr.util.OAuthParams; import io.jawg.osmcontributor.flickr.util.ResponseConverter; import io.jawg.osmcontributor.ui.adapters.ImageAdapter; import io.jawg.osmcontributor.utils.ConfigManager; import retrofit.Callback; import retrofit.RetrofitError; import retrofit.client.Response; import retrofit.mime.TypedFile; public class PhotoActivity extends AppCompatActivity { private static final String TAG = "PhotoActivity"; /*=========================================*/ /*------------CONSTANTS--------------------*/ /*=========================================*/ private static final int NB_IMAGE_REQUESTED = 35; private static final int NB_PAGE_REQUESTED = 1; private static final int ALLOW_CAMERA_PERMISSION = 100; private static final int REQUEST_CAMERA = 200; private FlickrPhotoClient flickrPhotoClient; private FlickrUploadClient flickrUploadClient; /*=========================================*/ /*------------INJECTIONS-------------------*/ /*=========================================*/ @Inject SharedPreferences sharedPreferences; @Inject ConfigManager configManager; /*=========================================*/ /*---------------VIEWS---------------------*/ /*=========================================*/ @BindView(R.id.grid_photos) GridView gridPhotos; @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.loading) ProgressBar loadingImage; @BindView(R.id.add_photo) FloatingActionButton addPhoto; @BindView(R.id.zoom_photo) SimpleDraweeView zoomPhoto; @BindView(R.id.no_photos) TextView noPhotos; /*=========================================*/ /*------------ATTRIBUTES-------------------*/ /*=========================================*/ private ImageAdapter imageAdapter; private int lastVisiblePos; private Long poiId; private GetFlickrPhotos asyncGetPhotos; private OsmTemplateApplication application; private FlickrOAuth flickrOAuth; private File photoFile; private ProgressDialog progressDialog; private double latitude; private double longitude; private int nbTry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_photo); ButterKnife.bind(this); EventBus.getDefault().register(this); application = (OsmTemplateApplication) getApplication(); flickrOAuth = new FlickrOAuth(); application.getOsmTemplateComponent().inject(this); application.getOsmTemplateComponent().inject(flickrOAuth); progressDialog = new ProgressDialog(this); progressDialog.setMessage(getString(R.string.upload_message)); progressDialog.setCancelable(false); // Set action bar infos. toolbar.setTitle("Photos"); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } // Loading image. addPhoto.setVisibility(View.VISIBLE); // Init parameters. latitude = getIntent().getDoubleExtra("latitude", 0); longitude = getIntent().getDoubleExtra("longitude", 0); poiId = getIntent().getLongExtra("poiId", 0); imageAdapter = new ImageAdapter(this, poiId); gridPhotos.setAdapter(imageAdapter); // Init listener and view initScrollListener(); initOnClickItemListener(); initView(); } /*=========================================*/ /*----------------ONCLICK------------------*/ /*=========================================*/ @OnClick(R.id.add_photo) public void onClickAddPhoto(View v) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissionIfNeeded(); } else { takePicture(); } } @OnClick(R.id.zoom_photo) public void onClickZoomPhoto(View v) { zoomPhoto.setVisibility(View.INVISIBLE); addPhoto.setVisibility(View.VISIBLE); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (zoomPhoto.getVisibility() == View.VISIBLE) { zoomPhoto.setVisibility(View.INVISIBLE); } else { if (asyncGetPhotos != null) { asyncGetPhotos.cancel(true); } onBackPressed(); } return true; } /*=========================================*/ /*-------------PRIVATE CODE----------------*/ /*=========================================*/ private void initScrollListener() { // Hide button on scroll down and show it on scroll up. lastVisiblePos = gridPhotos.getFirstVisiblePosition(); gridPhotos.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int currentFirstVisPos = view.getFirstVisiblePosition(); // Scroll down. if (currentFirstVisPos > lastVisiblePos) { addPhoto.setVisibility(View.INVISIBLE); } // Scroll up. if (currentFirstVisPos < lastVisiblePos) { addPhoto.setVisibility(View.VISIBLE); } lastVisiblePos = currentFirstVisPos; } }); } private void initOnClickItemListener() { gridPhotos.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { zoomPhoto.setImageURI(Uri.parse(ImageAdapter.getPhotosOriginals(poiId).get(position))); zoomPhoto.setVisibility(View.VISIBLE); addPhoto.setVisibility(View.INVISIBLE); } }); } private void initView() { if (ImageAdapter.getPhotoUrlsCachedThumbs(poiId) == null || ImageAdapter.getPhotoUrlsCachedThumbs(poiId).isEmpty()) { gridPhotos.setVisibility(View.INVISIBLE); loadingImage.setVisibility(View.VISIBLE); } asyncGetPhotos = new GetFlickrPhotos(longitude, latitude, application.getFlickr(), NB_IMAGE_REQUESTED, NB_PAGE_REQUESTED); asyncGetPhotos.execute(); } /*=========================================*/ /*-------------PHOTOS CODE-----------------*/ /*=========================================*/ private void takePicture() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { try { photoFile = createImageFile(); } catch (IOException ex) { ex.printStackTrace(); } // Continue only if the File was successfully created if (photoFile != null) { Uri photoURI = FileProvider.getUriForFile(this, this.getPackageName() + ".fileprovider", photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); startActivityForResult(takePictureIntent, REQUEST_CAMERA); } } } private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.FRANCE).format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); return File.createTempFile(imageFileName, ".jpg", storageDir); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CAMERA && resultCode == RESULT_OK) { uploadPhoto(); } } /*=========================================*/ /*---------------EVENTS--------------------*/ /*=========================================*/ /** * Event called when GetFlickrPhotos AsyncTask is done. * @param photosFoundEvent event with photos found */ @Subscribe(threadMode = ThreadMode.MAIN) public void onPhotosFoundEvent(PhotosFoundEvent photosFoundEvent) { List<List<Size>> photos = photosFoundEvent.getPhotos(); if (photos != null && !photos.isEmpty()) { noPhotos.setVisibility(View.INVISIBLE); for (List<Size> size : photos) { imageAdapter.addPhoto(size.get(Size.SQUARE).getSource(), poiId, Size.SQUARE); imageAdapter.addPhoto(size.get(Size.ORIGINAL).getSource(), poiId, Size.ORIGINAL); } } else { noPhotos.setVisibility(View.VISIBLE); } loadingImage.setVisibility(View.INVISIBLE); gridPhotos.setVisibility(View.VISIBLE); } private void uploadPhoto() { OAuthRequest oAuthRequest = flickrOAuth.getOAuthRequest(); if (oAuthRequest == null) { oAuthRequest = new OAuthRequest(application.getFlickr().getApiKey(), application.getFlickr().getSharedSecret()); oAuthRequest.setOAuthToken(configManager.getFlickrToken()); oAuthRequest.setOAuthTokenSecret(configManager.getFlickrTokenSecret()); flickrOAuth.setOAuthRequest(oAuthRequest); } oAuthRequest.setRequestUrl("https://up.flickr.com/services/upload"); oAuthRequest.initParam(OAuthParams.getOAuthParams().put(OAuthParams.OAUTH_TOKEN, oAuthRequest.getOAuthToken()).toMap()); oAuthRequest.signRequest(Verb.POST); progressDialog.show(); if (flickrUploadClient == null) { flickrUploadClient = FlickrUploadUtils.getRestAdapter(oAuthRequest.getParams()).create(FlickrUploadClient.class); } TypedFile typedFile = new TypedFile("multipart/form-data", photoFile); flickrUploadClient.upload(typedFile, new Callback<String>() { @Override public void success(String s, Response response) { onUploadFinishedEVent(ResponseConverter.convertImageId(s)); } @Override public void failure(RetrofitError error) { error.printStackTrace(); if (nbTry < 10) { nbTry++; uploadPhoto(); } else { nbTry = 0; Toast.makeText(PhotoActivity.this, "La communication avec Flickr a échoué, réessayer", Toast.LENGTH_LONG).show(); progressDialog.dismiss(); } } }); } public void onUploadFinishedEVent(final String photoId) { if (flickrPhotoClient == null) { flickrPhotoClient = FlickrPhotoUtils.getAdapter().create(FlickrPhotoClient.class); } OAuthRequest oauthRequest = new OAuthRequest(configManager.getFlickrApiKey(), configManager.getFlickrApiKeySecret()); oauthRequest.setRequestUrl("https://api.flickr.com/services/rest"); oauthRequest.setOAuthToken(configManager.getFlickrToken()); oauthRequest.setOAuthTokenSecret(configManager.getFlickrTokenSecret()); oauthRequest.initParam(OAuthParams.getOAuthParams() .put(OAuthParams.OAUTH_TOKEN, configManager.getFlickrToken()) .put("method", "flickr.photos.geo.setLocation") .put("photo_id", photoId) .put("lat", String.valueOf(latitude)) .put("lon", String.valueOf(longitude)).toMap()); oauthRequest.signRequest(Verb.GET); flickrPhotoClient.setProperties(oauthRequest.getParams(), new Callback<String>() { @Override public void success(String s, Response response) { setPhotoTag(photoId); } @Override public void failure(RetrofitError error) { progressDialog.dismiss(); } }); } private void setPhotoTag(final String photoId) { OAuthRequest oauthRequest = new OAuthRequest(configManager.getFlickrApiKey(), configManager.getFlickrApiKeySecret()); oauthRequest.setRequestUrl("https://api.flickr.com/services/rest"); oauthRequest.setOAuthToken(configManager.getFlickrToken()); oauthRequest.setOAuthTokenSecret(configManager.getFlickrTokenSecret()); oauthRequest.initParam(OAuthParams.getOAuthParams() .put(OAuthParams.OAUTH_TOKEN, configManager.getFlickrToken()) .put("method", "flickr.photos.addTags") .put("photo_id", photoId) .put("tags", "openstreetmap").toMap()); oauthRequest.signRequest(Verb.GET); flickrPhotoClient.setProperties(oauthRequest.getParams(), new Callback<String>() { @Override public void success(String s, Response response) { progressDialog.dismiss(); photoFile.delete(); Toast.makeText(PhotoActivity.this, "Votre photo a été envoyée. Elle s'affichera dans quelques minutes", Toast.LENGTH_LONG).show(); if (ImageAdapter.getPhotoUrlsCachedThumbs(poiId) == null || ImageAdapter.getPhotoUrlsCachedThumbs(poiId).isEmpty()) { finish(); } } @Override public void failure(RetrofitError error) { progressDialog.dismiss(); } }); } /*=========================================*/ /*-----------FOR ANDROID 6.0---------------*/ /*=========================================*/ /** * This method must be called if the android version is 6.0 or higher. * Check if the app has ACCESS_LOCATION (COARSE and FINE) and WRITE_EXTERNAL_STORAGE. * If the app doesn't have both permission, a request is prompted to the user. * For more informations about permission in Android 6.0, please refer to * https://developer.android.com/training/permissions/requesting.html?hl=Fr#explain */ private void requestPermissionIfNeeded() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Check if permission are enable by the user. Dangerous permissions requested are : int hasEnabledCameraPerm = checkSelfPermission(Manifest.permission.CAMERA); // If the user have already allow permission, launch map activity, else, ask the user to allow permission if (hasEnabledCameraPerm == PackageManager.PERMISSION_DENIED) { requestPermissions(new String[] {Manifest.permission.CAMERA}, ALLOW_CAMERA_PERMISSION); } else { takePicture(); } } } /** * This method is a callback. Check the user's answer after requesting permission. * * @param requestCode app request code (here, we only handle ALLOW_PERMISSION * @param permissions permissions requested (here, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, WRITE_EXTERNAL_STORAGE) * @param grantResults user's decision */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // If request is cancelled, the result arrays are empty. if (requestCode == ALLOW_CAMERA_PERMISSION && grantResults.length > 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { takePicture(); } else { permissionNotEnabled(); } } else { permissionNotEnabled(); } } /** * This method is called is a permission (or all permissions) are declined by the user. * If permissions are refused, we indicate why we request permissions and that, whitout it, * the app can not work. */ private void permissionNotEnabled() { new LovelyStandardDialog(this) .setTopColorRes(R.color.colorPrimaryDark) .setIcon(R.mipmap.icon) .setTitle(R.string.permissions_title) .setMessage(R.string.permissions_information) .setPositiveButton(android.R.string.ok, new View.OnClickListener() { @Override public void onClick(View v) { requestPermissionIfNeeded(); } }).show(); } }