Fixes issues with media with older Android versions
|
@ -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'
|
||||
|
||||
}
|
||||
|
|
|
@ -75,6 +75,12 @@
|
|||
android:launchMode="singleTask"
|
||||
android:noHistory="true"
|
||||
/>
|
||||
<activity android:name="fr.gouv.etalab.mastodon.activities.MediaActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:launchMode="singleTask"
|
||||
android:noHistory="true"
|
||||
/>
|
||||
<activity android:name="fr.gouv.etalab.mastodon.activities.SearchResultActivity"
|
||||
android:windowSoftInputMode="stateAlwaysHidden"
|
||||
android:configChanges="orientation|screenSize"
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
/* 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 <http://www.gnu.org/licenses>. */
|
||||
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<Attachment> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -97,7 +97,7 @@ public class TootActivity extends AppCompatActivity implements OnRetrieveSearcAc
|
|||
private ImageLoader imageLoader;
|
||||
private DisplayImageOptions options;
|
||||
private LinearLayout toot_picture_container;
|
||||
private List<Attachment> attachments;
|
||||
private ArrayList<Attachment> 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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1160,7 +1160,7 @@ public class API {
|
|||
|
||||
//Retrieves attachments
|
||||
JSONArray arrayAttachement = resobj.getJSONArray("media_attachments");
|
||||
List<Attachment> attachments = new ArrayList<>();
|
||||
ArrayList<Attachment> attachments = new ArrayList<>();
|
||||
if( arrayAttachement != null){
|
||||
for(int j = 0 ; j < arrayAttachement.length() ; j++){
|
||||
JSONObject attObj = arrayAttachement.getJSONObject(j);
|
||||
|
|
|
@ -14,11 +14,15 @@
|
|||
* see <http://www.gnu.org/licenses>. */
|
||||
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<Attachment> CREATOR = new Creator<Attachment>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Attachment> media_attachments;
|
||||
private ArrayList<Attachment> media_attachments;
|
||||
private List<Mention> mentions;
|
||||
private List<Tag> tags;
|
||||
private Application application;
|
||||
|
@ -208,11 +209,11 @@ public class Status implements Parcelable {
|
|||
}
|
||||
|
||||
|
||||
public List<Attachment> getMedia_attachments() {
|
||||
public ArrayList<Attachment> getMedia_attachments() {
|
||||
return media_attachments;
|
||||
}
|
||||
|
||||
public void setMedia_attachments(List<Attachment> media_attachments) {
|
||||
public void setMedia_attachments(ArrayList<Attachment> media_attachments) {
|
||||
this.media_attachments = media_attachments;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
|
||||
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) {
|
||||
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Status> 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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <http://www.gnu.org/licenses>. */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
After Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 207 B |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 597 B |
After Width: | Height: | Size: 213 B |
After Width: | Height: | Size: 678 B |
After Width: | Height: | Size: 779 B |
After Width: | Height: | Size: 355 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 548 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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 <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_image"
|
||||
>
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:src="@drawable/ic_prev_pic"
|
||||
android:id="@+id/media_prev"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:id="@+id/media_next"
|
||||
android:src="@drawable/ic_next_pic"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:layout_centerInParent="true"
|
||||
android:id="@+id/media_picture"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="ContentDescription" />
|
||||
<VideoView
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/media_video"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
<!-- Main Loader -->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/loader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
>
|
||||
<ProgressBar
|
||||
android:id="@+id/pbar_inf"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:indeterminate="true" />
|
||||
<TextView
|
||||
android:id="@+id/loader_progress"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:layout_below="@+id/pbar_inf"
|
||||
android:layout_width="50dp"
|
||||
android:gravity="center"
|
||||
android:layout_height="wrap_content" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
|
@ -30,7 +30,7 @@
|
|||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="10dp"
|
||||
android:padding="2dp">
|
||||
android:padding="1dp">
|
||||
</ProgressBar>
|
||||
<FrameLayout
|
||||
android:id="@+id/webview_container"
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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 <http://www.gnu.org/licenses>.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<TextView
|
||||
android:layout_gravity="center_vertical"
|
||||
android:id="@+id/picture_actionbar"
|
||||
android:maxLines="1"
|
||||
android:layout_width="match_parent"
|
||||
android:textColor="@color/white"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_download"
|
||||
android:title="@string/action_download"
|
||||
android:icon="@drawable/ic_action_download"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
|
@ -10,7 +10,7 @@
|
|||
<string name="action_privacy">Confidentialité</string>
|
||||
<string name="action_cache">Cache</string>
|
||||
<string name="action_logout">Déconnexion</string>
|
||||
|
||||
<string name="action_download">Télécharger</string>
|
||||
<string name="login">Connexion</string>
|
||||
|
||||
<!-- common -->
|
||||
|
@ -20,6 +20,8 @@
|
|||
<string name="cancel">Annuler</string>
|
||||
<string name="download">Télécharger</string>
|
||||
<string name="download_file">Télécharger %1$s</string>
|
||||
<string name="download_over">Téléchargement terminé</string>
|
||||
<string name="download_from">Fichier : %1$s</string>
|
||||
<string name="password">Mot de passe</string>
|
||||
<string name="email">Email</string>
|
||||
<string name="accounts">Comptes</string>
|
||||
|
|