diff --git a/app/build.gradle b/app/build.gradle index 40becbad4..5a4a46918 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,6 +25,8 @@ dependencies { compile 'com.android.support:support-v4:25.3.1' compile 'com.loopj.android:android-async-http:1.4.9' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' - compile 'com.evernote:android-job:1.1.10' + compile 'com.evernote:android-job:1.1.11' + compile 'com.github.bumptech.glide:glide:4.0.0-RC1' + annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a2217adfa..960322524 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -75,6 +75,12 @@ android:launchMode="singleTask" android:noHistory="true" /> + . */ +package fr.gouv.etalab.mastodon.activities; + + +import android.Manifest; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.URLUtil; +import android.widget.ImageView; +import android.widget.MediaController; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.VideoView; + +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.FileAsyncHttpResponseHandler; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; + +import java.io.File; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.util.ArrayList; + +import cz.msebera.android.httpclient.Header; +import fr.gouv.etalab.mastodon.client.Entities.Attachment; +import fr.gouv.etalab.mastodon.client.MastalabSSLSocketFactory; +import fr.gouv.etalab.mastodon.helper.Helper; +import mastodon.etalab.gouv.fr.mastodon.R; + +import static fr.gouv.etalab.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE; + + +/** + * Created by Thomas on 25/06/2017. + * Media Activity + */ + +public class MediaActivity extends AppCompatActivity { + + + private RelativeLayout loader; + private ArrayList attachments; + private ImageView imageView; + private VideoView videoView; + private float downX, downY; + private int mediaPosition; + MediaActivity.actionSwipe currentAction; + private ImageLoader imageLoader; + private DisplayImageOptions options; + static final int MIN_DISTANCE = 200; + private String finalUrlDownload; + private ImageView prev, next; + private boolean isHiding; + private Bitmap downloadedImage; + private File fileVideo; + private TextView progress; + private enum actionSwipe{ + RIGHT_TO_LEFT, + LEFT_TO_RIGHT, + POP + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_media); + attachments = getIntent().getParcelableArrayListExtra("mediaArray"); + mediaPosition = getIntent().getExtras().getInt("position", 1); + if( attachments == null || attachments.size() == 0) + finish(); + if( getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + loader = (RelativeLayout) findViewById(R.id.loader); + imageView = (ImageView) findViewById(R.id.media_picture); + videoView = (VideoView) findViewById(R.id.media_video); + prev = (ImageView) findViewById(R.id.media_prev); + next = (ImageView) findViewById(R.id.media_next); + prev.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mediaPosition--; + displayMediaAtPosition(actionSwipe.POP); + } + }); + next.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mediaPosition++; + displayMediaAtPosition(actionSwipe.POP); + } + }); + + progress = (TextView) findViewById(R.id.loader_progress); + setTitle(""); + final ViewGroup videoLayout = (ViewGroup) findViewById(R.id.videoLayout); // Your own view, read class comments + + isHiding = false; + imageLoader = ImageLoader.getInstance(); + options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + setTitle(""); + displayMediaAtPosition(actionSwipe.POP); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + case R.id.action_download: + if(Build.VERSION.SDK_INT >= 23 ){ + if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) { + ActivityCompat.requestPermissions(MediaActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE); + } else { + Helper.manageMoveFileDownload(MediaActivity.this, finalUrlDownload, downloadedImage, fileVideo); + } + }else{ + Helper.manageMoveFileDownload(MediaActivity.this, finalUrlDownload, downloadedImage, fileVideo); + } + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main_media, menu); + return true; + } + + + + /** + * Manage touch event + * Allows to swipe from timelines + * @param event MotionEvent + * @return boolean + */ + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if( mediaPosition > attachments.size() || mediaPosition < 1 || attachments.size() <= 1) + return super.dispatchTouchEvent(event); + switch(event.getAction()){ + case MotionEvent.ACTION_DOWN: { + downX = event.getX(); + downY = event.getY(); + //Displays navigation left/right buttons + if( attachments != null && attachments.size() > 1 && !isHiding){ + prev.setVisibility(View.VISIBLE); + next.setVisibility(View.VISIBLE); + isHiding = true; + new Handler().postDelayed(new Runnable(){ + public void run() { + prev.setVisibility(View.GONE); + next.setVisibility(View.GONE); + isHiding = false; + } + }, 1000); + } + return super.dispatchTouchEvent(event); + } + case MotionEvent.ACTION_UP: { + float upX = event.getX(); + float upY = event.getY(); + float deltaX = downX - upX; + float deltaY = downY - upY; + // swipe horizontal + if( downX > MIN_DISTANCE & (Math.abs(deltaX) > MIN_DISTANCE && Math.abs(deltaY) < MIN_DISTANCE)){ + if(deltaX < 0) { switchOnSwipe(MediaActivity.actionSwipe.LEFT_TO_RIGHT); return true; } + if(deltaX > 0) { switchOnSwipe(MediaActivity.actionSwipe.RIGHT_TO_LEFT); return true; } + }else{ + currentAction = MediaActivity.actionSwipe.POP; + } + } + } + return super.dispatchTouchEvent(event); + } + + + private void switchOnSwipe(actionSwipe action){ + loader.setVisibility(View.VISIBLE); + mediaPosition = (action == actionSwipe.LEFT_TO_RIGHT)?mediaPosition-1:mediaPosition+1; + displayMediaAtPosition(action); + } + + private void displayMediaAtPosition(actionSwipe action){ + if( mediaPosition > attachments.size() ) + mediaPosition = 1; + if( mediaPosition < 1) + mediaPosition = attachments.size(); + currentAction = action; + Attachment attachment = attachments.get(mediaPosition-1); + String type = attachment.getType(); + final String url = attachment.getUrl(); + finalUrlDownload = url; + videoView.setVisibility(View.GONE); + if( videoView.isPlaying()) { + videoView.stopPlayback(); + } + imageView.setVisibility(View.GONE); + + switch (type){ + case "image": + imageLoader.displayImage(url, imageView, options, new SimpleImageLoadingListener(){ + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + super.onLoadingComplete(imageUri, view, loadedImage); + loader.setVisibility(View.GONE); + imageView.setVisibility(View.VISIBLE); + downloadedImage = loadedImage; + fileVideo = null; + } + @Override + public void onLoadingFailed(java.lang.String imageUri, android.view.View view, FailReason failReason){ + imageLoader.displayImage(url, imageView, options); + loader.setVisibility(View.GONE); + } + }); + break; + case "video": + case "gifv": + AsyncHttpClient client = new AsyncHttpClient(); + MastalabSSLSocketFactory mastalabSSLSocketFactory; + try { + mastalabSSLSocketFactory = new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()); + mastalabSSLSocketFactory.setHostnameVerifier(MastalabSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + client.setSSLSocketFactory(mastalabSSLSocketFactory); + File file = new File(getCacheDir() + "/" + Helper.md5(url)+".mp4"); + if(file.exists()) { + Uri uri = Uri.parse(file.getAbsolutePath()); + videoView.setVisibility(View.VISIBLE); + videoView.setVideoURI(uri); + videoView.start(); + MediaController mc = new MediaController(MediaActivity.this); + videoView.setMediaController(mc); + videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + loader.setVisibility(View.GONE); + } + }); + videoView.setVisibility(View.VISIBLE); + fileVideo = file; + downloadedImage = null; + }else{ + progress.setText("0 %"); + progress.setVisibility(View.VISIBLE); + client.get(url, new FileAsyncHttpResponseHandler(/* Context */ this) { + @Override + public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) { + progress.setVisibility(View.GONE); + } + + @Override + public void onProgress(long bytesWritten, long totalSize) { + long progressPercentage = (long)100*bytesWritten/totalSize; + progress.setText(String.valueOf(progressPercentage) + " %"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, File response) { + File dir = getCacheDir(); + File from = new File(dir, response.getName()); + File to = new File(dir, Helper.md5(url) + ".mp4"); + if (from.exists()) + from.renameTo(to); + fileVideo = to; + downloadedImage = null; + progress.setVisibility(View.GONE); + Uri uri = Uri.parse(to.getAbsolutePath()); + videoView.setVisibility(View.VISIBLE); + videoView.setVideoURI(uri); + videoView.start(); + MediaController mc = new MediaController(MediaActivity.this); + videoView.setMediaController(mc); + videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + loader.setVisibility(View.GONE); + } + }); + videoView.setVisibility(View.VISIBLE); + } + }); + } + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) { + e.printStackTrace(); + } + break; + } + String filename = URLUtil.guessFileName(url, null, null); + if( filename == null) + filename = url; + LayoutInflater mInflater = LayoutInflater.from(MediaActivity.this); + ActionBar actionBar = getSupportActionBar(); + if( actionBar != null){ + View picture_actionbar = mInflater.inflate(R.layout.picture_actionbar, null); + TextView picture_actionbar_title = (TextView) picture_actionbar.findViewById(R.id.picture_actionbar); + picture_actionbar_title.setText(filename); + actionBar.setCustomView(picture_actionbar); + actionBar.setDisplayShowCustomEnabled(true); + }else { + setTitle(url); + } + } + + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java index c6a23b667..4f0dec6b2 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java @@ -97,7 +97,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc private ImageLoader imageLoader; private DisplayImageOptions options; private LinearLayout toot_picture_container; - private List attachments; + private ArrayList attachments; private boolean isSensitive = false; private ImageButton toot_visibility; private Button toot_it; @@ -107,7 +107,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc private ListView toot_lv_accounts; private BroadcastReceiver search_validate; private Status tootReply = null; - private String sharedContent, sharedSubject, sharedTitle; + private String sharedContent, sharedSubject; private CheckBox toot_sensitive; private String pattern = "^.*(@([a-zA-Z0-9_]{2,}))$"; @@ -139,7 +139,6 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc tootReply = b.getParcelable("tootReply"); sharedContent = b.getString("sharedContent", null); sharedSubject = b.getString("sharedSubject", null); - sharedTitle = b.getString("sharedTitle", null); } if( tootReply != null) { setTitle(R.string.toot_title_reply); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java index 08427efc7..2bc6edba2 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java @@ -15,32 +15,19 @@ package fr.gouv.etalab.mastodon.activities; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Bitmap; - -import android.media.MediaPlayer; -import android.os.Build; import android.os.Bundle; -import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; -import android.view.LayoutInflater; +import android.util.Log; import android.view.MenuItem; -import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.webkit.CookieManager; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; import fr.gouv.etalab.mastodon.helper.Helper; +import fr.gouv.etalab.mastodon.webview.MastalabWebChromeClient; +import fr.gouv.etalab.mastodon.webview.MastalabWebViewClient; import mastodon.etalab.gouv.fr.mastodon.R; @@ -52,8 +39,7 @@ import mastodon.etalab.gouv.fr.mastodon.R; public class WebviewActivity extends AppCompatActivity { private String url; - private ProgressBar pbar; - private static boolean isVideoFullscreen; + @Override @@ -67,48 +53,15 @@ public class WebviewActivity extends AppCompatActivity { finish(); if( getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean javascript = sharedpreferences.getBoolean(Helper.SET_JAVASCRIPT, true); - - - - pbar = (ProgressBar) findViewById(R.id.progress_bar); - WebView webView = (WebView) findViewById(R.id.webview); - webView.getSettings().setJavaScriptEnabled(javascript); - webView.getSettings().setUseWideViewPort(true); - webView.getSettings().setLoadWithOverviewMode(true); - webView.getSettings().setSupportZoom(true); - webView.getSettings().setDisplayZoomControls(false); - webView.getSettings().setBuiltInZoomControls(true); - webView.getSettings().setAllowContentAccess(true); - webView.getSettings().setLoadsImagesAutomatically(true); - webView.getSettings().setSupportMultipleWindows(false); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { - //noinspection deprecation - webView.getSettings().setPluginState(WebSettings.PluginState.ON); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - webView.getSettings().setMediaPlaybackRequiresUserGesture(true); - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - boolean cookies = sharedpreferences.getBoolean(Helper.SET_COOKIES, false); - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptThirdPartyCookies(webView, cookies); - } - webView.getSettings().setAppCacheEnabled(true); - webView.getSettings().setDatabaseEnabled(true); - webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); + WebView webView = Helper.initializeWebview(WebviewActivity.this, R.id.webview); setTitle(""); FrameLayout webview_container = (FrameLayout) findViewById(R.id.webview_container); final ViewGroup videoLayout = (ViewGroup) findViewById(R.id.videoLayout); // Your own view, read class comments - MastalabWebChromeClient mastalabWebChromeClient = new MastalabWebChromeClient(webView, webview_container, videoLayout); - mastalabWebChromeClient.setOnToggledFullscreen(new ToggledFullscreenCallback() { + MastalabWebChromeClient mastalabWebChromeClient = new MastalabWebChromeClient(WebviewActivity.this, webView, webview_container, videoLayout); + mastalabWebChromeClient.setOnToggledFullscreen(new MastalabWebChromeClient.ToggledFullscreenCallback() { @Override public void toggledFullscreen(boolean fullscreen) { @@ -130,7 +83,7 @@ public class WebviewActivity extends AppCompatActivity { } }); webView.setWebChromeClient(mastalabWebChromeClient); - webView.setWebViewClient(new MastalabWebViewClient()); + webView.setWebViewClient(new MastalabWebViewClient(WebviewActivity.this)); webView.loadUrl(url); } @@ -147,208 +100,7 @@ public class WebviewActivity extends AppCompatActivity { @Override public void onDestroy(){ - isVideoFullscreen = false; super.onDestroy(); } - - private class MastalabWebViewClient extends WebViewClient{ - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - } - @Override - public void onPageStarted (WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - ActionBar actionBar = getSupportActionBar(); - LayoutInflater mInflater = LayoutInflater.from(WebviewActivity.this); - if( actionBar != null){ - View webview_actionbar = mInflater.inflate(R.layout.webview_actionbar, null); - TextView webview_title = (TextView) webview_actionbar.findViewById(R.id.webview_title); - webview_title.setText(url); - actionBar.setCustomView(webview_actionbar); - actionBar.setDisplayShowCustomEnabled(true); - }else { - setTitle(url); - } - } - } - - interface ToggledFullscreenCallback { - void toggledFullscreen(boolean fullscreen); - } - - - private class MastalabWebChromeClient extends WebChromeClient implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { - - private FrameLayout videoViewContainer; - private CustomViewCallback videoViewCallback; - - private ToggledFullscreenCallback toggledFullscreenCallback; - - private WebView webView; - private View activityNonVideoView; - private ViewGroup activityVideoView; - - MastalabWebChromeClient(WebView webView, FrameLayout webviewContainer, ViewGroup videoLayout){ - isVideoFullscreen = false; - this.webView = webView; - this.activityNonVideoView = webviewContainer; - this.activityVideoView = videoLayout; - } - - @Override - public void onProgressChanged(WebView view, int progress) { - if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) { - pbar.setVisibility(ProgressBar.VISIBLE); - } - pbar.setProgress(progress); - if (progress == 100) { - pbar.setVisibility(ProgressBar.GONE); - } - } - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - super.onReceivedIcon(view, icon); - LayoutInflater mInflater = LayoutInflater.from(WebviewActivity.this); - ActionBar actionBar = getSupportActionBar(); - if( actionBar != null){ - View webview_actionbar = mInflater.inflate(R.layout.webview_actionbar, null); - TextView webview_title = (TextView) webview_actionbar.findViewById(R.id.webview_title); - webview_title.setText(view.getTitle()); - ImageView webview_favicon = (ImageView) webview_actionbar.findViewById(R.id.webview_favicon); - if( icon != null) - webview_favicon.setImageBitmap(icon); - actionBar.setCustomView(webview_actionbar); - actionBar.setDisplayShowCustomEnabled(true); - }else { - setTitle(view.getTitle()); - } - - } - - //FULLSCREEN VIDEO - //Code from https://stackoverflow.com/a/16179544/3197259 - - /** - * Set a callback that will be fired when the video starts or finishes displaying using a custom view (typically full-screen) - * @param callback A VideoEnabledWebChromeClient.ToggledFullscreenCallback callback - */ - void setOnToggledFullscreen(ToggledFullscreenCallback callback) { - this.toggledFullscreenCallback = callback; - } - - @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - if (view instanceof FrameLayout) { - if( getSupportActionBar() != null) - getSupportActionBar().hide(); - // A video wants to be shown - FrameLayout frameLayout = (FrameLayout) view; - View focusedChild = frameLayout.getFocusedChild(); - - // Save video related variables - isVideoFullscreen = true; - this.videoViewContainer = frameLayout; - this.videoViewCallback = callback; - - // Hide the non-video view, add the video view, and show it - activityNonVideoView.setVisibility(View.INVISIBLE); - activityVideoView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - activityVideoView.setVisibility(View.VISIBLE); - if (focusedChild instanceof android.widget.VideoView) { - // android.widget.VideoView (typically API level <11) - android.widget.VideoView videoView = (android.widget.VideoView) focusedChild; - // Handle all the required events - videoView.setOnCompletionListener(this); - videoView.setOnErrorListener(this); - } else { - // Other classes, including: - // - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 11-18) - // - android.webkit.HTML5VideoFullScreen$VideoTextureView, which inherits from android.view.TextureView (typically API level 11-18) - // - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 19+) - - // Handle HTML5 video ended event only if the class is a SurfaceView - // Test case: TextureView of Sony Xperia T API level 16 doesn't work fullscreen when loading the javascript below - if (webView != null && webView.getSettings().getJavaScriptEnabled() && focusedChild instanceof SurfaceView) { - // Run javascript code that detects the video end and notifies the Javascript interface - String js = "javascript:"; - js += "var _ytrp_html5_video_last;"; - js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];"; - js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {"; - { - js += "_ytrp_html5_video_last = _ytrp_html5_video;"; - js += "function _ytrp_html5_video_ended() {"; - { - js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must match Javascript interface name and method of VideoEnableWebView - } - js += "}"; - js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);"; - } - js += "}"; - webView.loadUrl(js); - } - } - // Notify full-screen change - if (toggledFullscreenCallback != null) { - toggledFullscreenCallback.toggledFullscreen(true); - } - } - } - - // Available in API level 14+, deprecated in API level 18+ - @Override @SuppressWarnings("deprecation") - public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { - onShowCustomView(view, callback); - } - - @Override - public void onHideCustomView() { - if( getSupportActionBar() != null) - getSupportActionBar().show(); - // This method should be manually called on video end in all cases because it's not always called automatically. - // This method must be manually called on back key press (from this class' onBackPressed() method). - if (isVideoFullscreen) { - // Hide the video view, remove it, and show the non-video view - activityVideoView.setVisibility(View.INVISIBLE); - activityVideoView.removeView(videoViewContainer); - activityNonVideoView.setVisibility(View.VISIBLE); - // Call back (only in API level <19, because in API level 19+ with chromium webview it crashes) - if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium.")) { - videoViewCallback.onCustomViewHidden(); - } - - // Reset video related variables - isVideoFullscreen = false; - videoViewContainer = null; - videoViewCallback = null; - - // Notify full-screen change - if (toggledFullscreenCallback != null) { - toggledFullscreenCallback.toggledFullscreen(false); - } - } - } - - // Video will start loading - @Override - public View getVideoLoadingProgressView() { - return super.getVideoLoadingProgressView(); - } - - // Video finished playing, only called in the case of android.widget.VideoView (typically API level <11) - @Override - public void onCompletion(MediaPlayer mp) { - onHideCustomView(); - } - - // Error while playing video, only called in the case of android.widget.VideoView (typically API level <11) - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - return false; // By returning false, onCompletion() will be called - } - - } - - } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java index 0e1e6122c..7bd78f251 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java @@ -1160,7 +1160,7 @@ public class API { //Retrieves attachments JSONArray arrayAttachement = resobj.getJSONArray("media_attachments"); - List attachments = new ArrayList<>(); + ArrayList attachments = new ArrayList<>(); if( arrayAttachement != null){ for(int j = 0 ; j < arrayAttachement.length() ; j++){ JSONObject attObj = arrayAttachement.getJSONObject(j); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java index ee9eb771b..dd662371c 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java @@ -14,11 +14,15 @@ * see . */ package fr.gouv.etalab.mastodon.client.Entities; +import android.os.Parcel; +import android.os.Parcelable; + /** * Created by Thomas on 23/04/2017. + * Manages Media */ -public class Attachment { +public class Attachment implements Parcelable { private String id; private String type; @@ -27,6 +31,31 @@ public class Attachment { private String preview_url; private String text_url; + public Attachment(Parcel in) { + id = in.readString(); + type = in.readString(); + url = in.readString(); + remote_url = in.readString(); + preview_url = in.readString(); + text_url = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Attachment createFromParcel(Parcel in) { + return new Attachment(in); + } + + @Override + public Attachment[] newArray(int size) { + return new Attachment[size]; + } + }; + + public Attachment() { + + } + public String getId() { return id; } @@ -74,4 +103,19 @@ public class Attachment { public void setText_url(String text_url) { this.text_url = text_url; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(type); + dest.writeString(url); + dest.writeString(remote_url); + dest.writeString(preview_url); + dest.writeString(text_url); + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java index 8f6c609b1..f30a9872e 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java @@ -18,6 +18,7 @@ package fr.gouv.etalab.mastodon.client.Entities; import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -46,7 +47,7 @@ public class Status implements Parcelable { private String visibility; private boolean attachmentShown = false; private boolean spoilerShown = false; - private List media_attachments; + private ArrayList media_attachments; private List mentions; private List tags; private Application application; @@ -208,11 +209,11 @@ public class Status implements Parcelable { } - public List getMedia_attachments() { + public ArrayList getMedia_attachments() { return media_attachments; } - public void setMedia_attachments(List media_attachments) { + public void setMedia_attachments(ArrayList media_attachments) { this.media_attachments = media_attachments; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java index 5bad28fba..e6f7aa02e 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java @@ -14,7 +14,6 @@ package fr.gouv.etalab.mastodon.drawers; * You should have received a copy of the GNU General Public License along with Thomas Schneider; if not, * see . */ -import android.Manifest; import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; @@ -22,21 +21,15 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; import android.graphics.drawable.Drawable; -import android.media.MediaPlayer; -import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebView; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.Button; @@ -46,18 +39,15 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; -import android.widget.VideoView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import java.util.ArrayList; import java.util.List; -import fr.gouv.etalab.mastodon.activities.MainActivity; +import fr.gouv.etalab.mastodon.activities.MediaActivity; import fr.gouv.etalab.mastodon.activities.ShowConversationActivity; import fr.gouv.etalab.mastodon.activities.TootActivity; import fr.gouv.etalab.mastodon.client.Entities.Error; @@ -71,7 +61,6 @@ import fr.gouv.etalab.mastodon.client.Entities.Attachment; import fr.gouv.etalab.mastodon.client.Entities.Status; import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface; -import static fr.gouv.etalab.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE; /** @@ -463,6 +452,7 @@ public class StatusListAdapter extends BaseAdapter implements OnPostActionInterf }else { holder.status_prev4_container.setVisibility(View.VISIBLE); } + int position = 1; for(final Attachment attachment: attachments){ ImageView imageView; if( i == 0) { @@ -496,13 +486,20 @@ public class StatusListAdapter extends BaseAdapter implements OnPostActionInterf if( url.trim().contains("missing.png")) continue; imageLoader.displayImage(url, imageView, options); + final int finalPosition = position; imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - showPicture(attachment); + Intent intent = new Intent(context, MediaActivity.class); + Bundle b = new Bundle(); + intent.putParcelableArrayListExtra("mediaArray", status.getMedia_attachments()); + b.putInt("position", finalPosition); + intent.putExtras(b); + context.startActivity(intent); } }); i++; + position++; } holder.status_document_container.setVisibility(View.VISIBLE); }else{ @@ -511,101 +508,6 @@ public class StatusListAdapter extends BaseAdapter implements OnPostActionInterf holder.status_show_more.setVisibility(View.GONE); } - private void showPicture(final Attachment attachment) { - - final AlertDialog.Builder alertadd = new AlertDialog.Builder(context); - LayoutInflater factory = LayoutInflater.from(context); - final View view = factory.inflate(R.layout.show_attachment, null); - alertadd.setView(view); - final RelativeLayout loader = (RelativeLayout) view.findViewById(R.id.loader); - switch (attachment.getType()){ - case "image": { - String url = attachment.getPreview_url(); - if(url == null || url.trim().equals("")) - url = attachment.getUrl(); - final ImageView imageView = (ImageView) view.findViewById(R.id.dialog_imageview); - imageLoader.displayImage(url, imageView, options, new SimpleImageLoadingListener(){ - @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { - super.onLoadingComplete(imageUri, view, loadedImage); - loader.setVisibility(View.GONE); - imageView.setVisibility(View.VISIBLE); - } - @Override - public void onLoadingFailed(java.lang.String imageUri, android.view.View view, FailReason failReason){ - imageLoader.displayImage(attachment.getPreview_url(), imageView, options); - loader.setVisibility(View.GONE); - } - }); - break; - } - case "gifv": - case "video": { - if( attachment.getRemote_url().contains(".gif") ){ - view.findViewById(R.id.dialog_webview_container).setVisibility(View.VISIBLE); - WebView webView = (WebView) view.findViewById(R.id.dialog_webview); - webView.getSettings().setJavaScriptEnabled(false); - webView.clearCache(false); - webView.setScrollbarFadingEnabled(true); - webView.getSettings().setBuiltInZoomControls(false); - webView.getSettings().setSupportZoom(false); - webView.getSettings().setUseWideViewPort(false); - webView.setVerticalScrollBarEnabled(false); - webView.setHorizontalScrollBarEnabled(false); - webView.setInitialScale(0); - String url = attachment.getRemote_url(); - if(url == null || url.trim().equals("")) - url = attachment.getUrl(); - webView.loadUrl(url); - loader.setVisibility(View.GONE); - }else { - String url = attachment.getRemote_url(); - if(url == null || url.trim().equals("")) - url = attachment.getUrl(); - Uri uri = Uri.parse(url); - VideoView videoView = (VideoView) view.findViewById(R.id.dialog_videoview); - videoView.setVisibility(View.VISIBLE); - videoView.setVideoURI(uri); - videoView.start(); - videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - loader.setVisibility(View.GONE); - } - }); - } - - break; - } - } - String urlDownload = attachment.getRemote_url(); - if( urlDownload == null || urlDownload.trim().equals("")) - urlDownload = attachment.getUrl(); - final String finalUrlDownload = urlDownload; - alertadd.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dlg, int sumthin) { - if(Build.VERSION.SDK_INT >= 23 ){ - if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) { - ActivityCompat.requestPermissions((MainActivity)context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE); - } else { - - Helper.manageDownloads(context, finalUrlDownload); - } - }else{ - Helper.manageDownloads(context, finalUrlDownload); - } - dlg.dismiss(); - } - }); - alertadd.setNeutralButton(R.string.close, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dlg, int sumthin) { - dlg.dismiss(); - } - }); - - alertadd.show(); - } - @Override public void onPostAction(int statusCode, API.StatusAction statusAction, String targetedId, Error error) { diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java index c1f8453b0..10d0d4ef6 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java @@ -51,6 +51,10 @@ import android.util.Log; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; +import android.webkit.CookieManager; +import android.webkit.MimeTypeMap; +import android.webkit.URLUtil; +import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.ImageView; import android.widget.TextView; @@ -65,9 +69,12 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import java.io.BufferedReader; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -75,6 +82,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Random; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -445,48 +453,94 @@ public class Helper { } + + + + /** * Manage downloads with URLs * @param context Context * @param url String download url */ - public static void manageDownloads(final Context context, final String url){ + public static void manageDownloads(final Context context, final String url, Bitmap bitmap){ - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final DownloadManager.Request request; - try { - request = new DownloadManager.Request(Uri.parse(url.trim())); - }catch (Exception e){ - Toast.makeText(context,R.string.toast_error,Toast.LENGTH_LONG).show(); - return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ + final AlertDialog.Builder builder = new AlertDialog.Builder(context); + final DownloadManager.Request request; + try { + request = new DownloadManager.Request(Uri.parse(url.trim())); + }catch (Exception e){ + Toast.makeText(context,R.string.toast_error,Toast.LENGTH_LONG).show(); + return; + } + final String fileName = URLUtil.guessFileName(url, null, null); + builder.setMessage(context.getResources().getString(R.string.download_file, fileName)); + builder.setCancelable(false) + .setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + request.allowScanningByMediaScanner(); + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + DownloadManager dm = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE); + dm.enqueue(request); + dialog.dismiss(); + } + + }) + .setNegativeButton(context.getString(R.string.cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + AlertDialog alert = builder.create(); + if( alert.getWindow() != null ) + alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + alert.show(); + }else { + final String fileName = URLUtil.guessFileName(url, null, null); + File myDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + File file = new File (myDir, fileName); + try { + FileOutputStream out = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + out.flush(); + out.close(); + Random r = new Random(); + int notificationIdTmp = r.nextInt(10000); + // prepare intent which is triggered if the + // notification is selected + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + Uri uri = Uri.parse("file://" + file.getAbsolutePath()); + intent.setDataAndType(uri, getMimeType(url)); + final PendingIntent pIntentDownload = PendingIntent.getActivity(context, notificationIdTmp, intent, PendingIntent.FLAG_ONE_SHOT); + + // build notification + // the addAction re-use the same intent to keep the example short + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_action_download) + .setTicker(context.getString(R.string.download_over)) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(true) + .setContentIntent(pIntentDownload) + .setContentText(context.getString(R.string.download_from, fileName)) + .setContentTitle(context.getString(R.string.download_over)); + notificationManager.notify(notificationIdTmp, notificationBuilder.build()); + } catch (Exception e) { + e.printStackTrace(); + } } - Uri uri = Uri.parse(url); - File f = new File("" + uri); - final String fileName = f.getName(); - builder.setMessage(context.getResources().getString(R.string.download_file, fileName)); - builder.setCancelable(false) - .setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - request.allowScanningByMediaScanner(); - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName); - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - DownloadManager dm = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE); - dm.enqueue(request); - dialog.dismiss(); - } - - }) - .setNegativeButton(context.getString(R.string.cancel), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - AlertDialog alert = builder.create(); - if( alert.getWindow() != null ) - alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - alert.show(); } + private static String getMimeType(String url) { + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (extension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + return type; + } /** * Sends notification with intent @@ -522,6 +576,53 @@ public class Helper { notificationManager.notify(notificationId, notificationBuilder.build()); } + + /** + * Manage downloads with URLs + * @param context Context + * @param url String download url + */ + public static void manageMoveFileDownload(final Context context, final String url, Bitmap bitmap, File fileVideo){ + + final String fileName = URLUtil.guessFileName(url, null, null); + File myDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + File file = new File (myDir, fileName); + try { + if( bitmap != null) { + FileOutputStream out = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + out.flush(); + out.close(); + }else{ + file = fileVideo; + } + Random r = new Random(); + int notificationIdTmp = r.nextInt(10000); + // prepare intent which is triggered if the + // notification is selected + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + Uri uri = Uri.parse("file://" + file.getAbsolutePath()); + intent.setDataAndType(uri, getMimeType(url)); + final PendingIntent pIntentDownload = PendingIntent.getActivity(context, notificationIdTmp, intent, PendingIntent.FLAG_ONE_SHOT); + + // build notification + // the addAction re-use the same intent to keep the example short + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_action_download) + .setTicker(context.getString(R.string.download_over)) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(true) + .setContentIntent(pIntentDownload) + .setContentText(context.getString(R.string.download_from, fileName)) + .setContentTitle(context.getString(R.string.download_over)); + notificationManager.notify(notificationIdTmp, notificationBuilder.build()); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** * Returns the instance of the authenticated user * @param context Context @@ -904,4 +1005,68 @@ public class Helper { return statusTV; } + + public static WebView initializeWebview(Activity activity, int webviewId){ + + WebView webView = (WebView) activity.findViewById(webviewId); + final SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean javascript = sharedpreferences.getBoolean(Helper.SET_JAVASCRIPT, true); + + webView.getSettings().setJavaScriptEnabled(javascript); + webView.getSettings().setUseWideViewPort(true); + webView.getSettings().setLoadWithOverviewMode(true); + webView.getSettings().setSupportZoom(true); + webView.getSettings().setDisplayZoomControls(false); + webView.getSettings().setBuiltInZoomControls(true); + webView.getSettings().setAllowContentAccess(true); + webView.getSettings().setLoadsImagesAutomatically(true); + webView.getSettings().setSupportMultipleWindows(false); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + //noinspection deprecation + webView.getSettings().setPluginState(WebSettings.PluginState.ON); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + webView.getSettings().setMediaPlaybackRequiresUserGesture(true); + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + boolean cookies = sharedpreferences.getBoolean(Helper.SET_COOKIES, false); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptThirdPartyCookies(webView, cookies); + } + webView.getSettings().setAppCacheEnabled(true); + webView.getSettings().setDatabaseEnabled(true); + webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); + + return webView; + } + + + public static String md5(final String s) { + final String MD5 = "MD5"; + try { + // Create MD5 Hash + MessageDigest digest = java.security.MessageDigest + .getInstance(MD5); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + // Create Hex String + StringBuilder hexString = new StringBuilder(); + for (byte aMessageDigest : messageDigest) { + String h = Integer.toHexString(0xFF & aMessageDigest); + while (h.length() < 2) + h = "0" + h; + hexString.append(h); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } + } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java index f198424e5..e5be2304c 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/HomeTimelineSyncJob.java @@ -22,13 +22,18 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.support.annotation.NonNull; +import android.view.View; import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; +import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import java.io.File; import java.util.List; @@ -124,13 +129,11 @@ public class HomeTimelineSyncJob extends Job implements OnRetrieveHomeTimelineSe List statuses = apiResponse.getStatuses(); if( apiResponse.getError() != null || statuses == null || statuses.size() == 0) return; - Bitmap icon_notification = null; final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String max_id = sharedpreferences.getString(Helper.LAST_HOMETIMELINE_MAX_ID + userId, null); + final String max_id = sharedpreferences.getString(Helper.LAST_HOMETIMELINE_MAX_ID + userId, null); //No previous notifications in cache, so no notification will be sent String message; - String title = null; for(Status status: statuses){ //The notification associated to max_id is discarded as it is supposed to have already been sent @@ -138,41 +141,55 @@ public class HomeTimelineSyncJob extends Job implements OnRetrieveHomeTimelineSe if( (max_id != null && status.getId().equals(max_id)) || status.getAccount().getAcct().trim().equals(acct.trim())) continue; String notificationUrl = status.getAccount().getAvatar(); - if( notificationUrl != null && icon_notification == null){ - try { - ImageLoader imageLoaderNoty = ImageLoader.getInstance(); - File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name)); - ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext()) - .imageDownloader(new PatchBaseImageDownloader(getContext())) - .threadPoolSize(5) - .threadPriority(Thread.MIN_PRIORITY + 3) - .denyCacheImageMultipleSizesInMemory() - .diskCache(new UnlimitedDiskCache(cacheDir)) - .build(); - imageLoaderNoty.init(config); - icon_notification = imageLoaderNoty.loadImageSync(notificationUrl); - title = getContext().getResources().getString(R.string.notif_pouet, status.getAccount().getUsername()); - }catch (Exception e){ - icon_notification = BitmapFactory.decodeResource(getContext().getResources(), - R.drawable.mastodonlogo); - } + + if(statuses.size() > 0 ) + message = getContext().getResources().getQuantityString(R.plurals.other_notif_hometimeline, statuses.size(), statuses.size()); + else + message = ""; + final Intent intent = new Intent(getContext(), MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK ); + intent.putExtra(INTENT_ACTION, HOME_TIMELINE_INTENT); + intent.putExtra(PREF_KEY_ID, userId); + long notif_id = Long.parseLong(userId); + final int notificationId = ((notif_id + 2) > 2147483647) ? (int) (2147483647 - notif_id - 2) : (int) (notif_id + 2); + + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.LAST_HOMETIMELINE_MAX_ID + userId, apiResponse.getMax_id()); + editor.apply(); + + if( notificationUrl != null){ + ImageLoader imageLoaderNoty = ImageLoader.getInstance(); + File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name)); + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext()) + .imageDownloader(new PatchBaseImageDownloader(getContext())) + .threadPoolSize(5) + .threadPriority(Thread.MIN_PRIORITY + 3) + .denyCacheImageMultipleSizesInMemory() + .diskCache(new UnlimitedDiskCache(cacheDir)) + .build(); + imageLoaderNoty.init(config); + DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + final String finalMessage = message; + final String finalTitle = getContext().getResources().getString(R.string.notif_pouet, status.getAccount().getUsername()); + imageLoaderNoty.loadImage(notificationUrl, options, new SimpleImageLoadingListener(){ + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + super.onLoadingComplete(imageUri, view, loadedImage); + if( max_id != null) + notify_user(getContext(), intent, notificationId, loadedImage, finalTitle, finalMessage); + } + @Override + public void onLoadingFailed(java.lang.String imageUri, android.view.View view, FailReason failReason){ + if( max_id != null) + notify_user(getContext(), intent, notificationId, BitmapFactory.decodeResource(getContext().getResources(), + R.drawable.mastodonlogo), finalTitle, finalMessage); + }}); + + } } - if(statuses.size() > 0 ) - message = getContext().getResources().getQuantityString(R.plurals.other_notif_hometimeline, statuses.size(), statuses.size()); - else - message = ""; - final Intent intent = new Intent(getContext(), MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK ); - intent.putExtra(INTENT_ACTION, HOME_TIMELINE_INTENT); - intent.putExtra(PREF_KEY_ID, userId); - long notif_id = Long.parseLong(userId); - int notificationId = ((notif_id + 2) > 2147483647) ? (int) (2147483647 - notif_id - 2) : (int) (notif_id + 2); - if( max_id != null) - notify_user(getContext(), intent, notificationId, icon_notification,title,message); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_HOMETIMELINE_MAX_ID + userId, apiResponse.getMax_id()); - editor.apply(); + } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java index fb97c1b49..28957237a 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java @@ -22,12 +22,19 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.support.annotation.NonNull; +import android.view.View; + import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; +import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; + import java.io.File; import java.util.List; import java.util.Set; @@ -134,7 +141,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + userId, null); + final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + userId, null); //No previous notifications in cache, so no notification will be sent int newFollows = 0; int newAdds = 0; @@ -143,7 +150,7 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications int newShare = 0; String notificationUrl = null; String title = null; - String message; + final String message; for(Notification notification: notifications){ //The notification associated to max_id is discarded as it is supposed to have already been sent if( max_id != null && notification.getId().equals(max_id)) @@ -187,24 +194,6 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications break; default: } - if( notificationUrl != null && icon_notification == null){ - try { - ImageLoader imageLoaderNoty = ImageLoader.getInstance(); - File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name)); - ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext()) - .imageDownloader(new PatchBaseImageDownloader(getContext())) - .threadPoolSize(5) - .threadPriority(Thread.MIN_PRIORITY + 3) - .denyCacheImageMultipleSizesInMemory() - .diskCache(new UnlimitedDiskCache(cacheDir)) - .build(); - imageLoaderNoty.init(config); - icon_notification = imageLoaderNoty.loadImageSync(notificationUrl); - }catch (Exception e){ - icon_notification = BitmapFactory.decodeResource(getContext().getResources(), - R.drawable.mastodonlogo); - } - } } int allNotifCount = newFollows + newAdds + newAsks + newMentions + newShare; if( allNotifCount > 0){ @@ -219,9 +208,38 @@ public class NotificationsSyncJob extends Job implements OnRetrieveNotifications intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT); intent.putExtra(PREF_KEY_ID, userId); long notif_id = Long.parseLong(userId); - int notificationId = ((notif_id + 1) > 2147483647) ? (int) (2147483647 - notif_id - 1) : (int) (notif_id + 1); - if( max_id != null) - notify_user(getContext(), intent, notificationId, icon_notification,title,message); + final int notificationId = ((notif_id + 1) > 2147483647) ? (int) (2147483647 - notif_id - 1) : (int) (notif_id + 1); + + if( notificationUrl != null && icon_notification == null){ + ImageLoader imageLoaderNoty = ImageLoader.getInstance(); + File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name)); + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext()) + .imageDownloader(new PatchBaseImageDownloader(getContext())) + .threadPoolSize(5) + .threadPriority(Thread.MIN_PRIORITY + 3) + .denyCacheImageMultipleSizesInMemory() + .diskCache(new UnlimitedDiskCache(cacheDir)) + .build(); + imageLoaderNoty.init(config); + DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + + final String finalTitle = title; + imageLoaderNoty.loadImage(notificationUrl, options, new SimpleImageLoadingListener(){ + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + super.onLoadingComplete(imageUri, view, loadedImage); + if( max_id != null) + notify_user(getContext(), intent, notificationId, loadedImage, finalTitle, message); + } + @Override + public void onLoadingFailed(java.lang.String imageUri, android.view.View view, FailReason failReason){ + if( max_id != null) + notify_user(getContext(), intent, notificationId, BitmapFactory.decodeResource(getContext().getResources(), + R.drawable.mastodonlogo), finalTitle, message); + }}); + } + } SharedPreferences.Editor editor = sharedpreferences.edit(); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/webview/MastalabWebChromeClient.java b/app/src/main/java/fr/gouv/etalab/mastodon/webview/MastalabWebChromeClient.java new file mode 100644 index 000000000..d4f5edb80 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/webview/MastalabWebChromeClient.java @@ -0,0 +1,223 @@ +package fr.gouv.etalab.mastodon.webview; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr + * + * This program 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. + * + * Mastodon Etalab 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 Thomas Schneider; if not, + * see . */ +import android.app.Activity; +import android.graphics.Bitmap; +import android.media.MediaPlayer; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import mastodon.etalab.gouv.fr.mastodon.R; + +/** + * Created by Thomas on 25/06/2017. + * Custom WebChromeClient + */ + +public class MastalabWebChromeClient extends WebChromeClient implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { + + private FrameLayout videoViewContainer; + private WebChromeClient.CustomViewCallback videoViewCallback; + + private ToggledFullscreenCallback toggledFullscreenCallback; + + private WebView webView; + private View activityNonVideoView; + private ViewGroup activityVideoView; + private ProgressBar pbar; + private boolean isVideoFullscreen; + private Activity activity; + + + public interface ToggledFullscreenCallback { + void toggledFullscreen(boolean fullscreen); + } + + public MastalabWebChromeClient(Activity activity, WebView webView, FrameLayout activityNonVideoView, ViewGroup activityVideoView){ + this.activity = activity; + this.isVideoFullscreen = false; + this.webView = webView; + this.pbar = (ProgressBar) activity.findViewById(R.id.progress_bar); + this.activityNonVideoView = activityNonVideoView; + this.activityVideoView = activityVideoView; + } + + @Override + public void onProgressChanged(WebView view, int progress) { + if( pbar != null){ + if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) { + pbar.setVisibility(ProgressBar.VISIBLE); + } + pbar.setProgress(progress); + if (progress == 100) { + pbar.setVisibility(ProgressBar.GONE); + } + } + } + + + @Override + public void onReceivedIcon(WebView view, Bitmap icon) { + super.onReceivedIcon(view, icon); + LayoutInflater mInflater = LayoutInflater.from(activity); + ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar(); + if( actionBar != null){ + View webview_actionbar = mInflater.inflate(R.layout.webview_actionbar, null); + TextView webview_title = (TextView) webview_actionbar.findViewById(R.id.webview_title); + webview_title.setText(view.getTitle()); + ImageView webview_favicon = (ImageView) webview_actionbar.findViewById(R.id.webview_favicon); + if( icon != null) + webview_favicon.setImageBitmap(icon); + actionBar.setCustomView(webview_actionbar); + actionBar.setDisplayShowCustomEnabled(true); + }else { + activity.setTitle(view.getTitle()); + } + + } + + //FULLSCREEN VIDEO + //Code from https://stackoverflow.com/a/16179544/3197259 + + /** + * Set a callback that will be fired when the video starts or finishes displaying using a custom view (typically full-screen) + * @param callback A VideoEnabledWebChromeClient.ToggledFullscreenCallback callback + */ + public void setOnToggledFullscreen(ToggledFullscreenCallback callback) { + this.toggledFullscreenCallback = callback; + } + + @Override + public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) { + if (view instanceof FrameLayout) { + if( ((AppCompatActivity) activity).getSupportActionBar() != null) + ((AppCompatActivity) activity).getSupportActionBar().hide(); + // A video wants to be shown + FrameLayout frameLayout = (FrameLayout) view; + View focusedChild = frameLayout.getFocusedChild(); + + // Save video related variables + isVideoFullscreen = true; + this.videoViewContainer = frameLayout; + this.videoViewCallback = callback; + + // Hide the non-video view, add the video view, and show it + activityNonVideoView.setVisibility(View.INVISIBLE); + activityVideoView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + activityVideoView.setVisibility(View.VISIBLE); + if (focusedChild instanceof android.widget.VideoView) { + // android.widget.VideoView (typically API level <11) + android.widget.VideoView videoView = (android.widget.VideoView) focusedChild; + // Handle all the required events + videoView.setOnCompletionListener(this); + videoView.setOnErrorListener(this); + } else { + // Other classes, including: + // - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 11-18) + // - android.webkit.HTML5VideoFullScreen$VideoTextureView, which inherits from android.view.TextureView (typically API level 11-18) + // - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 19+) + + // Handle HTML5 video ended event only if the class is a SurfaceView + // Test case: TextureView of Sony Xperia T API level 16 doesn't work fullscreen when loading the javascript below + if (webView != null && webView.getSettings().getJavaScriptEnabled() && focusedChild instanceof SurfaceView) { + // Run javascript code that detects the video end and notifies the Javascript interface + String js = "javascript:"; + js += "var _ytrp_html5_video_last;"; + js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];"; + js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {"; + { + js += "_ytrp_html5_video_last = _ytrp_html5_video;"; + js += "function _ytrp_html5_video_ended() {"; + { + js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must match Javascript interface name and method of VideoEnableWebView + } + js += "}"; + js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);"; + } + js += "}"; + webView.loadUrl(js); + } + } + // Notify full-screen change + if (toggledFullscreenCallback != null) { + toggledFullscreenCallback.toggledFullscreen(true); + } + } + } + + // Available in API level 14+, deprecated in API level 18+ + @Override @SuppressWarnings("deprecation") + public void onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback) { + onShowCustomView(view, callback); + } + + @Override + public void onHideCustomView() { + if( ((AppCompatActivity) activity).getSupportActionBar() != null) + ((AppCompatActivity) activity).getSupportActionBar().show(); + // This method should be manually called on video end in all cases because it's not always called automatically. + // This method must be manually called on back key press (from this class' onBackPressed() method). + if (isVideoFullscreen) { + // Hide the video view, remove it, and show the non-video view + activityVideoView.setVisibility(View.INVISIBLE); + activityVideoView.removeView(videoViewContainer); + activityNonVideoView.setVisibility(View.VISIBLE); + // Call back (only in API level <19, because in API level 19+ with chromium webview it crashes) + if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium.")) { + videoViewCallback.onCustomViewHidden(); + } + + // Reset video related variables + isVideoFullscreen = false; + videoViewContainer = null; + videoViewCallback = null; + + // Notify full-screen change + if (toggledFullscreenCallback != null) { + toggledFullscreenCallback.toggledFullscreen(false); + } + } + } + + // Video will start loading + @Override + public View getVideoLoadingProgressView() { + return super.getVideoLoadingProgressView(); + } + + // Video finished playing, only called in the case of android.widget.VideoView (typically API level <11) + @Override + public void onCompletion(MediaPlayer mp) { + onHideCustomView(); + } + + // Error while playing video, only called in the case of android.widget.VideoView (typically API level <11) + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + return false; // By returning false, onCompletion() will be called + } + +} + diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/webview/MastalabWebViewClient.java b/app/src/main/java/fr/gouv/etalab/mastodon/webview/MastalabWebViewClient.java new file mode 100644 index 000000000..9ff1d9348 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/webview/MastalabWebViewClient.java @@ -0,0 +1,66 @@ +package fr.gouv.etalab.mastodon.webview; +/* Copyright 2017 Thomas Schneider + * + * This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr + * + * This program 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. + * + * Mastodon Etalab 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 Thomas Schneider; if not, + * see . */ +import android.app.Activity; +import android.graphics.Bitmap; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.TextView; + +import fr.gouv.etalab.mastodon.helper.Helper; +import mastodon.etalab.gouv.fr.mastodon.R; + +/** + * Created by Thomas on 25/06/2017. + * Custom WebViewClient + */ + +public class MastalabWebViewClient extends WebViewClient { + + private Activity activity; + + public MastalabWebViewClient(Activity activity){ + this.activity = activity; + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + } + + + @Override + public void onPageStarted (WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + ActionBar actionBar = ((AppCompatActivity) activity).getSupportActionBar(); + LayoutInflater mInflater = LayoutInflater.from(activity); + if( actionBar != null){ + View webview_actionbar = mInflater.inflate(R.layout.webview_actionbar, null); + TextView webview_title = (TextView) webview_actionbar.findViewById(R.id.webview_title); + webview_title.setText(url); + actionBar.setCustomView(webview_actionbar); + actionBar.setDisplayShowCustomEnabled(true); + }else { + activity.setTitle(url); + } + } + + +} diff --git a/app/src/main/res/drawable-hdpi/ic_action_download.png b/app/src/main/res/drawable-hdpi/ic_action_download.png new file mode 100644 index 000000000..f08b33565 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_download.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_next_pic.png b/app/src/main/res/drawable-hdpi/ic_next_pic.png new file mode 100644 index 000000000..652842cd2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_next_pic.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_prev_pic.png b/app/src/main/res/drawable-hdpi/ic_prev_pic.png new file mode 100644 index 000000000..b84ff3630 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_prev_pic.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_download.png b/app/src/main/res/drawable-ldpi/ic_action_download.png new file mode 100644 index 000000000..a7d01ca58 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_download.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_next_pic.png b/app/src/main/res/drawable-ldpi/ic_next_pic.png new file mode 100644 index 000000000..c379cb765 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_next_pic.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_prev_pic.png b/app/src/main/res/drawable-ldpi/ic_prev_pic.png new file mode 100644 index 000000000..a622086d7 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_prev_pic.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_download.png b/app/src/main/res/drawable-mdpi/ic_action_download.png new file mode 100644 index 000000000..76e1c9b63 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_download.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_next_pic.png b/app/src/main/res/drawable-mdpi/ic_next_pic.png new file mode 100644 index 000000000..194882cfc Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_next_pic.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_prev_pic.png b/app/src/main/res/drawable-mdpi/ic_prev_pic.png new file mode 100644 index 000000000..967f56e74 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_prev_pic.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_download.png b/app/src/main/res/drawable-xhdpi/ic_action_download.png new file mode 100644 index 000000000..46501ca9b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_download.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_next_pic.png b/app/src/main/res/drawable-xhdpi/ic_next_pic.png new file mode 100644 index 000000000..5b518ec28 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_next_pic.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_prev_pic.png b/app/src/main/res/drawable-xhdpi/ic_prev_pic.png new file mode 100644 index 000000000..de2df668e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_prev_pic.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_download.png b/app/src/main/res/drawable-xxhdpi/ic_action_download.png new file mode 100644 index 000000000..80a236521 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_download.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_next_pic.png b/app/src/main/res/drawable-xxhdpi/ic_next_pic.png new file mode 100644 index 000000000..3443e3557 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_next_pic.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_prev_pic.png b/app/src/main/res/drawable-xxhdpi/ic_prev_pic.png new file mode 100644 index 000000000..4538ed3b6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_prev_pic.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_download.png b/app/src/main/res/drawable-xxxhdpi/ic_action_download.png new file mode 100644 index 000000000..a017260aa Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_download.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_next_pic.png b/app/src/main/res/drawable-xxxhdpi/ic_next_pic.png new file mode 100644 index 000000000..2df2d668c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_next_pic.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_prev_pic.png b/app/src/main/res/drawable-xxxhdpi/ic_prev_pic.png new file mode 100644 index 000000000..1f62de336 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_prev_pic.png differ diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml new file mode 100644 index 000000000..50b20e375 --- /dev/null +++ b/app/src/main/res/layout/activity_media.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_webview.xml b/app/src/main/res/layout/activity_webview.xml index d4053bb14..d3a26a3b4 100644 --- a/app/src/main/res/layout/activity_webview.xml +++ b/app/src/main/res/layout/activity_webview.xml @@ -30,7 +30,7 @@ style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="10dp" - android:padding="2dp"> + android:padding="1dp"> + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_media.xml b/app/src/main/res/menu/main_media.xml new file mode 100644 index 000000000..c2db22f17 --- /dev/null +++ b/app/src/main/res/menu/main_media.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4f717dc5e..f68e30a08 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,7 +10,7 @@ Confidentialité Cache Déconnexion - + Télécharger Connexion @@ -20,6 +20,8 @@ Annuler Télécharger Télécharger %1$s + Téléchargement terminé + Fichier : %1$s Mot de passe Email Comptes