Commit ffb9abf5 authored by Schoumi's avatar Schoumi

Manage upload multiple files at the same time

Fixe #3
parent 77a051c9
......@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "24.0.3"
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "fr.mobdev.goblim"
......@@ -22,6 +22,6 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.android.support:design:25.0.0'
compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.android.support:design:25.1.0'
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.mobdev.goblim" >
<uses-sdk android:minSdkVersion="11"/>
package="fr.mobdev.goblim">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
......@@ -15,37 +13,46 @@
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="fr.mobdev.goblim.activity.UploadActivity"
android:name=".activity.UploadActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
>
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="fr.mobdev.goblim.activity.HistoryActivity"
android:name=".activity.HistoryActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
>
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="fr.mobdev.goblim.activity.LinkActivity"
android:name=".activity.LinkActivity"
android:label="@string/title_activity_link"
android:theme="@style/AppTheme.NoActionBar"
>
</activity>
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name="fr.mobdev.goblim.activity.ServersActivity"
android:name=".activity.ServersActivity"
android:label="@string/title_activity_servers"
android:theme="@style/AppTheme.NoActionBar"
>
</activity>
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name=".activity.MultiLinkActivity"
android:label="@string/title_share_links"
android:theme="@style/AppTheme.NoActionBar"/>
</application>
</manifest>
</manifest>
\ No newline at end of file
/*
* Copyright (C) 2017 Anthony Chomienne, anthony@mob-dev.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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package fr.mobdev.goblim;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class ImageListAdapter extends RecyclerView.Adapter<ImageViewHolder> {
private OnImageClickListener listener;
private LinkedHashMap<Uri,Bitmap> datas;
private ImageLoader loader;
private ImageLoader.ImageLoaderListener loaderListener;
public ImageListAdapter(OnImageClickListener listener, ImageLoader.ImageLoaderListener loaderListener){
datas = new LinkedHashMap<>();
this.listener = listener;
this.loaderListener = loaderListener;
}
public void setBitmapsForUris(Context context, List<Uri> imageUris, List<Bitmap> bitmaps) {
int i = 0;
if(bitmaps.size() != imageUris.size())
return;
for(Uri imUri : imageUris) {
setBitmapForUri(imUri,bitmaps.get(i));
if(bitmaps.get(i) == null) {
ImageLoader.Message mes = new ImageLoader.Message();
mes.context = context;
mes.uri = imUri;
addMessageToQueue(mes);
}
i++;
}
}
public void setBitmapForUri(Uri imageUri, Bitmap thumb) {
if(datas.containsKey(imageUri)) {
datas.put(imageUri, thumb);
notifyDataSetChanged();
}
}
public void addUris(Context context, List<Uri> uris, boolean loadImg) {
for(Uri imUri : uris) {
if(loadImg) {
addUri(context,imUri);
} else {
datas.put(imUri,null);
}
}
notifyDataSetChanged();
}
public void addUri(Context context, Uri imUri) {
datas.put(imUri,null);
ImageLoader.Message mes = new ImageLoader.Message();
mes.context = context;
mes.uri = imUri;
addMessageToQueue(mes);
notifyDataSetChanged();
}
private void addMessageToQueue(ImageLoader.Message mes) {
if(loader == null || loader.getState() == Thread.State.TERMINATED) {
loader = new ImageLoader(loaderListener);
loader.start();
}
loader.addMessageToQueue(mes);
}
public Bitmap getForUri(Uri imUri) {
return datas.get(imUri);
}
@Override
public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.thumb_items,parent,false);
return new ImageViewHolder(v);
}
@Override
public void onBindViewHolder(ImageViewHolder holder, int position) {
List<Uri> uris = new ArrayList<>(datas.keySet());
final Uri imUri = uris.get(position);
Bitmap bt = datas.get(imUri);
if(bt != null) {
holder.thumb.setVisibility(View.VISIBLE);
holder.thumb.setImageBitmap(bt);
holder.progressLoad.setVisibility(View.GONE);
} else {
holder.thumb.setVisibility(View.GONE);
holder.progressLoad.setVisibility(View.VISIBLE);
}
holder.resetBt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
datas.remove(imUri);
listener.onRemove(imUri);
notifyDataSetChanged();
}
});
holder.thumb.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
listener.OnImageClick(imUri);
}
});
}
@Override
public int getItemCount() {
return datas.size();
}
public ArrayList<Bitmap> getBitmaps() {
return new ArrayList<>(datas.values());
}
public void clear() {
datas.clear();
notifyDataSetChanged();
}
public interface OnImageClickListener{
void OnImageClick(Uri imageUri);
void onRemove(Uri imageUri);
}
}
class ImageViewHolder extends RecyclerView.ViewHolder {
ProgressBar progressLoad;
ImageView resetBt;
ImageView thumb;
ImageViewHolder(View v) {
super(v);
resetBt = (ImageView) v.findViewById(R.id.reset_img);
thumb = (ImageView) v.findViewById(R.id.thumbnail_item);
progressLoad = (ProgressBar) v.findViewById(R.id.progress_thumb);
}
}
/*
* Copyright (C) 2017 Anthony Chomienne, anthony@mob-dev.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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package fr.mobdev.goblim;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.DisplayMetrics;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
public class ImageLoader extends Thread {
private Semaphore sem;
private List<Message> messages;
private ImageLoaderListener listener;
ImageLoader(ImageLoaderListener listener) {
sem = new Semaphore(0,true);
messages = new ArrayList<>();
this.listener = listener;
}
void addMessageToQueue(Message mes) {
messages.add(mes);
sem.release();
}
@Override
public void run(){
boolean isRunning = true;
while (isRunning){
try {
sem.acquire();
if (messages.size() > 0) {
Message mes = messages.get(0);
Bitmap thumb = loadThumb(mes);
if(thumb != null) {
listener.thumbLoaded(mes.uri, thumb);
}
}
}catch(InterruptedException e){
e.printStackTrace();
isRunning = false;
}
messages.remove(0);
if(messages.isEmpty())
listener.allThumbLoaded();
}
}
private Bitmap loadThumb(Message mes) {
Bitmap thumb = null;
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = true;
opts.inDensity = 640;
opts.inTargetDensity = DisplayMetrics.DENSITY_MEDIUM;
thumb = BitmapFactory.decodeStream(mes.context.getContentResolver().openInputStream(mes.uri), null, opts);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return thumb;
}
static class Message {
Context context;
Uri uri;
}
public interface ImageLoaderListener{
void thumbLoaded(Uri imageUri, Bitmap thumb);
void allThumbLoaded();
}
}
/*
* Copyright (C) 2017 Anthony Chomienne, anthony@mob-dev.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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package fr.mobdev.goblim;
import android.graphics.Bitmap;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ProgressBar;
import java.util.ArrayList;
import java.util.List;
public class MultiLinkAdapter extends RecyclerView.Adapter<MultiLinkViewHolder>{
private List<Bitmap> bitmaps;
private List<Integer> selecteds;
public MultiLinkAdapter(int length) {
bitmaps = new ArrayList<>();
selecteds = new ArrayList<>();
for(int i = 0; i < length; i++) {
bitmaps.add(null);
selecteds.add(i);
}
}
public void setBitmap(Bitmap bt, int position) {
bitmaps.set(position,bt);
notifyDataSetChanged();
}
public List<Integer> getSelecteds() {
return selecteds;
}
@Override
public MultiLinkViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.link_item,parent,false);
final MultiLinkViewHolder holder = new MultiLinkViewHolder(v);
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
CheckBox box;
if(v instanceof CheckBox) {
box = (CheckBox) v;
} else {
box = (CheckBox) v.findViewById(R.id.selected);
}
if(box.isChecked() && !selecteds.contains(holder.index)) {
selecteds.add(holder.index);
} else {
selecteds.remove(holder.index);
}
}
};
CheckBox box = (CheckBox) v.findViewById(R.id.selected);
box.setOnClickListener(listener);
v.setOnClickListener(listener);
return holder;
}
@Override
public void onBindViewHolder(MultiLinkViewHolder holder, int position) {
Bitmap bt = bitmaps.get(position);
if(bt != null) {
holder.progress.setVisibility(View.GONE);
holder.thumb.setImageBitmap(bt);
holder.thumb.setVisibility(View.VISIBLE);
} else {
holder.progress.setVisibility(View.VISIBLE);
holder.thumb.setVisibility(View.GONE);
}
holder.box.setChecked(selecteds.contains(position));
}
@Override
public int getItemCount() {
return bitmaps.size();
}
}
class MultiLinkViewHolder extends RecyclerView.ViewHolder {
int index;
CheckBox box;
ImageView thumb;
ProgressBar progress;
MultiLinkViewHolder(View itemView) {
super(itemView);
box = (CheckBox) itemView.findViewById(R.id.selected);
thumb = (ImageView) itemView.findViewById(R.id.thumbnail_link);
progress = (ProgressBar) itemView.findViewById(R.id.progress);
}
}
\ No newline at end of file
......@@ -102,7 +102,7 @@ public class LinkActivity extends AppCompatActivity {
final NetworkAdapter listener = new NetworkAdapter() {
@Override
public void deleteSucceed()
public void deleteSucceed(String deleteUrl)
{
List<Img> images = new ArrayList<>();
images.add(image);
......@@ -145,10 +145,10 @@ public class LinkActivity extends AppCompatActivity {
long days = millis / (24*60*60*1000);
//storage duration has ended or not?
if(storageDuration == 0 || storageDuration - days >= 0)
NetworkManager.getInstance(listener).deleteImage(LinkActivity.this, deleteUrl);
NetworkManager.getInstance(listener).delete(LinkActivity.this, deleteUrl);
else {
//image is no more on the server, delete is only local now
listener.deleteSucceed();
listener.deleteSucceed(deleteUrl);
}
}
})
......
/*
* Copyright (C) 2017 Anthony Chomienne, anthony@mob-dev.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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package fr.mobdev.goblim.activity;
import android.content.ClipboardManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import fr.mobdev.goblim.Database;
import fr.mobdev.goblim.MultiLinkAdapter;
import fr.mobdev.goblim.NetworkManager;
import fr.mobdev.goblim.R;
import fr.mobdev.goblim.listener.NetworkAdapter;
import fr.mobdev.goblim.objects.Img;
public class MultiLinkActivity extends AppCompatActivity {
private ArrayList<String> sharedUrls;
private List<String> deleteUrls;
private MultiLinkAdapter adapter;
private List<Img> images;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.multilink);
Toolbar toolbar = (Toolbar) findViewById(R.id.link_toolbar);
setSupportActionBar(toolbar);
//get url information
Intent receiveIntent = getIntent();
Object[] extra = (Object[]) receiveIntent.getSerializableExtra("imageIds");
final Long[] ids = Arrays.copyOf(extra,extra.length,Long[].class);
//setup Adapter
adapter = new MultiLinkAdapter(ids.length);
RecyclerView listView = (RecyclerView) findViewById(R.id.link_list);
listView.setAdapter(adapter);
sharedUrls = new ArrayList<>();
deleteUrls = new ArrayList<>();
images = new ArrayList<>();
final ImageButton shareButton = (ImageButton) findViewById(R.id.share_button);
final ImageButton copyClipboardButton = (ImageButton) findViewById(R.id.copy_clipboard_button);
final ImageButton deleteImageButton = (ImageButton) findViewById(R.id.delete_button);
shareButton.setEnabled(false);
copyClipboardButton.setEnabled(false);
deleteImageButton.setEnabled(false);
//TODO Save For Rotation
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
for(Long id : ids) {
Img image = Database.getInstance(getApplicationContext()).getImage(id);
images.add(image);
String url = image.getUrl();
String shortHash = image.getShortHash();
String realShortHash = image.getRealShortHash();
String token = image.getToken();
final Bitmap bt = image.getThumb();
final int index = i;
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.setBitmap(bt,index);
}
});
//add a / at the end of the url before adding the hash
if(!url.endsWith("/"))
url = url.concat("/");
String sharedUrl = url.concat(shortHash);
String deleteUrl = url.concat("d/"+realShortHash+"/"+token);
sharedUrls.add(sharedUrl);
deleteUrls.add(deleteUrl);
i++;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
shareButton.setEnabled(true);
copyClipboardButton.setEnabled(true);
deleteImageButton.setEnabled(true);
}
});
}
}).start();
//manage the sharing button
shareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
List<Integer> selected = adapter.getSelecteds();
String output = "";
for(Integer index : selected) {
output += sharedUrls.get(index)+"\n";
}
sendIntent.putExtra(Intent.EXTRA_TEXT, output);
sendIntent.setType("text/plain");
startActivity(sendIntent);
}
});
//manage the clipboard button
copyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
List<Integer> selected = adapter.getSelecteds();
String output = "";
for(Integer index : selected) {
output += sharedUrls.get(index)+"\n";
}
android.content.ClipData clip = android.content.ClipData.newPlainText("Copied URL", output);
clipboard.setPrimaryClip(clip);
Toast.makeText(MultiLinkActivity.this,getString(R.string.copy_to_clipboard),Toast.LENGTH_SHORT).show();
}
});
final NetworkAdapter listener = new NetworkAdapter() {
@Override
public void deleteSucceed(String deleteUrl)
{
List<Img> imgs = new ArrayList<>();
int idx = deleteUrls.indexOf(deleteUrl);
if(idx < 0 || idx >= images.size())
return;
imgs.add(images.get(idx));
Database.getInstance(getApplicationContext()).deleteImg(imgs);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MultiLinkActivity.this, R.string.delete_succeed, Toast.LENGTH_SHORT).show();
}
});
finish();
}
@Override
public void deleteError(final String error)
{
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MultiLinkActivity.this, error, Toast.LENGTH_SHORT).show();
}
});
}
};
deleteImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertD