Fixes issues with media with older Android versions

This commit is contained in:
tom79 2017-06-26 18:49:39 +02:00
parent 98715fd53a
commit cab652a9c8
37 changed files with 1135 additions and 467 deletions

View File

@ -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'
}

View File

@ -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"

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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
}
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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 "";
}
}

View File

@ -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();
}

View File

@ -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();

View File

@ -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
}
}

View File

@ -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);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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>