Allow to export/import

This commit is contained in:
Thomas 2020-11-13 18:45:55 +01:00
parent 150a81adc6
commit c3e475be6f
22 changed files with 1009 additions and 14 deletions

View File

@ -18,6 +18,14 @@
<string name="set_video_in_list">Vidéos dans une liste</string>
<string name="set_video_in_list_description">Change la mise en page pour afficher les vidéos dans une liste</string>
<string name="export_list">Exporter</string>
<string name="import_list">Importer</string>
<string name="export_notification_title">Exportation réussie !</string>
<string name="export_notification_content">Cliquer ici pour envoyer l\'exportation par mèl.</string>
<string name="export_notification_subjet">Nouvelle liste de lecture</string>
<string name="export_notification_body">Ouvrez la pièce jointe avec l\'application TubeAcad</string>
<string name="show_more">Montrer plus</string>
<string name="show_less">Montrer moins</string>
<string name="no_instances">Aucune instance !</string>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="my_images"
path="/TubeAcad/" />
</paths>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="app.fedilab.fedilabtube">
<application
android:name=".FedilabTube"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity
android:name=".MainActivity"
tools:node="merge">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.tubelab" />
<data android:pathPattern=".*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.tubelab" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -283,6 +283,14 @@
<string name="all">All</string>
<!-- end languages -->
<string name="export_list">Export</string>
<string name="import_list">Import</string>
<string name="export_notification_title">Successful export!</string>
<string name="export_notification_content">Tap here to send the export by email</string>
<string name="export_notification_subjet">New Playlist</string>
<string name="export_notification_body">Open the attached file with TubeLab</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="action_playlist_add">You don\'t have any playlists. Tap on the \"+\" icon to add a new playlist</string>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path="/TubeLab/" />
</paths>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="app.fedilab.fedilabtube">
<application
android:name=".FedilabTube"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity
android:name=".MainActivity"
tools:node="merge">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.tubelab" />
<data android:pathPattern=".*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.tubelab" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.tubelab" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -161,6 +161,17 @@
android:authorities="${applicationId}.workmanager-init"
tools:node="remove"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

View File

@ -65,6 +65,7 @@ import app.fedilab.fedilabtube.client.entities.WellKnownNodeinfo;
import app.fedilab.fedilabtube.fragment.DisplayOverviewFragment;
import app.fedilab.fedilabtube.fragment.DisplayVideosFragment;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.PlaylistExportHelper;
import app.fedilab.fedilabtube.helper.SwitchAccountHelper;
import app.fedilab.fedilabtube.services.RetrieveInfoService;
import app.fedilab.fedilabtube.sqlite.AccountDAO;
@ -84,6 +85,7 @@ public class MainActivity extends AppCompatActivity {
public static int PICK_INSTANCE = 5641;
public static int PICK_INSTANCE_SURF = 5642;
public static UserMe userMe;
public static TypeOfConnection typeOfConnection;
final FragmentManager fm = getSupportFragmentManager();
Fragment active;
private DisplayVideosFragment recentFragment, locaFragment, trendingFragment, subscriptionFragment, mostLikedFragment;
@ -121,7 +123,6 @@ public class MainActivity extends AppCompatActivity {
return false;
}
};
public static TypeOfConnection typeOfConnection;
@SuppressLint("ApplySharedPref")
public static void showRadioButtonDialogFullInstances(Activity activity, boolean storeInDb) {
@ -359,7 +360,9 @@ public class MainActivity extends AppCompatActivity {
RateThisApp.onCreate(this);
RateThisApp.showRateDialogIfNeeded(this);
}
if (!BuildConfig.full_instances) {
PlaylistExportHelper.manageIntentUrl(MainActivity.this, getIntent());
}
}
private void startInForeground() {
@ -611,9 +614,12 @@ public class MainActivity extends AppCompatActivity {
if (extras.getInt(Helper.INTENT_ACTION) == Helper.ADD_USER_INTENT) {
recreate();
}
} else if (!BuildConfig.full_instances) {
PlaylistExportHelper.manageIntentUrl(MainActivity.this, intent);
}
}
@SuppressLint("ApplySharedPref")
private void showRadioButtonDialog() {

View File

@ -14,6 +14,7 @@ package app.fedilab.fedilabtube;
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;
@ -24,12 +25,14 @@ import androidx.fragment.app.FragmentTransaction;
import app.fedilab.fedilabtube.client.data.PlaylistData;
import app.fedilab.fedilabtube.fragment.DisplayVideosFragment;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.PlaylistExportHelper;
import app.fedilab.fedilabtube.viewmodel.TimelineVM;
import es.dmoral.toasty.Toasty;
public class PlaylistsActivity extends AppCompatActivity {
private final int PICK_IMPORT = 5556;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -66,6 +69,22 @@ public class PlaylistsActivity extends AppCompatActivity {
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) {
if (data == null || data.getData() == null) {
Toasty.error(PlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
return;
}
PlaylistExportHelper.manageIntentUrl(PlaylistsActivity.this, data);
} else if (requestCode == PICK_IMPORT) {
Toasty.error(PlaylistsActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {

View File

@ -20,8 +20,6 @@ import android.content.res.ColorStateList;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
@ -72,7 +70,6 @@ import app.fedilab.fedilabtube.viewmodel.TimelineVM;
import es.dmoral.toasty.Toasty;
import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY;
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.NORMAL;
import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.SURFING;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.FOLLOW;
import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.MUTE;

View File

@ -755,4 +755,78 @@ public class VideoData {
this.description = description;
}
}
public static class VideoExport implements Parcelable {
public static final Creator<VideoExport> CREATOR = new Creator<VideoExport>() {
@Override
public VideoExport createFromParcel(Parcel in) {
return new VideoExport(in);
}
@Override
public VideoExport[] newArray(int size) {
return new VideoExport[size];
}
};
private int id;
private String uuid;
private Video videoData;
private int playlistDBid;
public VideoExport() {
}
protected VideoExport(Parcel in) {
id = in.readInt();
uuid = in.readString();
videoData = in.readParcelable(Video.class.getClassLoader());
playlistDBid = in.readInt();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public Video getVideoData() {
return videoData;
}
public void setVideoData(Video videoData) {
this.videoData = videoData;
}
public int getPlaylistDBid() {
return playlistDBid;
}
public void setPlaylistDBid(int playlistDBid) {
this.playlistDBid = playlistDBid;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(id);
parcel.writeString(uuid);
parcel.writeParcelable(videoData, i);
parcel.writeInt(playlistDBid);
}
}
}

View File

@ -15,11 +15,15 @@ package app.fedilab.fedilabtube.client.data;
* see <http://www.gnu.org/licenses>. */
import android.os.Parcel;
import android.os.Parcelable;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings({"unused", "RedundantSuppression"})
public class VideoPlaylistData {
@ -155,4 +159,90 @@ public class VideoPlaylistData {
this.uuid = uuid;
}
}
public static class VideoPlaylistExport implements Parcelable {
public static final Creator<VideoPlaylistExport> CREATOR = new Creator<VideoPlaylistExport>() {
@Override
public VideoPlaylistExport createFromParcel(Parcel in) {
return new VideoPlaylistExport(in);
}
@Override
public VideoPlaylistExport[] newArray(int size) {
return new VideoPlaylistExport[size];
}
};
private long playlistDBkey;
private String acct;
private String uuid;
private PlaylistData.Playlist playlist;
private List<VideoPlaylistData.VideoPlaylist> videos;
public VideoPlaylistExport() {
}
protected VideoPlaylistExport(Parcel in) {
playlistDBkey = in.readLong();
acct = in.readString();
uuid = in.readString();
playlist = in.readParcelable(PlaylistData.Playlist.class.getClassLoader());
in.readList(this.videos, VideoPlaylistData.VideoPlaylist.class.getClassLoader());
}
public String getAcct() {
return acct;
}
public void setAcct(String acct) {
this.acct = acct;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public PlaylistData.Playlist getPlaylist() {
return playlist;
}
public void setPlaylist(PlaylistData.Playlist playlist) {
this.playlist = playlist;
}
public long getPlaylistDBkey() {
return playlistDBkey;
}
public void setPlaylistDBkey(long playlistDBkey) {
this.playlistDBkey = playlistDBkey;
}
public List<VideoPlaylistData.VideoPlaylist> getVideos() {
return videos;
}
public void setVideos(List<VideoPlaylistData.VideoPlaylist> videos) {
this.videos = videos;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(playlistDBkey);
parcel.writeString(acct);
parcel.writeString(uuid);
parcel.writeParcelable(playlist, i);
parcel.writeList(videos);
}
}
}

View File

@ -14,13 +14,27 @@ package app.fedilab.fedilabtube.client.entities;
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.os.Parcel;
import android.os.Parcelable;
import com.google.gson.annotations.SerializedName;
import java.util.List;
@SuppressWarnings({"unused", "RedundantSuppression"})
public class StreamingPlaylists {
public class StreamingPlaylists implements Parcelable {
public static final Creator<StreamingPlaylists> CREATOR = new Creator<StreamingPlaylists>() {
@Override
public StreamingPlaylists createFromParcel(Parcel in) {
return new StreamingPlaylists(in);
}
@Override
public StreamingPlaylists[] newArray(int size) {
return new StreamingPlaylists[size];
}
};
@SerializedName("id")
private String id;
@SerializedName("type")
@ -34,6 +48,15 @@ public class StreamingPlaylists {
@SerializedName("redundancies")
private List<Redundancies> redundancies;
protected StreamingPlaylists(Parcel in) {
id = in.readString();
type = in.readInt();
playlistUrl = in.readString();
segmentsSha256Url = in.readString();
files = in.createTypedArrayList(File.CREATOR);
redundancies = in.createTypedArrayList(Redundancies.CREATOR);
}
public String getId() {
return id;
}
@ -82,10 +105,41 @@ public class StreamingPlaylists {
this.redundancies = redundancies;
}
public static class Redundancies {
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(id);
parcel.writeInt(type);
parcel.writeString(playlistUrl);
parcel.writeString(segmentsSha256Url);
parcel.writeTypedList(files);
parcel.writeTypedList(redundancies);
}
public static class Redundancies implements Parcelable {
public static final Creator<Redundancies> CREATOR = new Creator<Redundancies>() {
@Override
public Redundancies createFromParcel(Parcel in) {
return new Redundancies(in);
}
@Override
public Redundancies[] newArray(int size) {
return new Redundancies[size];
}
};
@SerializedName("baseUrl")
private String baseUrl;
protected Redundancies(Parcel in) {
baseUrl = in.readString();
}
public String getBaseUrl() {
return baseUrl;
}
@ -93,5 +147,15 @@ public class StreamingPlaylists {
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(baseUrl);
}
}
}

View File

@ -14,18 +14,38 @@ package app.fedilab.fedilabtube.client.entities;
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.os.Parcel;
import android.os.Parcelable;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
@SuppressWarnings({"unused", "RedundantSuppression"})
public class ViewsPerDay {
public class ViewsPerDay implements Parcelable {
public static final Creator<ViewsPerDay> CREATOR = new Creator<ViewsPerDay>() {
@Override
public ViewsPerDay createFromParcel(Parcel in) {
return new ViewsPerDay(in);
}
@Override
public ViewsPerDay[] newArray(int size) {
return new ViewsPerDay[size];
}
};
@SerializedName("date")
private Date date;
@SerializedName("views")
private int views;
protected ViewsPerDay(Parcel in) {
long tmpDate = in.readLong();
this.date = tmpDate == -1 ? null : new Date(tmpDate);
views = in.readInt();
}
public Date getDate() {
return date;
}
@ -41,4 +61,15 @@ public class ViewsPerDay {
public void setViews(int views) {
this.views = views;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(this.date != null ? this.date.getTime() : -1);
parcel.writeInt(views);
}
}

View File

@ -19,7 +19,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
@ -37,7 +36,6 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import app.fedilab.fedilabtube.MainActivity;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.client.data.InstanceData;
import app.fedilab.fedilabtube.databinding.DrawerAboutInstanceBinding;

View File

@ -14,10 +14,19 @@ package app.fedilab.fedilabtube.drawer;
* You should have received a copy of the GNU General Public License along with TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -29,19 +38,39 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.FutureTarget;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import app.fedilab.fedilabtube.AllPlaylistsActivity;
import app.fedilab.fedilabtube.BuildConfig;
import app.fedilab.fedilabtube.MainActivity;
import app.fedilab.fedilabtube.PlaylistsActivity;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.client.APIResponse;
import app.fedilab.fedilabtube.client.RetrofitPeertubeAPI;
import app.fedilab.fedilabtube.client.data.PlaylistData.Playlist;
import app.fedilab.fedilabtube.client.data.VideoData;
import app.fedilab.fedilabtube.client.data.VideoPlaylistData;
import app.fedilab.fedilabtube.helper.Helper;
import app.fedilab.fedilabtube.helper.NotificationHelper;
import app.fedilab.fedilabtube.helper.PlaylistExportHelper;
import app.fedilab.fedilabtube.viewmodel.PlaylistsVM;
import es.dmoral.toasty.Toasty;
import static app.fedilab.fedilabtube.viewmodel.PlaylistsVM.action.GET_LIST_VIDEOS;
public class PlaylistAdapter extends BaseAdapter {
@ -122,6 +151,9 @@ public class PlaylistAdapter extends BaseAdapter {
PopupMenu popup = new PopupMenu(context, holder.playlist_more);
popup.getMenuInflater()
.inflate(R.menu.playlist_menu, popup.getMenu());
if (!BuildConfig.full_instances) {
popup.getMenu().findItem(R.id.action_export).setVisible(true);
}
popup.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.action_delete) {
@ -145,6 +177,16 @@ public class PlaylistAdapter extends BaseAdapter {
if (context instanceof AllPlaylistsActivity) {
((AllPlaylistsActivity) context).manageAlert(playlist);
}
} else if (itemId == R.id.action_export) {
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE);
} else {
doExport(playlist);
}
} else {
doExport(playlist);
}
}
return true;
});
@ -159,6 +201,70 @@ public class PlaylistAdapter extends BaseAdapter {
}
private void doExport(Playlist playlist) {
new Thread(() -> {
File file = null;
RetrofitPeertubeAPI retrofitPeertubeAPI = new RetrofitPeertubeAPI(context);
APIResponse apiResponse = retrofitPeertubeAPI.playlistAction(GET_LIST_VIDEOS, playlist.getId(), null, null, null);
if (apiResponse != null) {
List<VideoPlaylistData.VideoPlaylist> videos = apiResponse.getVideoPlaylist();
VideoPlaylistData.VideoPlaylistExport videoPlaylistExport = new VideoPlaylistData.VideoPlaylistExport();
videoPlaylistExport.setPlaylist(playlist);
videoPlaylistExport.setUuid(playlist.getUuid());
videoPlaylistExport.setAcct(MainActivity.userMe.getAccount().getAcct());
videoPlaylistExport.setVideos(videos);
String data = PlaylistExportHelper.playlistToStringStorage(videoPlaylistExport);
File root = new File(Environment.getExternalStorageDirectory(), context.getString(R.string.app_name));
if (!root.exists()) {
root.mkdirs();
}
file = new File(root, "playlist_" + playlist.getUuid() + ".tubelab");
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.append(data);
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_LONG).show();
mainHandler.post(myRunnable);
return;
}
}
String urlAvatar = playlist.getThumbnailPath() != null ? Helper.getLiveInstance(context) + playlist.getThumbnailPath() : null;
FutureTarget<Bitmap> futureBitmapChannel = Glide.with(context.getApplicationContext())
.asBitmap()
.load(urlAvatar != null ? urlAvatar : R.drawable.missing_peertube).submit();
Bitmap icon = null;
try {
icon = futureBitmapChannel.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
if (file != null) {
Intent mailIntent = new Intent(Intent.ACTION_SEND);
mailIntent.setType("message/rfc822");
Uri contentUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileProvider", file);
mailIntent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.export_notification_subjet));
mailIntent.putExtra(Intent.EXTRA_TEXT, context.getString(R.string.export_notification_body));
mailIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
NotificationHelper.notify_user(context.getApplicationContext(),
playlist.getOwnerAccount(), mailIntent, icon,
context.getString(R.string.export_notification_title),
context.getString(R.string.export_notification_content));
}
}).start();
}
private static class ViewHolder {
LinearLayout playlist_container;
ImageView preview_playlist;
@ -166,5 +272,4 @@ public class PlaylistAdapter extends BaseAdapter {
ImageButton playlist_more;
}
}

View File

@ -446,7 +446,6 @@ public class DisplayVideosFragment extends Fragment implements AccountsHorizonta
}
public void scrollToTop() {
if (mLayoutManager != null) {
mLayoutManager.scrollToPositionWithOffset(0, 0);

View File

@ -0,0 +1,105 @@
package app.fedilab.fedilabtube.helper;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
*
* 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.
*
* TubeLab 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 TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.Activity;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import com.google.gson.Gson;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import app.fedilab.fedilabtube.PlaylistsActivity;
import app.fedilab.fedilabtube.client.data.VideoPlaylistData;
import app.fedilab.fedilabtube.sqlite.ManagePlaylistsDAO;
import app.fedilab.fedilabtube.sqlite.Sqlite;
public class PlaylistExportHelper {
/**
* Unserialized VideoPlaylistExport
*
* @param serializedVideoPlaylistExport String serialized VideoPlaylistExport
* @return VideoPlaylistExport
*/
public static VideoPlaylistData.VideoPlaylistExport restorePlaylistFromString(String serializedVideoPlaylistExport) {
Gson gson = new Gson();
try {
return gson.fromJson(serializedVideoPlaylistExport, VideoPlaylistData.VideoPlaylistExport.class);
} catch (Exception e) {
return null;
}
}
/**
* Serialized VideoPlaylistExport class
*
* @param videoPlaylistExport Playlist to serialize
* @return String serialized VideoPlaylistData.VideoPlaylistExport
*/
public static String playlistToStringStorage(VideoPlaylistData.VideoPlaylistExport videoPlaylistExport) {
Gson gson = new Gson();
try {
return gson.toJson(videoPlaylistExport);
} catch (Exception e) {
return null;
}
}
/**
* Manage intent for opening a tubelab file allowing to import a whole playlist and store it in db
*
* @param activity Activity
* @param intent Intent
*/
public static void manageIntentUrl(Activity activity, Intent intent) {
if (intent.getData() != null) {
String url = intent.getData().toString();
if (url.endsWith(".json")) {
File file = new File(url);
StringBuilder text = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
text.append(line);
text.append('\n');
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
if (text.length() > 20) {
new Thread(() -> {
VideoPlaylistData.VideoPlaylistExport videoPlaylistExport = PlaylistExportHelper.restorePlaylistFromString(text.toString());
if (videoPlaylistExport != null) {
SQLiteDatabase db = Sqlite.getInstance(activity.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
new ManagePlaylistsDAO(activity, db).insertPlaylist(videoPlaylistExport);
}
activity.runOnUiThread(() -> {
Intent intentPlaylist = new Intent(activity, PlaylistsActivity.class);
activity.startActivity(intentPlaylist);
});
}).start();
}
}
}
}
}

View File

@ -0,0 +1,344 @@
package app.fedilab.fedilabtube.sqlite;
/* Copyright 2020 Thomas Schneider
*
* This file is a part of TubeLab
*
* 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.
*
* TubeLab 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 TubeLab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.fedilabtube.client.data.PlaylistData;
import app.fedilab.fedilabtube.client.data.VideoData;
import app.fedilab.fedilabtube.client.data.VideoPlaylistData;
@SuppressWarnings("UnusedReturnValue")
public class ManagePlaylistsDAO {
private final SQLiteDatabase db;
public Context context;
public ManagePlaylistsDAO(Context context, SQLiteDatabase db) {
//Creation of the DB with tables
this.context = context;
this.db = db;
}
/**
* Unserialized Video
*
* @param serializedVideo String serialized Video
* @return Video
*/
public static VideoData.Video restoreVideoFromString(String serializedVideo) {
Gson gson = new Gson();
try {
return gson.fromJson(serializedVideo, VideoData.Video.class);
} catch (Exception e) {
return null;
}
}
/**
* Serialized Video class
*
* @param video Video to serialize
* @return String serialized video
*/
public static String videoToStringStorage(VideoData.Video video) {
Gson gson = new Gson();
try {
return gson.toJson(video);
} catch (Exception e) {
return null;
}
}
/**
* Unserialized Playlist
*
* @param serializedPlaylist String serialized Playlist
* @return Playlist
*/
public static PlaylistData.Playlist restorePlaylistFromString(String serializedPlaylist) {
Gson gson = new Gson();
try {
return gson.fromJson(serializedPlaylist, PlaylistData.Playlist.class);
} catch (Exception e) {
return null;
}
}
/**
* Serialized Playlist class
*
* @param playlist Playlist to serialize
* @return String serialized playlist
*/
public static String playlistToStringStorage(PlaylistData.Playlist playlist) {
Gson gson = new Gson();
try {
return gson.toJson(playlist);
} catch (Exception e) {
return null;
}
}
/**
* Insert playlist info in database
*
* @param videoPlaylistExport VideoPlaylistExport
* @return boolean
*/
public boolean insertPlaylist(VideoPlaylistData.VideoPlaylistExport videoPlaylistExport) {
if (videoPlaylistExport.getPlaylist() == null || checkExists(videoPlaylistExport.getPlaylist().getUuid())) {
return true;
}
ContentValues values = new ContentValues();
values.put(Sqlite.COL_ACCT, videoPlaylistExport.getAcct());
values.put(Sqlite.COL_UUID, videoPlaylistExport.getUuid());
values.put(Sqlite.COL_PLAYLIST, playlistToStringStorage(videoPlaylistExport.getPlaylist()));
//Inserts playlist
try {
long playlist_id = db.insertOrThrow(Sqlite.TABLE_LOCAL_PLAYLISTS, null, values);
videoPlaylistExport.setPlaylistDBkey(playlist_id);
for (VideoPlaylistData.VideoPlaylist videoPlaylist : videoPlaylistExport.getVideos()) {
//Insert videos
insertVideos(videoPlaylist.getVideo(), videoPlaylistExport);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Insert videos for playlists in database
*
* @param video Video to insert
* @param playlist VideoPlaylistExport targeted
* @return boolean
*/
private boolean insertVideos(VideoData.Video video, VideoPlaylistData.VideoPlaylistExport playlist) {
if (video == null || playlist == null || checkVideoExists(video.getUuid(), playlist.getUuid())) {
return true;
}
ContentValues values = new ContentValues();
values.put(Sqlite.COL_UUID, video.getUuid());
values.put(Sqlite.COL_PLAYLIST_ID, playlist.getPlaylistDBkey());
values.put(Sqlite.COL_VIDEO_DATA, videoToStringStorage(video));
//Inserts playlist
try {
db.insertOrThrow(Sqlite.TABLE_VIDEOS, null, values);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Check if playlist exists
*
* @param uuid String
* @return int
*/
private boolean checkExists(String uuid) {
try {
Cursor c = db.query(Sqlite.TABLE_LOCAL_PLAYLISTS, null, Sqlite.COL_UUID + " = \"" + uuid + "\"", null, null, null, null, "1");
int count = c.getCount();
c.close();
return count > 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Check if playlist exists
*
* @param videoUuid String
* @param playlistUuid String
* @return int
*/
private boolean checkVideoExists(String videoUuid, String playlistUuid) {
try {
String check_query = "SELECT * FROM " + Sqlite.TABLE_LOCAL_PLAYLISTS + " p INNER JOIN "
+ Sqlite.TABLE_VIDEOS + " v ON p.id = v." + Sqlite.COL_PLAYLIST_ID
+ " WHERE p." + Sqlite.COL_UUID + "=? AND v." + Sqlite.COL_UUID + "=? LIMIT 1";
Cursor c = db.rawQuery(check_query, new String[]{playlistUuid, videoUuid});
int count = c.getCount();
c.close();
return count > 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Remove a playlist with its uuid
*
* @param uuid String uuid of the Playlist
* @return int
*/
public int removePlaylist(String uuid) {
VideoPlaylistData.VideoPlaylistExport videoPlaylistExport = getSinglePlaylists(uuid);
db.delete(Sqlite.TABLE_VIDEOS, Sqlite.COL_PLAYLIST_ID + " = '" + videoPlaylistExport.getPlaylistDBkey() + "'", null);
return db.delete(Sqlite.TABLE_LOCAL_PLAYLISTS, Sqlite.COL_ID + " = '" + videoPlaylistExport.getPlaylistDBkey() + "'", null);
}
/**
* Returns a playlist from it's uid in db
*
* @return VideoPlaylistExport
*/
public VideoPlaylistData.VideoPlaylistExport getSinglePlaylists(String uuid) {
try {
Cursor c = db.query(Sqlite.TABLE_LOCAL_PLAYLISTS, null, Sqlite.COL_UUID + "='" + uuid + "'", null, null, null, Sqlite.COL_ID + " DESC", null);
return cursorToSingleVideoPlaylistExport(c);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Returns all playlists in db
*
* @return List<VideoPlaylistData.VideoPlaylistExport>
*/
public List<VideoPlaylistData.VideoPlaylistExport> getAllPlaylists() {
try {
Cursor c = db.query(Sqlite.TABLE_LOCAL_PLAYLISTS, null, null, null, null, null, Sqlite.COL_ID + " DESC", null);
return cursorToVideoPlaylistExport(c);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Returns all videos in a playlist
*
* @return List<VideoData.VideoExport>
*/
public List<VideoData.VideoExport> getAllVideosInPlaylist(VideoPlaylistData.VideoPlaylistExport videoPlaylistExport) {
try {
Cursor c = db.query(Sqlite.TABLE_VIDEOS, null, Sqlite.COL_PLAYLIST_ID + "='" + videoPlaylistExport.getPlaylistDBkey() + "'", null, null, null, Sqlite.COL_ID + " DESC", null);
return cursorToVideoExport(c);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/***
* Method to hydrate VideoPlaylistExport from database
* @param c Cursor
* @return VideoPlaylistData.VideoPlaylistExport
*/
private VideoPlaylistData.VideoPlaylistExport cursorToSingleVideoPlaylistExport(Cursor c) {
//No element found
if (c.getCount() == 0) {
c.close();
return null;
}
c.moveToFirst();
VideoPlaylistData.VideoPlaylistExport videoPlaylistExport = new VideoPlaylistData.VideoPlaylistExport();
videoPlaylistExport.setAcct(c.getString(c.getColumnIndex(Sqlite.COL_ACCT)));
videoPlaylistExport.setUuid(c.getString(c.getColumnIndex(Sqlite.COL_UUID)));
videoPlaylistExport.setPlaylistDBkey(c.getInt(c.getColumnIndex(Sqlite.COL_ID)));
videoPlaylistExport.setPlaylist(restorePlaylistFromString(c.getString(c.getColumnIndex(Sqlite.COL_PLAYLIST))));
//Close the cursor
c.close();
return videoPlaylistExport;
}
/***
* Method to hydrate VideoPlaylistExport from database
* @param c Cursor
* @return List<VideoPlaylistData.VideoPlaylistExport>
*/
private List<VideoPlaylistData.VideoPlaylistExport> cursorToVideoPlaylistExport(Cursor c) {
//No element found
if (c.getCount() == 0) {
c.close();
return null;
}
List<VideoPlaylistData.VideoPlaylistExport> videoPlaylistExports = new ArrayList<>();
while (c.moveToNext()) {
VideoPlaylistData.VideoPlaylistExport videoPlaylistExport = new VideoPlaylistData.VideoPlaylistExport();
videoPlaylistExport.setAcct(c.getString(c.getColumnIndex(Sqlite.COL_ACCT)));
videoPlaylistExport.setUuid(c.getString(c.getColumnIndex(Sqlite.COL_UUID)));
videoPlaylistExport.setPlaylistDBkey(c.getInt(c.getColumnIndex(Sqlite.COL_ID)));
videoPlaylistExport.setPlaylist(restorePlaylistFromString(c.getString(c.getColumnIndex(Sqlite.COL_PLAYLIST))));
videoPlaylistExports.add(videoPlaylistExport);
}
//Close the cursor
c.close();
return videoPlaylistExports;
}
/***
* Method to hydrate Video from database
* @param c Cursor
* @return List<VideoData.Video>
*/
private List<VideoData.VideoExport> cursorToVideoExport(Cursor c) {
//No element found
if (c.getCount() == 0) {
c.close();
return null;
}
List<VideoData.VideoExport> videoExports = new ArrayList<>();
while (c.moveToNext()) {
VideoData.VideoExport videoExport = new VideoData.VideoExport();
videoExport.setPlaylistDBid(c.getInt(c.getColumnIndex(Sqlite.COL_PLAYLIST_ID)));
videoExport.setUuid(c.getString(c.getColumnIndex(Sqlite.COL_UUID)));
videoExport.setId(c.getInt(c.getColumnIndex(Sqlite.COL_ID)));
videoExport.setVideoData(restoreVideoFromString(c.getString(c.getColumnIndex(Sqlite.COL_VIDEO_DATA))));
videoExports.add(videoExport);
}
//Close the cursor
c.close();
return videoExports;
}
}

View File

@ -21,7 +21,7 @@ import android.database.sqlite.SQLiteOpenHelper;
public class Sqlite extends SQLiteOpenHelper {
public static final int DB_VERSION = 2;
public static final int DB_VERSION = 3;
public static final String DB_NAME = "mastodon_etalab_db";
/***
* List of tables to manage users and data
@ -62,6 +62,11 @@ public class Sqlite extends SQLiteOpenHelper {
static final String COL_USER_INSTANCE = "USER_INSTANCE";
static final String TABLE_BOOKMARKED_INSTANCES = "BOOKMARKED_INSTANCES";
static final String COL_ABOUT = "ABOUT";
static final String TABLE_LOCAL_PLAYLISTS = "LOCAL_PLAYLISTS";
static final String COL_PLAYLIST = "PLAYLIST";
static final String TABLE_VIDEOS = "VIDEOS";
static final String COL_VIDEO_DATA = "VIDEO_DATA";
static final String COL_PLAYLIST_ID = "PLAYLIST_ID";
private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " ("
+ COL_USER_ID + " TEXT, " + COL_USERNAME + " TEXT NOT NULL, " + COL_ACCT + " TEXT NOT NULL, "
+ COL_DISPLAYED_NAME + " TEXT NOT NULL, " + COL_LOCKED + " INTEGER NOT NULL, "
@ -92,6 +97,19 @@ public class Sqlite extends SQLiteOpenHelper {
+ COL_USER_ID + " TEXT NOT NULL, "
+ COL_ABOUT + " TEXT NOT NULL, "
+ COL_USER_INSTANCE + " TEXT NOT NULL)";
private final String CREATE_TABLE_LOCAL_PLAYLISTS = "CREATE TABLE "
+ TABLE_LOCAL_PLAYLISTS + "("
+ COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ COL_ACCT + " TEXT NOT NULL, "
+ COL_UUID + " TEXT NOT NULL, "
+ COL_PLAYLIST + " TEXT NOT NULL)";
private final String CREATE_TABLE_VIDEOS = "CREATE TABLE "
+ TABLE_VIDEOS + "("
+ COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ COL_UUID + " TEXT NOT NULL, "
+ COL_VIDEO_DATA + " TEXT NOT NULL, "
+ COL_PLAYLIST_ID + " INTEGER, "
+ " FOREIGN KEY (" + COL_PLAYLIST_ID + ") REFERENCES " + COL_PLAYLIST + "(" + COL_ID + "));";
public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
@ -111,13 +129,19 @@ public class Sqlite extends SQLiteOpenHelper {
db.execSQL(CREATE_TABLE_USER_ACCOUNT);
db.execSQL(CREATE_TABLE_PEERTUBE_FAVOURITES);
db.execSQL(CREATE_TABLE_STORED_INSTANCES);
db.execSQL(CREATE_TABLE_LOCAL_PLAYLISTS);
db.execSQL(CREATE_TABLE_VIDEOS);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 3:
db.execSQL("DROP TABLE IF EXISTS " + TABLE_VIDEOS);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_LOCAL_PLAYLISTS);
case 2:
db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKED_INSTANCES);
}
}
@ -126,6 +150,9 @@ public class Sqlite extends SQLiteOpenHelper {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_TABLE_STORED_INSTANCES);
case 2:
db.execSQL(CREATE_TABLE_LOCAL_PLAYLISTS);
db.execSQL(CREATE_TABLE_VIDEOS);
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z" />
</vector>

View File

@ -11,4 +11,10 @@
android:icon="@drawable/ic_baseline_edit_24"
android:title="@string/edit"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_export"
android:icon="@drawable/ic_baseline_import_export_24"
android:title="@string/export_list"
android:visible="false"
app:showAsAction="ifRoom" />
</menu>