From 00c8c4fa88a5039e203e3ea93bfdd3b65c21feeb Mon Sep 17 00:00:00 2001 From: tom79 Date: Sun, 26 May 2019 15:56:02 +0200 Subject: [PATCH] Start playlist support --- .../android/activities/BaseMainActivity.java | 6 + .../android/activities/PlaylistsActivity.java | 314 ++++++++++++++++++ .../asynctasks/ManagePlaylistsAsyncTask.java | 105 ++++++ .../fedilab/android/client/APIResponse.java | 10 + .../android/client/Entities/Playlist.java | 204 ++++++++++++ .../fedilab/android/client/PeertubeAPI.java | 82 +++-- .../fragments/DisplayPlaylistsFragment.java | 211 ++++++++++++ .../interfaces/OnPlaylistActionInterface.java | 27 ++ .../main/res/drawable/ic_list_peertube.xml | 9 + .../main/res/layout/activity_playlists.xml | 109 ++++++ .../main/res/layout/fragment_playlists.xml | 77 +++++ .../main/res/menu/activity_main_drawer.xml | 4 + app/src/main/res/values/strings.xml | 1 + 13 files changed, 1116 insertions(+), 43 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java create mode 100644 app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java create mode 100644 app/src/main/java/app/fedilab/android/client/Entities/Playlist.java create mode 100644 app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java create mode 100644 app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java create mode 100644 app/src/main/res/drawable/ic_list_peertube.xml create mode 100644 app/src/main/res/layout/activity_playlists.xml create mode 100644 app/src/main/res/layout/fragment_playlists.xml diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 57c1927a3..36b13551d 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -1675,6 +1675,12 @@ public abstract class BaseMainActivity extends BaseActivity fragmentTag = "LISTS"; fragmentManager.beginTransaction() .replace(R.id.main_app_container, displayListsFragment, fragmentTag).commit(); + }else if(id == R.id.nav_peertube_playlists){ + toot.hide(); + DisplayListsFragment displayListsFragment = new DisplayListsFragment(); + fragmentTag = "PLAYLISTS"; + fragmentManager.beginTransaction() + .replace(R.id.main_app_container, displayListsFragment, fragmentTag).commit(); }else if(id == R.id.nav_filters){ toot.hide(); DisplayFiltersFragment displayFiltersFragment = new DisplayFiltersFragment(); diff --git a/app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java b/app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java new file mode 100644 index 000000000..02317ee16 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java @@ -0,0 +1,314 @@ +/* Copyright 2017 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +package app.fedilab.android.activities; + +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.text.InputFilter; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.RelativeLayout; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.asynctasks.ManageListsAsyncTask; +import app.fedilab.android.asynctasks.RetrieveFeedsAsyncTask; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Peertube; +import app.fedilab.android.client.Entities.Status; +import app.fedilab.android.drawers.PeertubeAdapter; +import app.fedilab.android.drawers.StatusListAdapter; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.interfaces.OnListActionInterface; +import es.dmoral.toasty.Toasty; + + +/** + * Created by Thomas on 26/05/2019. + * Display playlists for Peertube + */ + +public class PlaylistsActivity extends BaseActivity implements OnListActionInterface { + + + private String title, listId; + private RelativeLayout mainLoader, nextElementLoader, textviewNoAction; + private SwipeRefreshLayout swipeRefreshLayout; + private boolean swiped; + private List peertubes; + private String max_id; + private boolean firstLoad; + private boolean flag_loading; + private PeertubeAdapter peertubeAdapter; + LinearLayoutManager mLayoutManager; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + switch (theme){ + case Helper.THEME_LIGHT: + setTheme(R.style.AppTheme_NoActionBar); + break; + case Helper.THEME_DARK: + setTheme(R.style.AppThemeDark_NoActionBar); + break; + case Helper.THEME_BLACK: + setTheme(R.style.AppThemeBlack_NoActionBar); + break; + default: + setTheme(R.style.AppThemeDark_NoActionBar); + } + setContentView(R.layout.activity_playlists); + Toolbar toolbar = findViewById(R.id.toolbar); + if( theme == Helper.THEME_BLACK) + toolbar.setBackgroundColor(ContextCompat.getColor(PlaylistsActivity.this, R.color.black)); + setSupportActionBar(toolbar); + if( getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + peertubes = new ArrayList<>(); + + RecyclerView lv_playlist = findViewById(R.id.lv_playlist); + mainLoader = findViewById(R.id.loader); + nextElementLoader = findViewById(R.id.loading_next_status); + textviewNoAction = findViewById(R.id.no_action); + mainLoader.setVisibility(View.VISIBLE); + swipeRefreshLayout = findViewById(R.id.swipeContainer); + max_id = null; + flag_loading = true; + firstLoad = true; + swiped = false; + + + mainLoader.setVisibility(View.VISIBLE); + nextElementLoader.setVisibility(View.GONE); + boolean isOnWifi = Helper.isOnWIFI(PlaylistsActivity.this); + + peertubeAdapter = new PeertubeAdapter(PlaylistsActivity.this, Helper.getLiveInstance(PlaylistsActivity.this), false, this.peertubes); + + lv_playlist.setAdapter(peertubeAdapter); + mLayoutManager = new LinearLayoutManager(PlaylistsActivity.this); + lv_playlist.setLayoutManager(mLayoutManager); + + Bundle b = getIntent().getExtras(); + if(b != null){ + title = b.getString("title"); + listId = b.getString("id"); + }else{ + Toasty.error(this,getString(R.string.toast_error_search),Toast.LENGTH_LONG).show(); + } + if( getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + setTitle(title); + + + lv_playlist.addOnScrollListener(new RecyclerView.OnScrollListener() { + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) + { + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if(dy > 0){ + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if(firstVisibleItem + visibleItemCount == totalItemCount ) { + if(!flag_loading ) { + flag_loading = true; + new ManageListsAsyncTask(PlaylistsActivity.this,listId, max_id ,null, PlaylistsActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + nextElementLoader.setVisibility(View.VISIBLE); + } + } else { + nextElementLoader.setVisibility(View.GONE); + } + } + } + }); + + + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + max_id = null; + firstLoad = true; + flag_loading = true; + swiped = true; + MainActivity.countNewStatus = 0; + new ManageListsAsyncTask(PlaylistsActivity.this,listId, null ,null, PlaylistsActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + switch (theme){ + case Helper.THEME_LIGHT: + swipeRefreshLayout.setColorSchemeResources(R.color.mastodonC4, + R.color.mastodonC2, + R.color.mastodonC3); + swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ContextCompat.getColor(PlaylistsActivity.this, R.color.white)); + break; + case Helper.THEME_DARK: + swipeRefreshLayout.setColorSchemeResources(R.color.mastodonC4__, + R.color.mastodonC4, + R.color.mastodonC4); + swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ContextCompat.getColor(PlaylistsActivity.this, R.color.mastodonC1_)); + break; + case Helper.THEME_BLACK: + swipeRefreshLayout.setColorSchemeResources(R.color.dark_icon, + R.color.mastodonC2, + R.color.mastodonC3); + swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ContextCompat.getColor(PlaylistsActivity.this, R.color.black_3)); + break; + } + + new ManageListsAsyncTask(PlaylistsActivity.this,listId, null ,null, PlaylistsActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main_list, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + case R.id.action_add_user: + Intent intent = new Intent(PlaylistsActivity.this, ManageAccountsInListActivity.class); + intent.putExtra("title", title); + intent.putExtra("id", listId); + startActivity(intent); + return true; + case R.id.action_edit_list: + int style; + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + if (theme == Helper.THEME_DARK) { + style = R.style.DialogDark; + } else if (theme == Helper.THEME_BLACK){ + style = R.style.DialogBlack; + }else { + style = R.style.Dialog; + } + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(PlaylistsActivity.this, style); + LayoutInflater inflater = getLayoutInflater(); + @SuppressLint("InflateParams") View dialogView = inflater.inflate(R.layout.add_list, null); + dialogBuilder.setView(dialogView); + final EditText editText = dialogView.findViewById(R.id.add_list); + editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)}); + editText.setText(title); + dialogBuilder.setPositiveButton(R.string.validate, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + if( editText.getText() != null && editText.getText().toString().trim().length() > 0 ) + new ManageListsAsyncTask(PlaylistsActivity.this, ManageListsAsyncTask.action.UPDATE_LIST, null, listId, editText.getText().toString(), editText.getText().toString().trim(), PlaylistsActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + dialog.dismiss(); + } + }); + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.setTitle(getString(R.string.action_lists_create)); + alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialogInterface) { + //Hide keyboard + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + } + }); + if( alertDialog.getWindow() != null ) + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + alertDialog.show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + + @Override + public void onActionDone(ManageListsAsyncTask.action actionType, APIResponse apiResponse, int statusCode) { + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + mainLoader.setVisibility(View.GONE); + nextElementLoader.setVisibility(View.GONE); + //Discards 404 - error which can often happen due to toots which have been deleted + if (apiResponse.getError() != null) { + if ( !apiResponse.getError().getError().startsWith("404 -")) + Toasty.error(PlaylistsActivity.this, apiResponse.getError().getError(), Toast.LENGTH_LONG).show(); + swipeRefreshLayout.setRefreshing(false); + swiped = false; + flag_loading = false; + return; + } + if( actionType == ManageListsAsyncTask.action.GET_LIST_TIMELINE) { + + int previousPosition = this.lv_playlist.size(); + List statuses = apiResponse.getStatuses(); + max_id = apiResponse.getMax_id(); + flag_loading = (max_id == null); + if (!swiped && firstLoad && (statuses == null || statuses.size() == 0)) + textviewNoAction.setVisibility(View.VISIBLE); + else + textviewNoAction.setVisibility(View.GONE); + + if (swiped) { + if (previousPosition > 0) { + for (int i = 0; i < previousPosition; i++) { + this.statuses.remove(0); + } + statusListAdapter.notifyItemRangeRemoved(0, previousPosition); + } + swiped = false; + } + if (statuses != null && statuses.size() > 0) { + this.statuses.addAll(statuses); + statusListAdapter.notifyItemRangeInserted(previousPosition, statuses.size()); + } + swipeRefreshLayout.setRefreshing(false); + firstLoad = false; + }else if(actionType == ManageListsAsyncTask.action.UPDATE_LIST) { + + } + } +} diff --git a/app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java new file mode 100644 index 000000000..d8ca43977 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java @@ -0,0 +1,105 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +package app.fedilab.android.asynctasks; + +import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; + +import java.lang.ref.WeakReference; + +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.client.Entities.Playlist; +import app.fedilab.android.client.PeertubeAPI; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.interfaces.OnPlaylistActionInterface; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + + +/** + * Created by Thomas on 26/05/2019. + * Async works to manage Playlists + */ + +public class ManagePlaylistsAsyncTask extends AsyncTask { + + public enum action{ + GET_PLAYLIST, + GET_LIST_TIMELINE, + GET_LIST_ACCOUNT, + CREATE_PLAYLIST, + DELETE_LIST, + UPDATE_LIST, + ADD_USERS, + DELETE_USERS, + SEARCH_USER + } + + private OnPlaylistActionInterface listener; + private APIResponse apiResponse; + private int statusCode; + private action apiAction; + private WeakReference contextReference; + private String max_id, since_id; + + public ManagePlaylistsAsyncTask(Context context, action apiAction, Playlist playlist, String max_id, String since_id, OnPlaylistActionInterface onPlaylistActionInterface){ + contextReference = new WeakReference<>(context); + this.listener = onPlaylistActionInterface; + this.apiAction = apiAction; + this.max_id = max_id; + this.since_id = since_id; + + } + + + @Override + protected Void doInBackground(Void... params) { + SharedPreferences sharedpreferences = contextReference.get().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(contextReference.get())); + SQLiteDatabase db = Sqlite.getInstance(contextReference.get(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + Account account = new AccountDAO(contextReference.get(), db).getAccountByUserIDInstance(userId, instance); + if (apiAction == action.GET_PLAYLIST) { + apiResponse = new PeertubeAPI(contextReference.get()).getPlayists(account.getUsername()); + }else if(apiAction == action.GET_LIST_TIMELINE){ + apiResponse = new API(contextReference.get()).getListTimeline(this.listId, this.max_id, this.since_id, this.limit); + }else if(apiAction == action.GET_LIST_ACCOUNT){ + apiResponse = new API(contextReference.get()).getAccountsInList(this.listId,0); + }else if( apiAction == action.CREATE_PLAYLIST){ + apiResponse = new API(contextReference.get()).createPlaylist(this.title); + }else if(apiAction == action.DELETE_LIST){ + statusCode = new API(contextReference.get()).deleteList(this.listId); + }else if(apiAction == action.UPDATE_LIST){ + apiResponse = new API(contextReference.get()).updateList(this.listId, this.title); + }else if(apiAction == action.ADD_USERS){ + apiResponse = new API(contextReference.get()).addAccountToList(this.listId, this.accountsId); + }else if(apiAction == action.DELETE_USERS){ + statusCode = new API(contextReference.get()).deleteAccountFromList(this.listId, this.accountsId); + }else if( apiAction == action.SEARCH_USER){ + apiResponse = new API(contextReference.get()).searchAccounts(this.search, 20, true); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onActionDone(this.apiAction, apiResponse, statusCode); + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/APIResponse.java b/app/src/main/java/app/fedilab/android/client/APIResponse.java index 54cdd954e..965e5bea3 100644 --- a/app/src/main/java/app/fedilab/android/client/APIResponse.java +++ b/app/src/main/java/app/fedilab/android/client/APIResponse.java @@ -28,6 +28,7 @@ import app.fedilab.android.client.Entities.Instance; import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.Entities.Peertube; import app.fedilab.android.client.Entities.PeertubeNotification; +import app.fedilab.android.client.Entities.Playlist; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Results; import app.fedilab.android.client.Entities.Status; @@ -51,6 +52,7 @@ public class APIResponse { private List peertubes = null; private List peertubeNotifications = null; private List filters = null; + private List playlists = null; private List domains = null; private List lists = null; private List emojis = null; @@ -219,4 +221,12 @@ public class APIResponse { public void setFetchmore(boolean fetchmore) { this.fetchmore = fetchmore; } + + public List getPlaylists() { + return playlists; + } + + public void setPlaylists(List playlists) { + this.playlists = playlists; + } } diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Playlist.java b/app/src/main/java/app/fedilab/android/client/Entities/Playlist.java new file mode 100644 index 000000000..362409afe --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/Entities/Playlist.java @@ -0,0 +1,204 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +package app.fedilab.android.client.Entities; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Date; +import java.util.HashMap; + + +/** + * Created by Thomas on 26/05/2019. + * Manage List + */ + +public class Playlist implements Parcelable { + + private String id; + private String uuid; + private String displayName; + private String description; + private String videoChannelId; + private Date createdAt; + private boolean isLocal; + private Account ownerAccount; + private HashMap privacy; + private String thumbnailPath; + private HashMap type; + private Date updatedAt; + private int videosLength; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getVideoChannelId() { + return videoChannelId; + } + + public void setVideoChannelId(String videoChannelId) { + this.videoChannelId = videoChannelId; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public boolean isLocal() { + return isLocal; + } + + public void setLocal(boolean local) { + isLocal = local; + } + + public Account getOwnerAccount() { + return ownerAccount; + } + + public void setOwnerAccount(Account ownerAccount) { + this.ownerAccount = ownerAccount; + } + + public HashMap getPrivacy() { + return privacy; + } + + public void setPrivacy(HashMap privacy) { + this.privacy = privacy; + } + + public String getThumbnailPath() { + return thumbnailPath; + } + + public void setThumbnailPath(String thumbnailPath) { + this.thumbnailPath = thumbnailPath; + } + + public HashMap getType() { + return type; + } + + public void setType(HashMap type) { + this.type = type; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public int getVideosLength() { + return videosLength; + } + + public void setVideosLength(int videosLength) { + this.videosLength = videosLength; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.id); + dest.writeString(this.uuid); + dest.writeString(this.displayName); + dest.writeString(this.description); + dest.writeString(this.videoChannelId); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeByte(this.isLocal ? (byte) 1 : (byte) 0); + dest.writeParcelable(this.ownerAccount, flags); + dest.writeSerializable(this.privacy); + dest.writeString(this.thumbnailPath); + dest.writeSerializable(this.type); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeInt(this.videosLength); + } + + public Playlist() { + } + + protected Playlist(Parcel in) { + this.id = in.readString(); + this.uuid = in.readString(); + this.displayName = in.readString(); + this.description = in.readString(); + this.videoChannelId = in.readString(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + this.isLocal = in.readByte() != 0; + this.ownerAccount = in.readParcelable(Account.class.getClassLoader()); + this.privacy = (HashMap) in.readSerializable(); + this.thumbnailPath = in.readString(); + this.type = (HashMap) in.readSerializable(); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.videosLength = in.readInt(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Playlist createFromParcel(Parcel source) { + return new Playlist(source); + } + + @Override + public Playlist[] newArray(int size) { + return new Playlist[size]; + } + }; +} diff --git a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java index 5d19f5075..5525e024f 100644 --- a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java +++ b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java @@ -55,6 +55,7 @@ import app.fedilab.android.client.Entities.PeertubeComment; import app.fedilab.android.client.Entities.PeertubeInformation; import app.fedilab.android.client.Entities.PeertubeNotification; import app.fedilab.android.client.Entities.PeertubeVideoNotification; +import app.fedilab.android.client.Entities.Playlist; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Results; import app.fedilab.android.client.Entities.Status; @@ -1489,12 +1490,12 @@ public class PeertubeAPI { * Get lists for the user * @return APIResponse */ - public APIResponse getLists(){ + public APIResponse getPlayists(String username){ - List lists = new ArrayList<>(); + List playlists = new ArrayList<>(); try { - String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/lists"), 60, null, prefKeyOauthTokenT); - lists = parseLists(new JSONArray(response)); + String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl(String.format("/accounts/%s/video-playlists", username)), 60, null, prefKeyOauthTokenT); + playlists = parsePlaylists(context, new JSONArray(response)); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); } catch (NoSuchAlgorithmException e) { @@ -1506,37 +1507,11 @@ public class PeertubeAPI { } catch (JSONException e) { e.printStackTrace(); } - apiResponse.setLists(lists); + apiResponse.setPlaylists(playlists); return apiResponse; } - /** - * Get lists for a user by its id - * @return APIResponse - */ - @SuppressWarnings("unused") - public APIResponse getLists(String userId){ - List lists = new ArrayList<>(); - app.fedilab.android.client.Entities.List list; - try { - String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl(String.format("/accounts/%s/lists", userId)), 60, null, prefKeyOauthTokenT); - list = parseList(new JSONObject(response)); - lists.add(list); - } catch (HttpsConnection.HttpsConnectionException e) { - setError(e.getStatusCode(), e); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (KeyManagementException e) { - e.printStackTrace(); - } catch (JSONException e) { - e.printStackTrace(); - } - apiResponse.setLists(lists); - return apiResponse; - } /** * Parse json response for several howto @@ -2062,24 +2037,24 @@ public class PeertubeAPI { /** - * Parse Lists + * Parse Playlists * @param jsonArray JSONArray - * @return List of lists + * @return List of lists */ - private List parseLists(JSONArray jsonArray){ - List lists = new ArrayList<>(); + private List parsePlaylists(Context context, JSONArray jsonArray){ + List playlists = new ArrayList<>(); try { int i = 0; while (i < jsonArray.length() ) { JSONObject resobj = jsonArray.getJSONObject(i); - app.fedilab.android.client.Entities.List list = parseList(resobj); - lists.add(list); + Playlist playlist = parsePlaylist(context, resobj); + playlists.add(playlist); i++; } } catch (JSONException e) { setDefaultError(e); } - return lists; + return playlists; } @@ -2088,13 +2063,34 @@ public class PeertubeAPI { * @param resobj JSONObject * @return Emojis */ - private static app.fedilab.android.client.Entities.List parseList(JSONObject resobj){ - app.fedilab.android.client.Entities.List list = new app.fedilab.android.client.Entities.List(); + private static Playlist parsePlaylist(Context context, JSONObject resobj){ + Playlist playlist = new Playlist(); try { - list.setId(resobj.get("id").toString()); - list.setTitle(resobj.get("title").toString()); + playlist.setId(resobj.getString("id")); + playlist.setUuid(resobj.getString("uuid")); + playlist.setCreatedAt(Helper.stringToDate(context, resobj.getString("createdAt"))); + playlist.setDescription(resobj.getString("description")); + playlist.setDisplayName(resobj.getString("displayName")); + playlist.setLocal(resobj.getBoolean("isLocal")); + playlist.setVideoChannelId(resobj.getString("videoChannel")); + playlist.setThumbnailPath(resobj.getString("thumbnailPath")); + playlist.setOwnerAccount(parseAccountResponsePeertube(context, resobj.getJSONObject("ownerAccount"))); + playlist.setVideosLength(resobj.getInt("videosLength")); + try { + LinkedHashMap type = new LinkedHashMap<>(); + LinkedHashMap privacy = new LinkedHashMap<>(); + privacy.put(resobj.getJSONObject("privacy").getInt("id"), resobj.getJSONObject("privacy").get("label").toString()); + type.put(resobj.getJSONObject("type").getInt("id"), resobj.getJSONObject("type").get("label").toString()); + playlist.setType(type); + playlist.setPrivacy(privacy); + }catch (Exception ignored){} + + + try{ + playlist.setUpdatedAt(Helper.stringToDate(context, resobj.getString("updatedAt"))); + }catch (Exception ignored){} }catch (Exception ignored){} - return list; + return playlist; } private List parseAccountResponsePeertube(Context context, String instance, JSONArray jsonArray){ diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java new file mode 100644 index 000000000..d44c6d467 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java @@ -0,0 +1,211 @@ +package app.fedilab.android.fragments; +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.text.InputFilter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.ListActivity; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.asynctasks.ManageListsAsyncTask; +import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.drawers.ListAdapter; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.interfaces.OnListActionInterface; +import app.fedilab.android.interfaces.OnPlaylistActionInterface; +import es.dmoral.toasty.Toasty; + + +/** + * Created by Thomas on 26/05/2019. + * Fragment to display Playlists + */ +public class DisplayPlaylistsFragment extends Fragment implements OnPlaylistActionInterface { + + + private Context context; + private AsyncTask asyncTask; + private List lists; + private RelativeLayout mainLoader; + private FloatingActionButton add_new; + private ListAdapter listAdapter; + private RelativeLayout textviewNoAction; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + //View for fragment is the same that fragment accounts + View rootView = inflater.inflate(R.layout.fragment_playlists, container, false); + + context = getContext(); + lists = new ArrayList<>(); + + + ListView lv_playlist = rootView.findViewById(R.id.lv_playlist); + textviewNoAction = rootView.findViewById(R.id.no_action); + mainLoader = rootView.findViewById(R.id.loader); + RelativeLayout nextElementLoader = rootView.findViewById(R.id.loading_next_items); + mainLoader.setVisibility(View.VISIBLE); + nextElementLoader.setVisibility(View.GONE); + lists = new ArrayList<>(); + listAdapter = new ListAdapter(context, lists, textviewNoAction); + lv_playlist.setAdapter(listAdapter); + asyncTask = new ManagePlaylistsAsyncTask(context, ManagePlaylistsAsyncTask.action.GET_PLAYLIST, null, null, null,DisplayPlaylistsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + try { + add_new = ((MainActivity) context).findViewById(R.id.add_new); + }catch (Exception ignored){} + if( add_new != null) + add_new.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + int style; + if (theme == Helper.THEME_DARK) { + style = R.style.DialogDark; + } else if (theme == Helper.THEME_BLACK){ + style = R.style.DialogBlack; + }else { + style = R.style.Dialog; + } + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, style); + LayoutInflater inflater = ((Activity)context).getLayoutInflater(); + @SuppressLint("InflateParams") View dialogView = inflater.inflate(R.layout.add_list, null); + dialogBuilder.setView(dialogView); + final EditText editText = dialogView.findViewById(R.id.add_list); + editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(255)}); + dialogBuilder.setPositiveButton(R.string.validate, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + if( editText.getText() != null && editText.getText().toString().trim().length() > 0 ) + new ManagePlaylistsAsyncTask(context, ManagePlaylistsAsyncTask.action.CREATE_PLAYLIST, null, null, null, editText.getText().toString().trim(), DisplayPlaylistsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + dialog.dismiss(); + add_new.setEnabled(false); + } + }); + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + + + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.setTitle(getString(R.string.action_lists_create)); + alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialogInterface) { + //Hide keyboard + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + } + }); + if( alertDialog.getWindow() != null ) + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + alertDialog.show(); + } + }); + return rootView; + } + + + + @Override + public void onCreate(Bundle saveInstance) + { + super.onCreate(saveInstance); + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + this.context = context; + } + + public void onDestroy() { + super.onDestroy(); + if(asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING) + asyncTask.cancel(true); + } + + + + + @Override + public void onActionDone(ManagePlaylistsAsyncTask.action actionType, APIResponse apiResponse, int statusCode) { + mainLoader.setVisibility(View.GONE); + add_new.setEnabled(true); + if( apiResponse.getError() != null){ + Toasty.error(context, apiResponse.getError().getError(),Toast.LENGTH_LONG).show(); + return; + } + if( actionType == ManagePlaylistsAsyncTask.action.GET_PLAYLIST) { + if (apiResponse.getLists() != null && apiResponse.getLists().size() > 0) { + this.lists.addAll(apiResponse.getLists()); + listAdapter.notifyDataSetChanged(); + textviewNoAction.setVisibility(View.GONE); + } else { + textviewNoAction.setVisibility(View.VISIBLE); + } + }else if( actionType == ManagePlaylistsAsyncTask.action.CREATE_PLAYLIST){ + if (apiResponse.getLists() != null && apiResponse.getLists().size() > 0) { + String listId = apiResponse.getLists().get(0).getId(); + String title = apiResponse.getLists().get(0).getTitle(); + Intent intent = new Intent(context, ListActivity.class); + Bundle b = new Bundle(); + b.putString("id", listId); + b.putString("title", title); + intent.putExtras(b); + context.startActivity(intent); + this.lists.add(0, apiResponse.getLists().get(0)); + listAdapter.notifyDataSetChanged(); + textviewNoAction.setVisibility(View.GONE); + }else{ + Toasty.error(context, apiResponse.getError().getError(),Toast.LENGTH_LONG).show(); + } + }else if( actionType == ManagePlaylistsAsyncTask.action.DELETE_LIST){ + if( this.lists.size() == 0) + textviewNoAction.setVisibility(View.VISIBLE); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java b/app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java new file mode 100644 index 000000000..4a2843d30 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java @@ -0,0 +1,27 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +package app.fedilab.android.interfaces; + + +import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask; +import app.fedilab.android.client.APIResponse; + +/** + * Created by Thomas on 26/05/2019 + * Interface when actions have been done with playlists + */ +public interface OnPlaylistActionInterface { + void onActionDone(ManagePlaylistsAsyncTask.action actionType, APIResponse apiResponse, int statusCode); +} diff --git a/app/src/main/res/drawable/ic_list_peertube.xml b/app/src/main/res/drawable/ic_list_peertube.xml new file mode 100644 index 000000000..4c2fb8834 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_peertube.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_playlists.xml b/app/src/main/res/layout/activity_playlists.xml new file mode 100644 index 000000000..20acecefb --- /dev/null +++ b/app/src/main/res/layout/activity_playlists.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playlists.xml b/app/src/main/res/layout/fragment_playlists.xml new file mode 100644 index 000000000..218ed1341 --- /dev/null +++ b/app/src/main/res/layout/fragment_playlists.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index 08a65d467..f85060780 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -74,6 +74,10 @@ android:id="@+id/nav_peertube_history" android:icon="@drawable/ic_history_black" android:title="@string/history" /> + Manage tags Remember the position in Home timeline History + Playlists %d vote