Merge branch 'develop'

This commit is contained in:
tom79 2019-05-29 19:29:36 +02:00
commit 4f5f18dc5a
73 changed files with 2746 additions and 456 deletions

View File

@ -6,8 +6,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode 273
versionName "2.1.0"
versionCode 274
versionName "2.2.0"
multiDexEnabled true
renderscriptTargetApi 28 as int
renderscriptSupportModeEnabled true

View File

@ -157,6 +157,10 @@
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize"
/>
<activity android:name="app.fedilab.android.activities.PlaylistsActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize"
/>
<activity android:name="app.fedilab.android.activities.WebviewActivity"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize"

View File

@ -93,6 +93,7 @@ import app.fedilab.android.fragments.DisplayListsFragment;
import app.fedilab.android.fragments.DisplayMutedInstanceFragment;
import app.fedilab.android.fragments.DisplayNotificationsFragment;
import app.fedilab.android.fragments.DisplayPeertubeNotificationsFragment;
import app.fedilab.android.fragments.DisplayPlaylistsFragment;
import app.fedilab.android.fragments.DisplayStatusFragment;
import app.fedilab.android.fragments.SettingsPeertubeFragment;
import app.fedilab.android.fragments.TabLayoutNotificationsFragment;
@ -1540,7 +1541,7 @@ public abstract class BaseMainActivity extends BaseActivity
}else{
delete_all.show();
}
if( id != R.id.nav_list && id != R.id.nav_filters){
if( id != R.id.nav_list && id != R.id.nav_filters && id != R.id.nav_peertube_playlists){
add_new.hide();
}else{
add_new.show();
@ -1590,6 +1591,21 @@ public abstract class BaseMainActivity extends BaseActivity
fragmentTag = "MY_VIDEOS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, fragment, fragmentTag).commit();
} else if (id == R.id.nav_peertube_history) {
bundle = new Bundle();
DisplayStatusFragment fragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.PEERTUBE_HISTORY);
bundle.putString("instanceType","PEERTUBE");
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE);
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(getApplicationContext()));
Account account = new AccountDAO(getApplicationContext(), db).getUniqAccount(userId, instance);
bundle.putString("targetedid",account.getUsername());
fragment.setArguments(bundle);
fragmentTag = "MY_HISTORY";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, fragment, fragmentTag).commit();
} else if (id == R.id.nav_blocked || id == R.id.nav_pixelfed_blocked) {
toot.hide();
accountsFragment = new DisplayAccountsFragment();
@ -1642,12 +1658,6 @@ public abstract class BaseMainActivity extends BaseActivity
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, displayFavoritesPeertubeFragment, fragmentTag).commit();
toot.hide();
}else if (id == R.id.nav_peertube_fav) {
DisplayFavoritesPeertubeFragment displayFavoritesPeertubeFragment = new DisplayFavoritesPeertubeFragment();
fragmentTag = "BOOKMARKS_PEERTUBE";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, displayFavoritesPeertubeFragment, fragmentTag).commit();
toot.hide();
}else if( id == R.id.nav_follow_request){
toot.hide();
DisplayFollowRequestSentFragment followRequestSentFragment = new DisplayFollowRequestSentFragment();
@ -1660,6 +1670,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();
DisplayPlaylistsFragment displayPlaylistsFragment = new DisplayPlaylistsFragment();
fragmentTag = "PLAYLISTS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, displayPlaylistsFragment, fragmentTag).commit();
}else if(id == R.id.nav_filters){
toot.hide();
DisplayFiltersFragment displayFiltersFragment = new DisplayFiltersFragment();

View File

@ -32,10 +32,12 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Html;
@ -72,25 +74,32 @@ import com.google.android.exoplayer2.util.Util;
import java.lang.ref.WeakReference;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.net.ssl.HttpsURLConnection;
import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask;
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.Error;
import app.fedilab.android.client.Entities.Peertube;
import app.fedilab.android.client.Entities.Playlist;
import app.fedilab.android.client.Entities.Status;
import app.fedilab.android.client.TLSSocketFactory;
import app.fedilab.android.drawers.StatusListAdapter;
import app.fedilab.android.fragments.DisplayStatusFragment;
import app.fedilab.android.helper.CrossActions;
import app.fedilab.android.helper.FullScreenMediaController;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.interfaces.OnPlaylistActionInterface;
import app.fedilab.android.sqlite.AccountDAO;
import app.fedilab.android.sqlite.InstancesDAO;
import app.fedilab.android.sqlite.PeertubeFavoritesDAO;
import app.fedilab.android.sqlite.Sqlite;
import app.fedilab.android.sqlite.TimelinesDAO;
import app.fedilab.android.webview.MastalabWebChromeClient;
import app.fedilab.android.webview.MastalabWebViewClient;
import es.dmoral.toasty.Toasty;
@ -103,6 +112,10 @@ import app.fedilab.android.asynctasks.UpdateAccountInfoAsyncTask;
import app.fedilab.android.interfaces.OnPostActionInterface;
import app.fedilab.android.interfaces.OnRetrievePeertubeInterface;
import static app.fedilab.android.activities.BaseMainActivity.mPageReferenceMap;
import static app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask.action.GET_LIST_VIDEOS;
import static app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask.action.GET_PLAYLIST;
import static app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask.action.GET_PLAYLIST_FOR_VIDEO;
import static app.fedilab.android.helper.Helper.changeDrawableColor;
@ -111,12 +124,12 @@ import static app.fedilab.android.helper.Helper.changeDrawableColor;
* Peertube activity
*/
public class PeertubeActivity extends BaseActivity implements OnRetrievePeertubeInterface, OnPostActionInterface {
public class PeertubeActivity extends BaseActivity implements OnRetrievePeertubeInterface, OnPostActionInterface, OnPlaylistActionInterface {
private String peertubeInstance, videoId;
private FullScreenMediaController.fullscreen fullscreen;
private RelativeLayout loader;
private TextView peertube_view_count, peertube_bookmark, peertube_like_count, peertube_dislike_count, peertube_share, peertube_download, peertube_description, peertube_title;
private TextView peertube_view_count, peertube_playlist, peertube_bookmark, peertube_like_count, peertube_dislike_count, peertube_share, peertube_download, peertube_description, peertube_title;
private ScrollView peertube_information_container;
private int stopPosition;
private Peertube peertube;
@ -135,7 +148,8 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
private TextView add_comment_read;
private EditText add_comment_write;
private String instance;
private List<String> playlistForVideo;
private List<Playlist> playlists;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -156,6 +170,7 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
setTheme(R.style.AppThemeDark);
}
fullScreenMode = false;
playlistForVideo = new ArrayList<>();
setContentView(R.layout.activity_peertube);
loader = findViewById(R.id.loader);
peertube_view_count = findViewById(R.id.peertube_view_count);
@ -173,6 +188,7 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
my_pp = findViewById(R.id.my_pp);
add_comment_read = findViewById(R.id.add_comment_read);
add_comment_write = findViewById(R.id.add_comment_write);
peertube_playlist = findViewById(R.id.peertube_playlist);
send = findViewById(R.id.send);
add_comment_read.setOnClickListener(new View.OnClickListener() {
@Override
@ -189,6 +205,10 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
if( MainActivity.social != UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE){
write_comment_container.setVisibility(View.GONE);
}
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE){
peertube_playlist.setVisibility(View.VISIBLE);
peertube_bookmark.setVisibility(View.GONE);
}
send.setOnClickListener(new View.OnClickListener() {
@Override
@ -204,6 +224,7 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
}
}
});
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(getApplicationContext()));
@ -296,7 +317,9 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
initFullscreenButton();
}
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE){
new ManagePlaylistsAsyncTask(PeertubeActivity.this,GET_PLAYLIST, null, null, null , PeertubeActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
new RetrievePeertubeSingleAsyncTask(PeertubeActivity.this, peertubeInstance, videoId, PeertubeActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ -475,6 +498,66 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
}
peertube = apiResponse.getPeertubes().get(0);
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE){
new ManagePlaylistsAsyncTask(PeertubeActivity.this,GET_PLAYLIST_FOR_VIDEO, null, peertube.getId(), null , PeertubeActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
peertube_playlist.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( playlists != null && peertube.getId() != null) {
PopupMenu popup = new PopupMenu(PeertubeActivity.this, peertube_playlist);
for(Playlist playlist: playlists){
String title = null;
for (String id : playlistForVideo) {
if (playlist.getId().equals(id)) {
title = "" + playlist.getDisplayName();
break;
}
}
if( title == null){
title = playlist.getDisplayName();
}
MenuItem item = popup.getMenu().add(0, 0, Menu.NONE, title);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
item.setActionView(new View(getApplicationContext()));
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return false;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
return false;
}
});
if(playlistForVideo.contains(playlist.getId())){
item.setTitle(playlist.getDisplayName());
new ManagePlaylistsAsyncTask(PeertubeActivity.this,ManagePlaylistsAsyncTask.action.DELETE_VIDEOS, playlist, peertube.getId(), null , PeertubeActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
playlistForVideo.remove(playlist.getId());
}else{
item.setTitle( "" + playlist.getDisplayName());
new ManagePlaylistsAsyncTask(PeertubeActivity.this,ManagePlaylistsAsyncTask.action.ADD_VIDEOS, playlist, peertube.getId(), null , PeertubeActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
playlistForVideo.add(playlist.getId());
}
return false;
}
});
popup.show();
}
}
}
});
if( peertube.isCommentsEnabled()) {
new RetrievePeertubeSingleCommentsAsyncTask(PeertubeActivity.this, peertubeInstance, videoId, PeertubeActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE)
@ -870,4 +953,14 @@ public class PeertubeActivity extends BaseActivity implements OnRetrievePeertube
peertube_like_count.setCompoundDrawablesWithIntrinsicBounds( null, thumbUp, null, null);
peertube_dislike_count.setCompoundDrawablesWithIntrinsicBounds( null, thumbDown, null, null);
}
@Override
public void onActionDone(ManagePlaylistsAsyncTask.action actionType, APIResponse apiResponse, int statusCode) {
if( actionType == GET_PLAYLIST_FOR_VIDEO && apiResponse != null) {
playlistForVideo = apiResponse.getPlaylistForVideos();
}else if( actionType == GET_PLAYLIST && apiResponse != null){
playlists = apiResponse.getPlaylists();
}
}
}

View File

@ -0,0 +1,272 @@
/* 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 <http://www.gnu.org/licenses>. */
package app.fedilab.android.activities;
import android.annotation.SuppressLint;
import android.content.Context;
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.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Peertube;
import app.fedilab.android.client.Entities.Playlist;
import app.fedilab.android.drawers.PeertubeAdapter;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.interfaces.OnPlaylistActionInterface;
import es.dmoral.toasty.Toasty;
import static app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask.action.GET_LIST_VIDEOS;
/**
* Created by Thomas on 26/05/2019.
* Display playlists for Peertube
*/
public class PlaylistsActivity extends BaseActivity implements OnPlaylistActionInterface {
private RelativeLayout mainLoader, nextElementLoader, textviewNoAction;
private SwipeRefreshLayout swipeRefreshLayout;
private boolean swiped;
private List<Peertube> peertubes;
private String max_id;
private Playlist playlist;
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);
}
if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ActionBar actionBar = getSupportActionBar();
if( actionBar != null ) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
@SuppressLint("InflateParams") View view = inflater.inflate(R.layout.simple_bar, null);
actionBar.setCustomView(view, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
ImageView toolbar_close = actionBar.getCustomView().findViewById(R.id.toolbar_close);
TextView toolbar_title = actionBar.getCustomView().findViewById(R.id.toolbar_title);
toolbar_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar_title.setText(R.string.upload_video);
if (theme == Helper.THEME_LIGHT){
Toolbar toolbar = actionBar.getCustomView().findViewById(R.id.toolbar);
Helper.colorizeToolbar(toolbar, R.color.black, PlaylistsActivity.this);
}
}
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){
playlist = b.getParcelable("playlist");
}else{
Toasty.error(this,getString(R.string.toast_error_search),Toast.LENGTH_LONG).show();
return;
}
if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTitle(playlist.getDisplayName());
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 ManagePlaylistsAsyncTask(PlaylistsActivity.this,GET_LIST_VIDEOS, playlist, null, max_id , 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 ManagePlaylistsAsyncTask(PlaylistsActivity.this,GET_LIST_VIDEOS, playlist, 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 ManagePlaylistsAsyncTask(PlaylistsActivity.this,GET_LIST_VIDEOS, playlist, null, null , PlaylistsActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActionDone(ManagePlaylistsAsyncTask.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 == GET_LIST_VIDEOS) {
int previousPosition = this.peertubes.size();
List<Peertube> videos = apiResponse.getPeertubes();
max_id = apiResponse.getMax_id();
flag_loading = (max_id == null);
if (!swiped && firstLoad && (videos == null || videos.size() == 0))
textviewNoAction.setVisibility(View.VISIBLE);
else
textviewNoAction.setVisibility(View.GONE);
if (swiped) {
if (previousPosition > 0) {
for (int i = 0; i < previousPosition; i++) {
this.peertubes.remove(0);
}
peertubeAdapter.notifyItemRangeRemoved(0, previousPosition);
}
swiped = false;
}
if (videos != null && videos.size() > 0) {
this.peertubes.addAll(videos);
peertubeAdapter.notifyItemRangeInserted(previousPosition, videos.size());
}
swipeRefreshLayout.setRefreshing(false);
firstLoad = false;
}
}
}

View File

@ -47,6 +47,7 @@ import android.text.Html;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -153,8 +154,8 @@ import app.fedilab.android.interfaces.OnRetrieveAttachmentInterface;
import app.fedilab.android.interfaces.OnRetrieveEmojiInterface;
import app.fedilab.android.interfaces.OnRetrieveSearcAccountshInterface;
import app.fedilab.android.interfaces.OnRetrieveSearchInterface;
import static app.fedilab.android.helper.Helper.changeDrawableColor;
import static app.fedilab.android.helper.Helper.countWithEmoji;
/**
* Created by Thomas on 01/05/2017.
@ -219,6 +220,9 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
public static HashMap<String, Uri> filesMap;
private Poll poll;
private ImageButton poll_action;
public static boolean autocomplete;
private String newContent;
private TextWatcher textWatcher;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -253,6 +257,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}else{
max_media_count = 4;
}
autocomplete = false;
setContentView(R.layout.activity_toot);
ActionBar actionBar = getSupportActionBar();
if( actionBar != null ) {
@ -449,7 +454,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
if( mentionAccount != null){
toot_content.setText(String.format("@%s\n", mentionAccount));
toot_content.setSelection(toot_content.getText().length());
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
}
if( tootMention != null && urlMention != null) {
if (fileMention != null) {
@ -476,7 +481,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
});
}
toot_content.setText(String.format("\n\nvia @%s\n\n%s\n\n", tootMention, urlMention));
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
}
@ -511,7 +516,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
toot_content.setText(sharedContent);
if (selectionBefore >= 0 && selectionBefore < toot_content.length())
toot_content.setSelection(selectionBefore);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
}
if (image != null) {
new HttpsConnection(TootActivity.this, instance).download(image, TootActivity.this);
@ -520,14 +525,12 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
toot_content.setText(String.format("\n%s", sharedContent));
if (selectionBefore >= 0 && selectionBefore < toot_content.length())
toot_content.setSelection(selectionBefore);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
}
}
attachments = new ArrayList<>();
int charsInCw = 0;
int charsInToot = 0;
if (!sharedUri.isEmpty()) {
uploadSharedImage(sharedUri);
@ -561,7 +564,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
});
toot_space_left.setText(String.valueOf(charsInToot + charsInCw));
toot_space_left.setText(String.valueOf(countLength()));
toot_cw.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -674,83 +677,121 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
int totalChar = toot_cw_content.length() + toot_content.length();
toot_space_left.setText(String.valueOf(totalChar));
toot_space_left.setText(String.valueOf(countLength()));
}
});
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA)
toot_content.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (autocomplete) {
toot_content.removeTextChangedListener(textWatcher);
Thread thread = new Thread() {
@Override
public void run() {
int currentCount = countLength();
while (currentCount < 500) {
newContent = newContent + new String(Character.toChars(0x1F917));
toot_content.setText(newContent);
currentCount++;
}
toot_content.setSelection(toot_content.getText().length());
toot_content.addTextChangedListener(textWatcher);
autocomplete = false;
toot_space_left.setText(String.valueOf(currentCount));
}
};
thread.start();
return;
}
@Override
public void afterTextChanged(Editable s) {
if( toot_content.getSelectionStart() != 0)
currentCursorPosition = toot_content.getSelectionStart();
if( s.toString().length() == 0 )
currentCursorPosition = 0;
//Only check last 15 characters before cursor position to avoid lags
if( currentCursorPosition < 15 ){ //Less than 15 characters are written before the cursor position
searchLength = currentCursorPosition;
}else {
searchLength = 15;
if (toot_content.getSelectionStart() != 0)
currentCursorPosition = toot_content.getSelectionStart();
if (s.toString().length() == 0)
currentCursorPosition = 0;
//Only check last 15 characters before cursor position to avoid lags
if (currentCursorPosition < 15) { //Less than 15 characters are written before the cursor position
searchLength = currentCursorPosition;
} else {
searchLength = 15;
}
int totalChar = countLength();
toot_space_left.setText(String.valueOf(totalChar));
if (currentCursorPosition - (searchLength - 1) < 0 || currentCursorPosition == 0 || currentCursorPosition > s.toString().length())
return;
String patternh = "^(.|\\s)*(:fedilab_hugs:)$";
final Pattern hPattern = Pattern.compile(patternh);
Matcher mh = hPattern.matcher((s.toString().substring(currentCursorPosition - searchLength, currentCursorPosition)));
if (mh.matches()) {
autocomplete = true;
newContent = s.toString().replaceAll(":fedilab_hugs:", " ");
return;
}
Matcher m, mt;
if (s.toString().charAt(0) == '@')
m = sPattern.matcher(s.toString().substring(currentCursorPosition - searchLength, currentCursorPosition));
else
m = sPattern.matcher(s.toString().substring(currentCursorPosition - (searchLength - 1), currentCursorPosition));
if (m.matches()) {
String search = m.group(3);
if (pp_progress != null && pp_actionBar != null) {
pp_progress.setVisibility(View.VISIBLE);
pp_actionBar.setVisibility(View.GONE);
}
int totalChar = toot_cw_content.length() + toot_content.length();
toot_space_left.setText(String.valueOf(totalChar));
if( currentCursorPosition- (searchLength-1) < 0 || currentCursorPosition == 0 || currentCursorPosition > s.toString().length())
return;
Matcher m, mt;
if( s.toString().charAt(0) == '@')
m = sPattern.matcher(s.toString().substring(currentCursorPosition- searchLength, currentCursorPosition));
new RetrieveSearchAccountsAsyncTask(getApplicationContext(), search, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
if (s.toString().charAt(0) == '#')
mt = tPattern.matcher(s.toString().substring(currentCursorPosition - searchLength, currentCursorPosition));
else
m = sPattern.matcher(s.toString().substring(currentCursorPosition- (searchLength-1), currentCursorPosition));
if(m.matches()) {
String search = m.group(3);
mt = tPattern.matcher(s.toString().substring(currentCursorPosition - (searchLength - 1), currentCursorPosition));
if (mt.matches()) {
String search = mt.group(3);
if (pp_progress != null && pp_actionBar != null) {
pp_progress.setVisibility(View.VISIBLE);
pp_actionBar.setVisibility(View.GONE);
}
new RetrieveSearchAccountsAsyncTask(getApplicationContext(),search,TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else{
if( s.toString().charAt(0) == '#')
mt = tPattern.matcher(s.toString().substring(currentCursorPosition- searchLength, currentCursorPosition));
new RetrieveSearchAsyncTask(getApplicationContext(), search, true, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
if (s.toString().charAt(0) == ':')
mt = ePattern.matcher(s.toString().substring(currentCursorPosition - searchLength, currentCursorPosition));
else
mt = tPattern.matcher(s.toString().substring(currentCursorPosition- (searchLength-1), currentCursorPosition));
if(mt.matches()) {
String search = mt.group(3);
mt = ePattern.matcher(s.toString().substring(currentCursorPosition - (searchLength - 1), currentCursorPosition));
if (mt.matches()) {
String shortcode = mt.group(3);
if (pp_progress != null && pp_actionBar != null) {
pp_progress.setVisibility(View.VISIBLE);
pp_actionBar.setVisibility(View.GONE);
}
new RetrieveSearchAsyncTask(getApplicationContext(),search,true, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else{
if( s.toString().charAt(0) == ':')
mt = ePattern.matcher(s.toString().substring(currentCursorPosition- searchLength, currentCursorPosition));
else
mt = ePattern.matcher(s.toString().substring(currentCursorPosition- (searchLength-1), currentCursorPosition));
if(mt.matches()) {
String shortcode = mt.group(3);
if (pp_progress != null && pp_actionBar != null) {
pp_progress.setVisibility(View.VISIBLE);
pp_actionBar.setVisibility(View.GONE);
}
new RetrieveEmojiAsyncTask(getApplicationContext(),shortcode,TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else {
toot_content.dismissDropDown();
}
new RetrieveEmojiAsyncTask(getApplicationContext(), shortcode, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
toot_content.dismissDropDown();
}
}
totalChar = toot_cw_content.length() + toot_content.length();
toot_space_left.setText(String.valueOf(totalChar));
}
});
totalChar = countLength();
toot_space_left.setText(String.valueOf(totalChar));
}
};
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA)
toot_content.addTextChangedListener(textWatcher);
if( scheduledstatus != null)
restoreServerSchedule(scheduledstatus.getStatus());
@ -766,7 +807,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
});
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
}
@Override
@ -1225,7 +1266,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
picker.show(getSupportFragmentManager(), "COUNTRY_PICKER");
return true;
case R.id.action_emoji:
final List<Emojis> emojis = new CustomEmojiDAO(getApplicationContext(), db).getAllEmojis();
final List<Emojis> emojis = new CustomEmojiDAO(getApplicationContext(), db).getAllEmojis(account.getInstance());
final AlertDialog.Builder builder = new AlertDialog.Builder(this, style);
int paddingPixel = 15;
float density = getResources().getDisplayMetrics().density;
@ -1532,7 +1573,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
String tootContent;
if( toot_cw_content.getText() != null && toot_cw_content.getText().toString().trim().length() > 0 )
split_toot_size -= toot_cw_content.getText().toString().trim().length();
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || !split_toot || (toot_content.getText().toString().trim().length() < split_toot_size)){
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || !split_toot || (countLength() < split_toot_size)){
tootContent = toot_content.getText().toString().trim();
}else{
splitToot = Helper.splitToots(toot_content.getText().toString().trim(), split_toot_size);
@ -1740,7 +1781,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
//Adds the shorter text_url of attachment at the end of the toot
int selectionBefore = toot_content.getSelectionStart();
toot_content.setText(String.format("%s\n\n%s",toot_content.getText().toString(), attachment.getText_url()));
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
//Moves the cursor
toot_content.setSelection(selectionBefore);
}
@ -1897,7 +1938,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
//Clears the text_url at the end of the toot for this attachment
int selectionBefore = toot_content.getSelectionStart();
toot_content.setText(toot_content.getText().toString().replace(attachment.getText_url(), ""));
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
//Moves the cursor
if (selectionBefore >= 0 && selectionBefore < toot_content.length())
toot_content.setSelection(selectionBefore);
@ -1995,7 +2036,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
int cwSize = toot_cw_content.getText().toString().trim().length();
int size = toot_content.getText().toString().trim().length() + cwSize;
if( split_toot && (size >= split_toot_size) && stepSpliToot < splitToot.size()){
if( split_toot && splitToot != null && (size >= split_toot_size) && stepSpliToot < splitToot.size()){
String tootContent = splitToot.get(stepSpliToot);
stepSpliToot += 1;
Status toot = new Status();
@ -2112,7 +2153,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
if (currentCursorPosition < oldContent.length() )
newContent += oldContent.substring(currentCursorPosition, oldContent.length());
toot_content.setText(newContent);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
toot_content.setSelection(newPosition);
AccountsSearchAdapter accountsListAdapter = new AccountsSearchAdapter(TootActivity.this, new ArrayList<>());
toot_content.setThreshold(1);
@ -2183,7 +2224,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
if( currentCursorPosition < oldContent.length() )
newContent += oldContent.substring(currentCursorPosition, oldContent.length()-1);
toot_content.setText(newContent);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
toot_content.setSelection(newPosition);
EmojisSearchAdapter emojisSearchAdapter = new EmojisSearchAdapter(TootActivity.this, new ArrayList<>());
toot_content.setThreshold(1);
@ -2237,7 +2278,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
if( currentCursorPosition < oldContent.length() )
newContent += oldContent.substring(currentCursorPosition, oldContent.length()-1);
toot_content.setText(newContent);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
toot_content.setSelection(newPosition);
TagsSearchAdapter tagsSearchAdapter = new TagsSearchAdapter(TootActivity.this, new ArrayList<>());
toot_content.setThreshold(1);
@ -2367,7 +2408,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
toot_content.setText(content);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
toot_content.setSelection(toot_content.getText().length());
switch (status.getVisibility()){
case "public":
@ -2410,7 +2451,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
invalidateOptionsMenu();
initialContent = toot_content.getText().toString();
toot_space_left.setText(String.valueOf(toot_content.getText().length() + toot_cw_content.getText().length()));
toot_space_left.setText(String.valueOf(countLength()));
}
@ -2517,7 +2558,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
toot_content.setText(content);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
toot_content.setSelection(toot_content.getText().length());
switch (status.getVisibility()){
case "public":
@ -2552,7 +2593,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
invalidateOptionsMenu();
initialContent = toot_content.getText().toString();
toot_space_left.setText(String.valueOf(toot_content.getText().length() + toot_cw_content.getText().length()));
toot_space_left.setText(String.valueOf(countLength()));
}
private void tootReply(){
@ -2676,7 +2717,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
else
toot_content.append(" ");
}
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
toot_content.requestFocus();
if( capitalize) {
@ -2700,7 +2741,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
toot_content.setText(toot_content.getText() +" #"+tag.getName());
}
toot_content.setSelection(currentCursorPosition);
toot_space_left.setText(String.valueOf(toot_content.length()));
toot_space_left.setText(String.valueOf(countLength()));
}
}
@ -3011,4 +3052,26 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface,
}
private int countLength(){
if( toot_content == null || toot_cw_content == null) {
return -1;
}
String content = toot_content.getText().toString();
String cwContent = toot_cw_content.getText().toString();
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA ){
Matcher matcherALink = Patterns.WEB_URL.matcher(content);
while (matcherALink.find()){
int matchStart = matcherALink.start();
int matchEnd = matcherALink.end();
final String url = content.substring(matcherALink.start(1), matcherALink.end(1));
if( matchEnd <= content.length() && matchEnd >= matchStart){
content = content.replaceFirst(url,"abcdefghijklmnopkrstuvw");
}
}
}
int contentLength = content.length() - countWithEmoji(content);
int cwLength = cwContent.length() - countWithEmoji(cwContent);
return cwLength + contentLength;
}
}

View File

@ -0,0 +1,100 @@
/* 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 <http://www.gnu.org/licenses>. */
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 java.util.List;
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<Void, Void, Void> {
public enum action{
GET_PLAYLIST,
GET_LIST_VIDEOS,
CREATE_PLAYLIST,
DELETE_PLAYLIST,
UPDATE_PLAYLIST,
ADD_VIDEOS,
DELETE_VIDEOS,
GET_PLAYLIST_FOR_VIDEO,
}
private OnPlaylistActionInterface listener;
private APIResponse apiResponse;
private int statusCode;
private action apiAction;
private WeakReference<Context> contextReference;
private String max_id;
private Playlist playlist;
private String videoId;
public ManagePlaylistsAsyncTask(Context context, action apiAction, Playlist playlist, String videoId, String max_id, OnPlaylistActionInterface onPlaylistActionInterface){
contextReference = new WeakReference<>(context);
this.listener = onPlaylistActionInterface;
this.apiAction = apiAction;
this.max_id = max_id;
this.playlist = playlist;
this.videoId = videoId;
}
@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_VIDEOS){
apiResponse = new PeertubeAPI(contextReference.get()).getPlaylistVideos(playlist.getId(),max_id, null);
}else if(apiAction == action.DELETE_PLAYLIST){
statusCode = new PeertubeAPI(contextReference.get()).deletePlaylist(playlist.getId());
}else if(apiAction == action.ADD_VIDEOS){
statusCode = new PeertubeAPI(contextReference.get()).addVideoPlaylist(playlist.getId(),videoId);
}else if(apiAction == action.DELETE_VIDEOS){
statusCode = new PeertubeAPI(contextReference.get()).deleteVideoPlaylist(playlist.getId(),videoId);
}else if(apiAction == action.GET_PLAYLIST_FOR_VIDEO){
apiResponse = new PeertubeAPI(contextReference.get()).getPlaylistForVideo(videoId);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onActionDone(this.apiAction, apiResponse, statusCode);
}
}

View File

@ -94,6 +94,7 @@ public class RetrieveFeedsAsyncTask extends AsyncTask<Void, Void, Void> {
PLOCAL,
CHANNEL,
MYVIDEOS,
PEERTUBE_HISTORY,
PIXELFED,
PF_HOME,
@ -311,6 +312,10 @@ public class RetrieveFeedsAsyncTask extends AsyncTask<Void, Void, Void> {
PeertubeAPI peertubeAPI = new PeertubeAPI(this.contextReference.get());
apiResponse = peertubeAPI.getMyVideos(max_id);
break;
case PEERTUBE_HISTORY:
peertubeAPI = new PeertubeAPI(this.contextReference.get());
apiResponse = peertubeAPI.getMyHistory(max_id);
break;
case CHANNEL:
peertubeAPI = new PeertubeAPI(this.contextReference.get());
apiResponse = peertubeAPI.getVideosChannel(targetedID, max_id);

View File

@ -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<Peertube> peertubes = null;
private List<PeertubeNotification> peertubeNotifications = null;
private List<Filters> filters = null;
private List<Playlist> playlists = null;
private List<String> domains = null;
private List<app.fedilab.android.client.Entities.List> lists = null;
private List<Emojis> emojis = null;
@ -59,6 +61,7 @@ public class APIResponse {
private Instance instance;
private List<StoredStatus> storedStatuses;
private boolean fetchmore = false;
private List<String> playlistForVideos;
public List<Account> getAccounts() {
return accounts;
@ -219,4 +222,20 @@ public class APIResponse {
public void setFetchmore(boolean fetchmore) {
this.fetchmore = fetchmore;
}
public List<Playlist> getPlaylists() {
return playlists;
}
public void setPlaylists(List<Playlist> playlists) {
this.playlists = playlists;
}
public List<String> getPlaylistForVideos() {
return playlistForVideos;
}
public void setPlaylistForVideos(List<String> playlistForVideos) {
this.playlistForVideos = playlistForVideos;
}
}

View File

@ -25,8 +25,10 @@ public class PeertubeInformation {
private LinkedHashMap<String, String> languages;
private LinkedHashMap<Integer, String> licences;
private LinkedHashMap<Integer, String> privacies;
private LinkedHashMap<Integer, String> playlistPrivacies;
private LinkedHashMap<String, String> translations;
public static final LinkedHashMap<String, String> langueMapped;
static {
LinkedHashMap<String, String> aMap = new LinkedHashMap<>();
@ -86,4 +88,12 @@ public class PeertubeInformation {
public void setPrivacies(LinkedHashMap<Integer, String> privacies) {
this.privacies = privacies;
}
public LinkedHashMap<Integer, String> getPlaylistPrivacies() {
return playlistPrivacies;
}
public void setPlaylistPrivacies(LinkedHashMap<Integer, String> playlistPrivacies) {
this.playlistPrivacies = playlistPrivacies;
}
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Integer, String> privacy;
private String thumbnailPath;
private HashMap<Integer, String> 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<Integer, String> getPrivacy() {
return privacy;
}
public void setPrivacy(HashMap<Integer, String> privacy) {
this.privacy = privacy;
}
public String getThumbnailPath() {
return thumbnailPath;
}
public void setThumbnailPath(String thumbnailPath) {
this.thumbnailPath = thumbnailPath;
}
public HashMap<Integer, String> getType() {
return type;
}
public void setType(HashMap<Integer, String> 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<Integer, String>) in.readSerializable();
this.thumbnailPath = in.readString();
this.type = (HashMap<Integer, String>) in.readSerializable();
long tmpUpdatedAt = in.readLong();
this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt);
this.videosLength = in.readInt();
}
public static final Parcelable.Creator<Playlist> CREATOR = new Parcelable.Creator<Playlist>() {
@Override
public Playlist createFromParcel(Parcel source) {
return new Playlist(source);
}
@Override
public Playlist[] newArray(int size) {
return new Playlist[size];
}
};
}

View File

@ -555,7 +555,31 @@ public class Status implements Parcelable{
SpannableString spannableStringContent, spannableStringCW;
if( (status.getReblog() != null && status.getReblog().getContent() == null) || (status.getReblog() == null && status.getContent() == null))
return;
spannableStringContent = new SpannableString(status.getReblog() != null ?status.getReblog().getContent():status.getContent());
String content = status.getReblog() != null ?status.getReblog().getContent():status.getContent();
Pattern aLink = Pattern.compile("<a href=\"([^\"]*)\"[^>]*(((?!<\\/a).)*)<\\/a>");
Matcher matcherALink = aLink.matcher(content);
while (matcherALink.find()){
String beforemodification;
String urlText = matcherALink.group(2);
urlText = urlText.substring(1);
beforemodification = urlText;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
urlText = new SpannableString(Html.fromHtml(urlText, Html.FROM_HTML_MODE_LEGACY)).toString();
else
urlText = new SpannableString(Html.fromHtml(urlText)).toString();
if( urlText.startsWith("http") ){
urlText = urlText.replace("http://","").replace("https://","").replace("www.","");
if( urlText.length() > 31){
urlText = urlText.substring(0,30);
urlText += '…';
}
}
content = content.replaceAll(Pattern.quote(beforemodification),urlText);
}
spannableStringContent = new SpannableString(content);
String spoilerText = "";
if( status.getReblog() != null && status.getReblog().getSpoiler_text() != null)
spoilerText = status.getReblog().getSpoiler_text();

View File

@ -54,6 +54,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;
@ -231,55 +232,6 @@ public class PeertubeAPI {
}
/***
* Update credential of the authenticated user *synchronously*
* @return APIResponse
*/
public APIResponse updateCredential(String display_name, String note, ByteArrayInputStream avatar, String avatarName, ByteArrayInputStream header, String headerName, API.accountPrivacy privacy, HashMap<String, String> customFields) {
HashMap<String, String> requestParams = new HashMap<>();
if( display_name != null)
try {
requestParams.put("display_name",URLEncoder.encode(display_name, "UTF-8"));
} catch (UnsupportedEncodingException e) {
requestParams.put("display_name",display_name);
}
if( note != null)
try {
requestParams.put("note",URLEncoder.encode(note, "UTF-8"));
} catch (UnsupportedEncodingException e) {
requestParams.put("note",note);
}
if( privacy != null)
requestParams.put("locked",privacy== API.accountPrivacy.LOCKED?"true":"false");
int i = 0;
if( customFields != null && customFields.size() > 0){
Iterator it = customFields.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
requestParams.put("fields_attributes["+i+"][name]",(String)pair.getKey());
requestParams.put("fields_attributes["+i+"][value]",(String)pair.getValue());
it.remove();
i++;
}
}
try {
new HttpsConnection(context, this.instance).patch(getAbsoluteUrl("/accounts/update_credentials"), 60, requestParams, avatar, avatarName, header, headerName, prefKeyOauthTokenT);
} catch (HttpsConnection.HttpsConnectionException e) {
e.printStackTrace();
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return apiResponse;
}
/***
* Verifiy credential of the authenticated user *synchronously*
* @return Account
@ -319,6 +271,15 @@ public class PeertubeAPI {
peertubeInformation.setPrivacies(_pprivacies);
response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/video-playlists/privacies"), 60, null, null);
JSONObject plprivacies = new JSONObject(response);
LinkedHashMap<Integer, String> _plprivacies = new LinkedHashMap<>();
for( int i = 1 ; i <= plprivacies.length() ; i++){
_plprivacies.put(i, plprivacies.getString(String.valueOf(i)));
}
peertubeInformation.setPlaylistPrivacies(_plprivacies);
response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/videos/licences"), 60, null, null);
JSONObject licences = new JSONObject(response);
LinkedHashMap<Integer, String> _plicences = new LinkedHashMap<>();
@ -520,9 +481,61 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getVideos(String acct, String max_id) {
return getVideos(acct, max_id, null, tootPerPage);
return getVideos(acct, max_id, null);
}
/**
* Retrieves history for videos for the account *synchronously*
*
* @param max_id String id max
* @return APIResponse
*/
public APIResponse getMyHistory(String max_id) {
return getMyHistory(max_id, null);
}
/**
* Retrieves history for videos for the account *synchronously*
*
* @param max_id String id max
* @param since_id String since the id
* @return APIResponse
*/
@SuppressWarnings("SameParameterValue")
private APIResponse getMyHistory(String max_id, String since_id) {
HashMap<String, String> params = new HashMap<>();
if (max_id != null)
params.put("start", max_id);
if (since_id != null)
params.put("since_id", since_id);
params.put("count", String.valueOf(tootPerPage));
List<Peertube> peertubes = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
String response = httpsConnection.get(getAbsoluteUrl("/users/me/history/videos"), 60, params, prefKeyOauthTokenT);
JSONArray jsonArray = new JSONObject(response).getJSONArray("data");
peertubes = parsePeertube(jsonArray);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse.setPeertubes(peertubes);
return apiResponse;
}
/**
* Retrieves videos for the account *synchronously*
@ -531,7 +544,7 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getMyVideos(String max_id) {
return getMyVideos(max_id, null, tootPerPage);
return getMyVideos(max_id, null);
}
@ -541,20 +554,17 @@ public class PeertubeAPI {
*
* @param max_id String id max
* @param since_id String since the id
* @param limit int limit - max value 40
* @return APIResponse
*/
@SuppressWarnings("SameParameterValue")
private APIResponse getMyVideos(String max_id, String since_id, int limit) {
private APIResponse getMyVideos(String max_id, String since_id) {
HashMap<String, String> params = new HashMap<>();
if (max_id != null)
params.put("start", max_id);
if (since_id != null)
params.put("since_id", since_id);
if (0 < limit || limit > 40)
limit = 40;
params.put("count", String.valueOf(limit));
params.put("count", String.valueOf(tootPerPage));
List<Peertube> peertubes = new ArrayList<>();
try {
@ -565,42 +575,6 @@ public class PeertubeAPI {
peertubes = parsePeertube(jsonArray);
} catch (HttpsConnection.HttpsConnectionException e) {
if( e.getStatusCode() == 401){ //Avoid the issue with the refresh token
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
SharedPreferences sharedpreferences = context.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(context));
Account account = new AccountDAO(context, db).getUniqAccount(userId, instance);
HashMap<String, String> values = new PeertubeAPI(context).refreshToken(account.getClient_id(), account.getClient_secret(), account.getRefresh_token());
if( values != null) {
String newtoken = values.get("access_token");
String refresh_token = values.get("refresh_token");
if (newtoken != null)
account.setToken(newtoken);
if (refresh_token != null)
account.setRefresh_token(refresh_token);
new AccountDAO(context, db).updateAccount(account);
prefKeyOauthTokenT = newtoken;
}
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
String response;
try {
response = httpsConnection.get(getAbsoluteUrl("/users/me/videos"), 60, params, prefKeyOauthTokenT);
JSONArray jsonArray = new JSONObject(response).getJSONArray("data");
peertubes = parsePeertube(jsonArray);
} catch (IOException e1) {
e1.printStackTrace();
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (KeyManagementException e1) {
e1.printStackTrace();
} catch (HttpsConnection.HttpsConnectionException e1) {
e1.printStackTrace();
} catch (JSONException e1) {
e1.printStackTrace();
}
}
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
@ -623,20 +597,17 @@ public class PeertubeAPI {
* @param acct String Id of the account
* @param max_id String id max
* @param since_id String since the id
* @param limit int limit - max value 40
* @return APIResponse
*/
@SuppressWarnings("SameParameterValue")
private APIResponse getVideos(String acct, String max_id, String since_id, int limit) {
private APIResponse getVideos(String acct, String max_id, String since_id) {
HashMap<String, String> params = new HashMap<>();
if (max_id != null)
params.put("start", max_id);
if (since_id != null)
params.put("since_id", since_id);
if (0 < limit || limit > 40)
limit = 40;
params.put("count", String.valueOf(limit));
params.put("count", String.valueOf(tootPerPage));
List<Peertube> peertubes = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
@ -667,7 +638,7 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getNotifications(String max_id){
return getNotifications(max_id, null, 20);
return getNotifications(max_id, null);
}
/**
@ -677,7 +648,7 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getNotificationsSince(String since_id){
return getNotifications(null, since_id, 20);
return getNotifications(null, since_id);
}
/**
@ -685,20 +656,17 @@ public class PeertubeAPI {
*
* @param max_id String id max
* @param since_id String since the id
* @param limit int limit - max value 40
* @return APIResponse
*/
@SuppressWarnings("SameParameterValue")
private APIResponse getNotifications(String max_id, String since_id, int limit) {
private APIResponse getNotifications(String max_id, String since_id) {
HashMap<String, String> params = new HashMap<>();
if (max_id != null)
params.put("start", max_id);
if (since_id != null)
params.put("since_id", since_id);
if (0 < limit || limit > 40)
limit = 40;
params.put("count", String.valueOf(limit));
params.put("count", String.valueOf(tootPerPage));
List<PeertubeNotification> peertubeNotifications = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
@ -730,7 +698,7 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getVideosChannel(String acct, String max_id) {
return getVideosChannel(acct, max_id, null, tootPerPage);
return getVideosChannel(acct, max_id, null);
}
/**
@ -739,20 +707,17 @@ public class PeertubeAPI {
* @param acct String Id of the account
* @param max_id String id max
* @param since_id String since the id
* @param limit int limit - max value 40
* @return APIResponse
*/
@SuppressWarnings("SameParameterValue")
private APIResponse getVideosChannel(String acct, String max_id, String since_id, int limit) {
private APIResponse getVideosChannel(String acct, String max_id, String since_id) {
HashMap<String, String> params = new HashMap<>();
if (max_id != null)
params.put("start", max_id);
if (since_id != null)
params.put("since_id", since_id);
if (0 < limit || limit > 40)
limit = 40;
params.put("count", String.valueOf(limit));
params.put("count", String.valueOf(tootPerPage));
List<Peertube> peertubes = new ArrayList<>();
try {
@ -787,7 +752,37 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getSubscriptionsTL( String max_id) {
return getTL("/users/me/subscriptions/videos","-publishedAt",null, max_id, null, null, tootPerPage);
try {
return getTL("/users/me/subscriptions/videos","-publishedAt",null, max_id, null, null);
} catch (HttpsConnection.HttpsConnectionException e) {
if( e.getStatusCode() == 401 || e.getStatusCode() == 403) {
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
Account targetedAccount = new AccountDAO(context, db).getAccountByToken(prefKeyOauthTokenT);
HashMap<String, String> values = refreshToken(targetedAccount.getClient_id(), targetedAccount.getClient_secret(), targetedAccount.getRefresh_token());
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if (values.containsKey("access_token") && values.get("access_token") != null) {
targetedAccount.setToken(values.get("access_token"));
String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
//This account is currently logged in, the token is updated
if (prefKeyOauthTokenT.equals(token)) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, targetedAccount.getToken());
editor.apply();
}
}
if (values.containsKey("refresh_token") && values.get("refresh_token") != null)
targetedAccount.setRefresh_token(values.get("refresh_token"));
new AccountDAO(context, db).updateAccount(targetedAccount);
try {
return getTL("/users/me/subscriptions/videos","-publishedAt",null, max_id, null, null);
} catch (HttpsConnection.HttpsConnectionException e1) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
setError(e.getStatusCode(), e);
return apiResponse;
}
}
/**
@ -796,7 +791,12 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getOverviewTL( String max_id) {
return getTL("/overviews/videos",null,null, max_id, null, null, tootPerPage);
try {
return getTL("/overviews/videos",null,null, max_id, null, null);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
/**
@ -805,7 +805,12 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getTrendingTL( String max_id) {
return getTL("/videos/","-trending", null,max_id, null, null, tootPerPage);
try {
return getTL("/videos/","-trending", null,max_id, null, null);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
/**
@ -814,7 +819,12 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getRecentlyAddedTL( String max_id) {
return getTL("/videos/","-publishedAt",null,max_id, null, null, tootPerPage);
try {
return getTL("/videos/","-publishedAt",null,max_id, null, null);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
/**
@ -823,7 +833,12 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getLocalTL( String max_id) {
return getTL("/videos/","-publishedAt", "local",max_id, null, null, tootPerPage);
try {
return getTL("/videos/","-publishedAt", "local",max_id, null, null);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
/**
@ -831,7 +846,12 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getSubscriptionsTLSinceId(String since_id) {
return getTL("/users/me/subscriptions/videos",null,null,null, since_id, null, tootPerPage);
try {
return getTL("/users/me/subscriptions/videos",null,null,null, since_id, null);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
/**
@ -839,7 +859,12 @@ public class PeertubeAPI {
* @return APIResponse
*/
public APIResponse getSubscriptionsTLMinId(String min_id) {
return getTL("/users/me/subscriptions/videos",null, null,null, null, min_id, tootPerPage);
try {
return getTL("/users/me/subscriptions/videos",null, null,null, null, min_id);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
return apiResponse;
}
}
@ -847,10 +872,9 @@ public class PeertubeAPI {
* Retrieves home timeline for the account *synchronously*
* @param max_id String id max
* @param since_id String since the id
* @param limit int limit - max value 40
* @return APIResponse
*/
private APIResponse getTL(String action, String sort, String filter, String max_id, String since_id, String min_id, int limit) {
private APIResponse getTL(String action, String sort, String filter, String max_id, String since_id, String min_id) throws HttpsConnection.HttpsConnectionException {
HashMap<String, String> params = new HashMap<>();
if (max_id != null)
@ -859,9 +883,7 @@ public class PeertubeAPI {
params.put("since_id", since_id);
if (min_id != null)
params.put("min_id", min_id);
if (0 > limit || limit > 80)
limit = 80;
params.put("count",String.valueOf(limit));
params.put("count",String.valueOf(tootPerPage));
if( sort != null)
params.put("sort",sort);
else
@ -926,12 +948,7 @@ public class PeertubeAPI {
peertubes4.get(0).setHeaderTypeValue(videoA.getJSONObject(1).getJSONObject("channel").getString("displayName"));
peertubes.addAll(peertubes4);
}
}
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
@ -1014,8 +1031,7 @@ public class PeertubeAPI {
HashMap<String, String> params = new HashMap<>();
if( max_id == null)
max_id = "0";
params.put("start", max_id);
params.put("count", "50");
params.put("start", String.valueOf(tootPerPage));
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
String response = httpsConnection.get("https://"+instance+"/api/v1/videos", 60, params, null);
@ -1073,7 +1089,7 @@ public class PeertubeAPI {
*/
public APIResponse searchPeertube(String instance, String query) {
HashMap<String, String> params = new HashMap<>();
params.put("count", "50");
params.put("count", String.valueOf(tootPerPage));
try {
params.put("search", URLEncoder.encode(query, "UTF-8"));
} catch (UnsupportedEncodingException e) {
@ -1293,85 +1309,58 @@ public class PeertubeAPI {
}
/**
* Get filters for the user
* Video is in play lists
* @return APIResponse
*/
public APIResponse getFilters(){
public APIResponse getPlaylistForVideo(String videoId){
List<Filters> filters = null;
try {
String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/filters"), 60, null, prefKeyOauthTokenT);
filters = parseFilters(new JSONArray(response));
} 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.setFilters(filters);
return apiResponse;
}
/**
* Get a Filter by its id
* @return APIResponse
*/
@SuppressWarnings("unused")
public APIResponse getFilters(String filterId){
List<Filters> filters = new ArrayList<>();
Filters filter;
try {
String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl(String.format("/filters/%s", filterId)), 60, null, prefKeyOauthTokenT);
filter = parseFilter(new JSONObject(response));
filters.add(filter);
} 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.setFilters(filters);
return apiResponse;
}
/**
* Create a filter
* @param filter Filter
* @return APIResponse
*/
public APIResponse addFilters(Filters filter){
HashMap<String, String> params = new HashMap<>();
params.put("phrase", filter.getPhrase());
StringBuilder parameters = new StringBuilder();
for(String context: filter.getContext())
parameters.append("context[]=").append(context).append("&");
if( parameters.length() > 0) {
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(10));
params.put("context[]", parameters.toString());
}
params.put("irreversible", String.valueOf(filter.isIrreversible()));
params.put("whole_word", String.valueOf(filter.isWhole_word()));
params.put("expires_in", String.valueOf(filter.getExpires_in()));
ArrayList<Filters> filters = new ArrayList<>();
params.put("videoIds",videoId);
List<String> ids = new ArrayList<>();
try {
String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/filters"), 60, params, prefKeyOauthTokenT);
Filters resfilter = parseFilter(new JSONObject(response));
filters.add(resfilter);
String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/users/me/video-playlists/videos-exist"), 60, params, prefKeyOauthTokenT);
JSONArray jsonArray = new JSONObject(response).getJSONArray(videoId);
try {
int i = 0;
while (i < jsonArray.length() ) {
JSONObject resobj = jsonArray.getJSONObject(i);
String playlistId = resobj.getString("playlistId");
ids.add(playlistId);
i++;
}
} catch (JSONException e) {
setDefaultError(e);
}
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse = new APIResponse();
apiResponse.setPlaylistForVideos(ids);
return apiResponse;
}
/**
* Get lists for the user
* @return APIResponse
*/
public APIResponse getPlayists(String username){
List<Playlist> playlists = new ArrayList<>();
try {
String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl(String.format("/accounts/%s/video-playlists", username)), 60, null, prefKeyOauthTokenT);
playlists = parsePlaylists(context, new JSONObject(response).getJSONArray("data"));
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
@ -1383,20 +1372,46 @@ public class PeertubeAPI {
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse.setFilters(filters);
apiResponse.setPlaylists(playlists);
return apiResponse;
}
/**
* Delete a filter
* @param filter Filter
* @return APIResponse
*/
public int deleteFilters(Filters filter){
/**
* Delete a Playlist
* @param playlistId String, the playlist id
* @return int
*/
public int deletePlaylist(String playlistId){
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
httpsConnection.delete(getAbsoluteUrl(String.format("/filters/%s", filter.getId())), 60, null, prefKeyOauthTokenT);
httpsConnection.delete(getAbsoluteUrl(String.format("/video-playlists/%s", playlistId)), 60, null, prefKeyOauthTokenT);
actionCode = httpsConnection.getActionCode();
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return actionCode;
}
/**
* Delete video in a Playlist
* @param playlistId String, the playlist id
* @param videoId String, the video id
* @return int
*/
public int deleteVideoPlaylist(String playlistId, String videoId){
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
httpsConnection.delete(getAbsoluteUrl(String.format("/video-playlists/%s/videos/%s", playlistId, videoId)), 60, null, prefKeyOauthTokenT);
actionCode = httpsConnection.getActionCode();
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
@ -1411,30 +1426,61 @@ public class PeertubeAPI {
}
/**
* Delete a filter
* @param filter Filter
* Add video in a Playlist
* @param playlistId String, the playlist id
* @param videoId String, the video id
* @return int
*/
public int addVideoPlaylist(String playlistId, String videoId){
try {
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
HashMap<String, String> params = new HashMap<>();
params.put("videoId", videoId);
httpsConnection.post(getAbsoluteUrl(String.format("/video-playlists/%s/videos", playlistId)), 60, params, prefKeyOauthTokenT);
actionCode = httpsConnection.getActionCode();
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return actionCode;
}
/**
* Retrieves status for the account *synchronously*
*
* @param playlistid String Id of the playlist
* @param max_id String id max
* @param since_id String since the id
* @return APIResponse
*/
public APIResponse updateFilters(Filters filter){
@SuppressWarnings("SameParameterValue")
public APIResponse getPlaylistVideos(String playlistid, String max_id, String since_id) {
HashMap<String, String> params = new HashMap<>();
params.put("phrase", filter.getPhrase());
StringBuilder parameters = new StringBuilder();
for(String context: filter.getContext())
parameters.append("context[]=").append(context).append("&");
if( parameters.length() > 0) {
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(10));
params.put("context[]", parameters.toString());
}
params.put("irreversible", String.valueOf(filter.isIrreversible()));
params.put("whole_word", String.valueOf(filter.isWhole_word()));
params.put("expires_in", String.valueOf(filter.getExpires_in()));
ArrayList<Filters> filters = new ArrayList<>();
if (max_id != null)
params.put("start", max_id);
if (since_id != null)
params.put("since_id", since_id);
params.put("count", String.valueOf(tootPerPage));
params.put("sort","-updatedAt");
List<Peertube> peertubes = new ArrayList<>();
try {
String response = new HttpsConnection(context, this.instance).put(getAbsoluteUrl(String.format("/filters/%s", filter.getId())), 60, params, prefKeyOauthTokenT);
Filters resfilter = parseFilter(new JSONObject(response));
filters.add(resfilter);
HttpsConnection httpsConnection = new HttpsConnection(context, this.instance);
String response = httpsConnection.get(getAbsoluteUrl(String.format("/video-playlists/%s/videos", playlistid)), 60, params, prefKeyOauthTokenT);
JSONArray jsonArray = new JSONObject(response).getJSONArray("data");
peertubes = parsePeertube(jsonArray);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
@ -1444,62 +1490,10 @@ public class PeertubeAPI {
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse.setFilters(filters);
apiResponse.setPeertubes(peertubes);
return apiResponse;
}
/**
* Get lists for the user
* @return APIResponse
*/
public APIResponse getLists(){
List<app.fedilab.android.client.Entities.List> lists = new ArrayList<>();
try {
String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/lists"), 60, null, prefKeyOauthTokenT);
lists = parseLists(new JSONArray(response));
} 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;
}
/**
* Get lists for a user by its id
* @return APIResponse
*/
@SuppressWarnings("unused")
public APIResponse getLists(String userId){
List<app.fedilab.android.client.Entities.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
@ -2025,24 +2019,24 @@ public class PeertubeAPI {
/**
* Parse Lists
* Parse Playlists
* @param jsonArray JSONArray
* @return List<List> of lists
* @return List<Playlist> of lists
*/
private List<app.fedilab.android.client.Entities.List> parseLists(JSONArray jsonArray){
List<app.fedilab.android.client.Entities.List> lists = new ArrayList<>();
private List<Playlist> parsePlaylists(Context context, JSONArray jsonArray){
List<Playlist> 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;
}
@ -2051,13 +2045,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());
}catch (Exception ignored){}
return list;
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<Integer, String> type = new LinkedHashMap<>();
LinkedHashMap<Integer, String> 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){ignored.printStackTrace();}
try{
playlist.setUpdatedAt(Helper.stringToDate(context, resobj.getString("updatedAt")));
}catch (Exception ignored){ignored.printStackTrace();}
}catch (Exception ignored){ignored.printStackTrace();}
return playlist;
}
private List<Account> parseAccountResponsePeertube(Context context, String instance, JSONArray jsonArray){

View File

@ -85,12 +85,13 @@ public class EmojisSearchAdapter extends ArrayAdapter<Emojis> implements Filtera
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.emoji_shortcode.setText(String.format("%s", emoji.getShortcode()));
//Profile picture
Glide.with(holder.emoji_icon.getContext())
.load(emoji.getUrl())
.into(holder.emoji_icon);
if( emoji != null) {
holder.emoji_shortcode.setText(String.format("%s", emoji.getShortcode()));
//Profile picture
Glide.with(holder.emoji_icon.getContext())
.load(emoji.getUrl())
.into(holder.emoji_icon);
}
return convertView;
}
@ -124,15 +125,18 @@ public class EmojisSearchAdapter extends ArrayAdapter<Emojis> implements Filtera
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
ArrayList<Emojis> c = (ArrayList<Emojis>) results.values;
if (results.count > 0) {
clear();
addAll(c);
notifyDataSetChanged();
} else{
clear();
notifyDataSetChanged();
}
try{
ArrayList<Emojis> c = (ArrayList<Emojis>) results.values;
if (results.count > 0) {
clear();
addAll(c);
notifyDataSetChanged();
} else{
clear();
notifyDataSetChanged();
}
}catch (Exception ignored){}
}
};

View File

@ -0,0 +1,178 @@
package app.fedilab.android.drawers;
/* 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 <http://www.gnu.org/licenses>. */
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.activities.PlaylistsActivity;
import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Playlist;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.interfaces.OnPlaylistActionInterface;
/**
* Created by Thomas on 26/05/2019.
* Adapter for playlists
*/
public class PlaylistAdapter extends BaseAdapter implements OnPlaylistActionInterface {
private List<Playlist> playlists;
private LayoutInflater layoutInflater;
private Context context;
private PlaylistAdapter playlistAdapter;
private RelativeLayout textviewNoAction;
public PlaylistAdapter(Context context, List<Playlist> lists, RelativeLayout textviewNoAction){
this.playlists = lists;
layoutInflater = LayoutInflater.from(context);
this.context = context;
playlistAdapter = this;
this.textviewNoAction = textviewNoAction;
}
@Override
public int getCount() {
return playlists.size();
}
@Override
public Object getItem(int position) {
return playlists.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Playlist playlist = playlists.get(position);
final ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.drawer_search, parent, false);
holder = new ViewHolder();
holder.search_title = convertView.findViewById(R.id.search_keyword);
holder.search_container = convertView.findViewById(R.id.search_container);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
if( theme == Helper.THEME_LIGHT){
holder.search_container.setBackgroundResource(R.color.mastodonC3__);
Helper.changeDrawableColor(context, R.drawable.ic_keyboard_arrow_right,R.color.black);
}else if(theme == Helper.THEME_DARK){
holder.search_container.setBackgroundResource(R.color.mastodonC1_);
Helper.changeDrawableColor(context, R.drawable.ic_keyboard_arrow_right,R.color.dark_text);
}else if(theme == Helper.THEME_BLACK) {
holder.search_container.setBackgroundResource(R.color.black_2);
Helper.changeDrawableColor(context, R.drawable.ic_keyboard_arrow_right,R.color.dark_text);
}
Drawable next = ContextCompat.getDrawable(context, R.drawable.ic_keyboard_arrow_right);
holder.search_title.setText(playlist.getDisplayName());
assert next != null;
final float scale = context.getResources().getDisplayMetrics().density;
next.setBounds(0,0,(int) (30 * scale + 0.5f),(int) (30 * scale + 0.5f));
holder.search_title.setCompoundDrawables(null, null, next, null);
holder.search_container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, PlaylistsActivity.class);
Bundle b = new Bundle();
b.putParcelable("playlist", playlist);
intent.putExtras(b);
context.startActivity(intent);
}
});
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;
}
holder.search_container.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(context, style);
builder.setTitle(context.getString(R.string.action_lists_delete) + ": " + playlist.getDisplayName());
builder.setMessage(context.getString(R.string.action_lists_confirm_delete) );
builder.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
playlists.remove(playlist);
playlistAdapter.notifyDataSetChanged();
new ManagePlaylistsAsyncTask(context, ManagePlaylistsAsyncTask.action.DELETE_PLAYLIST,playlist, null, null, PlaylistAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if( playlists.size() == 0 && textviewNoAction != null && textviewNoAction.getVisibility() == View.GONE)
textviewNoAction.setVisibility(View.VISIBLE);
dialog.dismiss();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
return false;
}
});
return convertView;
}
@Override
public void onActionDone(ManagePlaylistsAsyncTask.action actionType, APIResponse apiResponse, int statusCode) {
}
private class ViewHolder {
LinearLayout search_container;
TextView search_title;
}
}

View File

@ -2757,7 +2757,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct
if (getItemViewType(viewHolder.getAdapterPosition()) == FOCUSED_STATUS && status.getApplication() != null && status.getApplication().getName() != null && status.getApplication().getName().length() > 0) {
Application application = status.getApplication();
holder.status_toot_app.setText(application.getName());
if (application.getWebsite() != null && !application.getWebsite().trim().equals("null") && application.getWebsite().trim().length() == 0) {
if (application.getWebsite() != null && !application.getWebsite().trim().equals("null") && application.getWebsite().trim().length() > 0) {
holder.status_toot_app.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

View File

@ -0,0 +1,461 @@
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 <http://www.gnu.org/licenses>. */
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.v4.app.FragmentTransaction;
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.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.jaredrummler.materialspinner.MaterialSpinner;
import net.gotev.uploadservice.MultipartUploadRequest;
import net.gotev.uploadservice.ServerResponse;
import net.gotev.uploadservice.UploadInfo;
import net.gotev.uploadservice.UploadNotificationConfig;
import net.gotev.uploadservice.UploadStatusDelegate;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.activities.PeertubeUploadActivity;
import app.fedilab.android.activities.PlaylistsActivity;
import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask;
import app.fedilab.android.asynctasks.RetrievePeertubeChannelsAsyncTask;
import app.fedilab.android.client.APIResponse;
import app.fedilab.android.client.Entities.Account;
import app.fedilab.android.client.Entities.Playlist;
import app.fedilab.android.drawers.PlaylistAdapter;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.interfaces.OnPlaylistActionInterface;
import app.fedilab.android.interfaces.OnRetrievePeertubeInterface;
import es.dmoral.toasty.Toasty;
import static app.fedilab.android.asynctasks.RetrievePeertubeInformationAsyncTask.peertubeInformation;
/**
* Created by Thomas on 26/05/2019.
* Fragment to display Playlists
*/
public class DisplayPlaylistsFragment extends Fragment implements OnPlaylistActionInterface, OnRetrievePeertubeInterface {
private Context context;
private AsyncTask<Void, Void, Void> asyncTask;
private List<Playlist> playlists;
private RelativeLayout mainLoader;
private FloatingActionButton add_new;
private PlaylistAdapter playlistAdapter;
private RelativeLayout textviewNoAction;
private HashMap<Integer, String> privacyToSend;
private HashMap<String, String> channelToSend;
private MaterialSpinner set_upload_channel;
private MaterialSpinner set_upload_privacy;
private HashMap<String, String> channels;
@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();
playlists = 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);
playlists = new ArrayList<>();
playlistAdapter = new PlaylistAdapter(context, playlists, textviewNoAction);
lv_playlist.setAdapter(playlistAdapter);
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){}
LinkedHashMap<String, String> translations = null;
if( peertubeInformation != null && peertubeInformation.getTranslations() != null)
translations = new LinkedHashMap<>(peertubeInformation.getTranslations());
LinkedHashMap<Integer, String> privaciesInit = new LinkedHashMap<>(peertubeInformation.getPrivacies());
Map.Entry<Integer,String> entryInt = privaciesInit.entrySet().iterator().next();
privacyToSend = new HashMap<>();
privacyToSend.put(entryInt.getKey(), entryInt.getValue());
LinkedHashMap<Integer, String> privacies = new LinkedHashMap<>(peertubeInformation.getPrivacies());
//Populate privacies
String[] privaciesA = new String[privacies.size()];
Iterator it = privacies.entrySet().iterator();
int i = 0;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
if( translations == null || translations.size() == 0 || !translations.containsKey((String)pair.getValue()))
privaciesA[i] = (String)pair.getValue();
else
privaciesA[i] = translations.get((String)pair.getValue());
it.remove();
i++;
}
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_playlist, null);
dialogBuilder.setView(dialogView);
EditText display_name = dialogView.findViewById(R.id.display_name);
EditText description = dialogView.findViewById(R.id.description);
set_upload_channel = dialogView.findViewById(R.id.set_upload_channel);
set_upload_privacy = dialogView.findViewById(R.id.set_upload_privacy);
Helper.changeMaterialSpinnerColor(context, set_upload_privacy);
Helper.changeMaterialSpinnerColor(context, set_upload_channel);
new RetrievePeertubeChannelsAsyncTask(context, DisplayPlaylistsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
display_name.setFilters(new InputFilter[]{new InputFilter.LengthFilter(120)});
description.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1000)});
dialogBuilder.setPositiveButton(R.string.validate, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
if( display_name.getText() != null && display_name.getText().toString().trim().length() > 0 ) {
Playlist playlist = new Playlist();
playlist.setDisplayName(display_name.getText().toString().trim());
if( description.getText() != null && description.getText().toString().trim().length() > 0 ){
playlist.setDescription(description.getText().toString().trim());
}
String idChannel = null;
if( channelToSend != null ) {
Map.Entry<String, String> channelM = channelToSend.entrySet().iterator().next();
idChannel = channelM.getValue();
if( idChannel.length() > 0)
playlist.setVideoChannelId(idChannel);
}
Map.Entry<Integer, String> privacyM = privacyToSend.entrySet().iterator().next();
String label = privacyM.getValue();
String idPrivacy = String.valueOf(privacyM.getKey());
if( label.equals("Public") && (playlist.getVideoChannelId() == null || playlist.getVideoChannelId().equals(""))){
Toasty.error(context, context.getString(R.string.error_channel_mandatory),Toast.LENGTH_LONG).show();
}else{
if( privacyToSend != null){
playlist.setPrivacy(privacyToSend);
}
//new ManagePlaylistsAsyncTask(context, ManagePlaylistsAsyncTask.action.CREATE_PLAYLIST, playlist, null, null, DisplayPlaylistsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
UploadNotificationConfig uploadConfig = new UploadNotificationConfig();
uploadConfig.getCompleted().autoClear = true;
try {
String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
new MultipartUploadRequest(context, "https://" + Helper.getLiveInstance(context) + "/api/v1/video-playlists/")
//.addFileToUpload(uri.toString().replace("file://",""), "videofile")
.addHeader("Authorization", "Bearer " + token)
.setNotificationConfig(uploadConfig)
// .addParameter("name", filename)
.addParameter("videoChannelId", idChannel)
.addParameter("privacy", idPrivacy)
.addParameter("displayName", playlist.getDisplayName())
.addParameter("description", playlist.getDescription())
.setMaxRetries(1)
.setDelegate(new UploadStatusDelegate() {
@Override
public void onProgress(Context context, UploadInfo uploadInfo) {
// your code here
}
@Override
public void onError(Context context, UploadInfo uploadInfo, ServerResponse serverResponse,
Exception exception) {
// your code here
exception.printStackTrace();
}
@Override
public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
DisplayPlaylistsFragment displayPlaylistsFragment;
if( getActivity() == null)
return;
displayPlaylistsFragment = (DisplayPlaylistsFragment) getActivity().getSupportFragmentManager().findFragmentByTag("PLAYLISTS");
final FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
if( displayPlaylistsFragment != null) {
ft.detach(displayPlaylistsFragment);
ft.attach(displayPlaylistsFragment);
ft.commit();
}
}
@Override
public void onCancelled(Context context, UploadInfo uploadInfo) {
// your code here
}
})
.startUpload();
} catch (MalformedURLException e) {
e.printStackTrace();
}
dialog.dismiss();
add_new.setEnabled(false);
}
}else{
Toasty.error(context, context.getString(R.string.error_display_name),Toast.LENGTH_LONG).show();
}
}
});
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_playlist_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(display_name.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.getPlaylists() != null && apiResponse.getPlaylists().size() > 0) {
this.playlists.addAll(apiResponse.getPlaylists());
playlistAdapter.notifyDataSetChanged();
textviewNoAction.setVisibility(View.GONE);
} else {
textviewNoAction.setVisibility(View.VISIBLE);
}
}else if( actionType == ManagePlaylistsAsyncTask.action.CREATE_PLAYLIST){
if (apiResponse.getPlaylists() != null && apiResponse.getPlaylists().size() > 0) {
Intent intent = new Intent(context, PlaylistsActivity.class);
Bundle b = new Bundle();
b.putParcelable("playlist", apiResponse.getPlaylists().get(0));
intent.putExtras(b);
context.startActivity(intent);
this.playlists.add(0, apiResponse.getPlaylists().get(0));
playlistAdapter.notifyDataSetChanged();
textviewNoAction.setVisibility(View.GONE);
}else{
Toasty.error(context, apiResponse.getError().getError(),Toast.LENGTH_LONG).show();
}
}else if( actionType == ManagePlaylistsAsyncTask.action.DELETE_PLAYLIST){
if( this.playlists.size() == 0)
textviewNoAction.setVisibility(View.VISIBLE);
}
}
@Override
public void onRetrievePeertube(APIResponse apiResponse) {
}
@Override
public void onRetrievePeertubeComments(APIResponse apiResponse) {
}
@Override
public void onRetrievePeertubeChannels(APIResponse apiResponse) {
if (apiResponse.getError() != null || apiResponse.getAccounts() == null || apiResponse.getAccounts().size() == 0) {
if (apiResponse.getError() != null && apiResponse.getError().getError() != null)
Toasty.error(context, apiResponse.getError().getError(), Toast.LENGTH_LONG).show();
else
Toasty.error(context, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
return;
}
//Populate channels
List<Account> accounts = apiResponse.getAccounts();
String[] channelName = new String[accounts.size()+1];
String[] channelId= new String[accounts.size()+1];
int i = 1;
channelName[0] = "";
channelId[0] = "";
channels = new HashMap<>();
for(Account account: accounts){
channels.put(account.getUsername(),account.getId());
channelName[i] = account.getUsername();
channelId[i] = account.getId();
i++;
}
channelToSend = new HashMap<>();
channelToSend.put(channelName[0], channelId[0]);
ArrayAdapter<String> adapterChannel = new ArrayAdapter<>(context,
android.R.layout.simple_spinner_dropdown_item, channelName);
set_upload_channel.setAdapter(adapterChannel);
LinkedHashMap<String, String> translations = null;
if (peertubeInformation.getTranslations() != null)
translations = new LinkedHashMap<>(peertubeInformation.getTranslations());
LinkedHashMap<Integer, String> privaciesInit = new LinkedHashMap<>(peertubeInformation.getPlaylistPrivacies());
Map.Entry<Integer, String> entryInt = privaciesInit.entrySet().iterator().next();
privacyToSend = new HashMap<>();
privacyToSend.put(entryInt.getKey(), entryInt.getValue());
LinkedHashMap<Integer, String> privacies = new LinkedHashMap<>(peertubeInformation.getPlaylistPrivacies());
//Populate privacies
String[] privaciesA = new String[privacies.size()];
Iterator it = privacies.entrySet().iterator();
i = 0;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (translations == null || translations.size() == 0 || !translations.containsKey((String) pair.getValue()))
privaciesA[i] = (String) pair.getValue();
else
privaciesA[i] = translations.get((String) pair.getValue());
it.remove();
i++;
}
ArrayAdapter<String> adapterPrivacies = new ArrayAdapter<>(context,
android.R.layout.simple_spinner_dropdown_item, privaciesA);
set_upload_privacy.setAdapter(adapterPrivacies);
//Manage privacies
set_upload_privacy.setOnItemSelectedListener(new MaterialSpinner.OnItemSelectedListener<String>() {
@Override
public void onItemSelected(MaterialSpinner view, int position, long id, String item) {
LinkedHashMap<Integer, String> privaciesCheck = new LinkedHashMap<>(peertubeInformation.getPrivacies());
Iterator it = privaciesCheck.entrySet().iterator();
int i = 0;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (i == position) {
privacyToSend = new HashMap<>();
privacyToSend.put((Integer) pair.getKey(), (String) pair.getValue());
break;
}
it.remove();
i++;
}
}
});
//Manage languages
set_upload_channel.setOnItemSelectedListener(new MaterialSpinner.OnItemSelectedListener<String>() {
@Override
public void onItemSelected(MaterialSpinner view, int position, long id, String item) {
LinkedHashMap<String, String> channelsCheck = new LinkedHashMap<>(channels);
Iterator it = channelsCheck.entrySet().iterator();
int i = 0;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
if( i == position){
channelToSend = new HashMap<>();
channelToSend.put((String)pair.getKey(), (String)pair.getValue());
break;
}
it.remove();
i++;
}
}
});
}
}

View File

@ -460,7 +460,8 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
if( max_id == null)
max_id = "0";
//max_id needs to work like an offset
max_id = String.valueOf(Integer.valueOf(max_id) + 50);
int tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40);
max_id = String.valueOf(Integer.valueOf(max_id) + tootPerPage);
if( apiResponse.getPeertubes() == null){
return;
}
@ -953,7 +954,7 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn
ArrayList<Status> tmpStatuses = new ArrayList<>();
for (Status tmpStatus : statuses) {
//Put the toot at its place in the list (id desc)
if( !apiResponse.isFetchmore() && !this.statuses.contains(tmpStatus) && tmpStatus.getCreated_at().after(this.statuses.get(0).getCreated_at())) { //Element not already added
if( !apiResponse.isFetchmore() && !this.statuses.contains(tmpStatus) && tmpStatus.getCreated_at() != null && this.statuses.get(0).getCreated_at() != null && tmpStatus.getCreated_at().after(this.statuses.get(0).getCreated_at())) { //Element not already added
//Mark status at new ones when their id is greater than the last read toot id
if (type == RetrieveFeedsAsyncTask.Type.HOME && lastReadTootDate != null && tmpStatus.getCreated_at().after(lastReadTootDate) ) {
tmpStatus.setNew(true);

View File

@ -3288,7 +3288,7 @@ public class Helper {
* @return ArrayList<String> split toot
*/
public static ArrayList<String> splitToots(String content, int maxChars){
String[] splitContent = content.split("(\\.\\s){1}");
String[] splitContent = content.split("((\\.\\s)|(,\\s)|(;\\s)|(\\?\\s)|(!\\s)){1}");
ArrayList<String> splitToot = new ArrayList<>();
StringBuilder tempContent = new StringBuilder(splitContent[0]);
ArrayList<String> mentions = new ArrayList<>();
@ -3309,7 +3309,7 @@ public class Helper {
int mentionLength = mentionString.length();
int maxCharsMention = maxChars - mentionLength;
for(int i= 0 ; i < splitContent.length ; i++){
if (i < (splitContent.length - 1) && (tempContent.length() + splitContent[i + 1].length()) < (maxChars - 10)) {
if (i < (splitContent.length - 1) && (countLength(tempContent.toString()) + countLength(splitContent[i + 1])) < (maxChars - 10)) {
tempContent.append(". ").append(splitContent[i + 1]);
} else {
splitToot.add(tempContent.toString());
@ -3334,6 +3334,37 @@ public class Helper {
public static int countLength(String text){
if( text == null) {
return 0;
}
if( MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA ){
Matcher matcherALink = Patterns.WEB_URL.matcher(text);
while (matcherALink.find()){
int matchStart = matcherALink.start();
int matchEnd = matcherALink.end();
final String url = text.substring(matcherALink.start(1), matcherALink.end(1));
if( matchEnd <= text.length() && matchEnd >= matchStart){
if( url.length() > 23){
text = text.replaceFirst(url,"abcdefghijklmnopkrstuvw");
}
}
}
}
return text.length() - countWithEmoji(text);
}
public static int countWithEmoji(String text){
int emojiCount = 0;
for (int i = 0; i < text.length(); i++) {
int type = Character.getType(text.charAt(i));
if (type == Character.SURROGATE || type == Character.OTHER_SYMBOL) {
emojiCount++;
}
}
return emojiCount/2;
}
public static boolean filterToots(Context context, Status status, List<String> timedMute, RetrieveFeedsAsyncTask.Type type){
String filter;
if( status == null)

View File

@ -16,6 +16,8 @@ import com.vanniktech.emoji.emoji.Emoji;
import app.fedilab.android.R;
import static app.fedilab.android.activities.TootActivity.autocomplete;
public class MastalabAutoCompleteTextView extends android.support.v7.widget.AppCompatAutoCompleteTextView implements EmojiEditTextInterface {
private float emojiSize;
@ -56,7 +58,7 @@ public class MastalabAutoCompleteTextView extends android.support.v7.widget.AppC
protected void onTextChanged(final CharSequence text, final int start, final int lengthBefore, final int lengthAfter) {
final Paint.FontMetrics fontMetrics = getPaint().getFontMetrics();
final float defaultEmojiSize = fontMetrics.descent - fontMetrics.ascent;
if( emoji) {
if( emoji && !autocomplete) {
EmojiManager.getInstance().replaceWithImages(getContext(), getText(), emojiSize, defaultEmojiSize);
}
}

View File

@ -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 <http://www.gnu.org/licenses>. */
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);
}

View File

@ -117,6 +117,18 @@ public class CustomEmojiDAO {
}
}
/**
* Returns all emojis in db for an instance
* @return emojis List<Emojis>
*/
public List<Emojis> getAllEmojis(String instance){
try {
Cursor c = db.query(Sqlite.TABLE_CUSTOM_EMOJI, null, Sqlite.COL_INSTANCE + " = '" + instance+ "'", null, Sqlite.COL_SHORTCODE , null, Sqlite.COL_SHORTCODE + " ASC", null);
return cursorToListEmojis(c);
} catch (Exception e) {
return null;
}
}
/**
* Returns an emoji by its shortcode in db

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#606984"
android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z"/>
</vector>

View File

@ -58,7 +58,7 @@
</FrameLayout>
<!-- Main Loader -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout
android:id="@+id/loader"
android:visibility="gone"
android:layout_width="match_parent"
@ -148,6 +148,21 @@
android:text=""
android:layout_height="wrap_content"
/>
<TextView
android:visibility="gone"
android:id="@+id/peertube_playlist"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:drawableTop="@drawable/ic_list_peertube_activity"
android:drawablePadding="5dp"
android:layout_width="wrap_content"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:text=""
android:layout_height="wrap_content"
/>
<LinearLayout
android:layout_weight="1"
android:layout_width="0dp"

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses>.
-->
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="app.fedilab.android.activities.PlaylistsActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="?attr/popupOverlay"/>
</android.support.design.widget.AppBarLayout>
<RelativeLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:paddingLeft="@dimen/fab_margin"
android:paddingRight="@dimen/fab_margin"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Listview status -->
<android.support.v4.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:id="@+id/swipeContainer"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/lv_playlist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
/>
</android.support.v4.widget.SwipeRefreshLayout>
<RelativeLayout
android:id="@+id/no_action"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:padding="10dp"
android:gravity="center"
android:textSize="25sp"
android:layout_gravity="center"
android:textStyle="italic|bold"
android:typeface="serif"
android:text="@string/action_playlist_empty_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<!-- Main Loader -->
<RelativeLayout
android:id="@+id/loader"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
</RelativeLayout>
<!-- Loader for next status -->
<RelativeLayout
android:id="@+id/loading_next_status"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|center_horizontal"
android:gravity="bottom|center_horizontal"
android:layout_height="20dp">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminate="true" />
</RelativeLayout>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.DrawerLayout>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp">
<TextView
android:layout_marginTop="10dp"
android:labelFor="@+id/display_name"
android:text="@string/display_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:layout_marginTop="5dp"
android:id="@+id/display_name"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
/>
<TextView
android:layout_marginTop="10dp"
android:labelFor="@+id/description"
android:layout_width="match_parent"
android:text="@string/description"
android:layout_height="wrap_content" />
<EditText
android:layout_marginTop="5dp"
android:id="@+id/description"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="5"
/>
<!-- Videos channels -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_marginTop="10dp"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:gravity="center"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/channel"/>
<com.jaredrummler.materialspinner.MaterialSpinner
android:textSize="14sp"
android:id="@+id/set_upload_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<!-- Videos Privacy -->
<LinearLayout
android:layout_marginTop="10dp"
android:layout_width="0dp"
android:layout_weight="2"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:gravity="center"
android:textSize="16sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_privacy"/>
<com.jaredrummler.materialspinner.MaterialSpinner
android:textSize="14sp"
android:id="@+id/set_upload_privacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingLeft="@dimen/fab_margin"
android:paddingRight="@dimen/fab_margin"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Listview list -->
<ListView
android:id="@+id/lv_playlist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:divider="@null"
/>
<RelativeLayout
android:id="@+id/no_action"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/no_action_text"
android:padding="10dp"
android:gravity="center"
android:textSize="25sp"
android:layout_gravity="center"
android:textStyle="italic|bold"
android:typeface="serif"
android:text="@string/action_playlist_add"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<!-- Main Loader -->
<RelativeLayout
android:id="@+id/loader"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
</RelativeLayout>
<!-- Loader for next items -->
<RelativeLayout
android:id="@+id/loading_next_items"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|center_horizontal"
android:gravity="bottom|center_horizontal"
android:layout_height="20dp">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="match_parent"
android:indeterminate="true" />
</RelativeLayout>
</RelativeLayout>

View File

@ -63,13 +63,18 @@
<menu>
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_peertube_fav"
android:icon="@drawable/ic_favorite_peertube_full"
android:title="@string/peertube_favorites" />
android:id="@+id/nav_peertube_playlists"
android:icon="@drawable/ic_list_peertube"
android:title="@string/playlists" />
<item
android:id="@+id/nav_my_video"
android:icon="@drawable/ic_video_library"
android:title="@string/my_videos" />
<item
android:id="@+id/nav_peertube_history"
android:icon="@drawable/ic_history_black"
android:title="@string/history" />
<item
android:id="@+id/nav_upload"
android:icon="@drawable/ic_cloud_upload"

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -860,7 +860,17 @@
<string name="display_timeline">عرض الخيوط الزمنية</string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">إدارة الوسوم</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_remember_position">تذكّر وضعيتي على الخيط الرئيسي</string>
<string name="history">التاريخ</string>
<string name="playlists">قوائم التشغيل</string>
<string name="display_name">الاسم العلني</string>
<string name="privacy">الخصوصية</string>
<string name="create">إنشاء</string>
<string name="action_playlist_add">ليس لديك أي قوائم تشغيل. انقر على أيقونة \"+\" لإضافة قائمة تشغيل جديدة</string>
<string name="error_display_name">يجب عليك إدخال اسم علني!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">إنشاء قائمة تشغيل</string>
<string name="action_playlist_empty_content">قائمة التشغيل هذه فارغة حاليا.</string>
<plurals name="number_of_vote">
<item quantity="zero">لا صوت</item>
<item quantity="one">صوت واحد</item>

View File

@ -690,7 +690,7 @@ Quan s\'esborra l\'aplicació s\'eliminen les dades immediatament.\n
<string name="some_words_any">Qualsevol d\'aquestes paraules (separades-per-espais)</string>
<string name="some_words_all">Totes aquestes paraules (separades-per-espais)</string>
<string name="some_words_none">Cap d\'aquestes paraules (separades per espais)</string>
<string name="some_tags">Add some words to filter (space-separated)</string>
<string name="some_tags">Afegir paraules al filtre (separades per espais)</string>
<string name="change_tag_column">Canvi de nom de columna</string>
<string name="no_misskey_instance">No hi ha instàncies de Misskey</string>
<string name="misskey_instance">Instància Misskey</string>
@ -835,9 +835,19 @@ Quan s\'esborra l\'aplicació s\'eliminen les dades immediatament.\n
<string name="set_blur_sensitive">Tornar borrosos els mèdia sensibles</string>
<string name="set_display_timeline_in_list">Mostra pissarres com a llista</string>
<string name="display_timeline">Mostra pissarres</string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_display_bot_icon">Marcar els brams fets per bots</string>
<string name="add_tags">Gestionar etiquetes</string>
<string name="set_remember_position">Recordar la posició en la pissarra principal</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vot</item>
<item quantity="other">%d vots</item>

View File

@ -851,6 +851,16 @@ Uživatelské jméno a heslo nejsou nikdy ukládány. Jsou použity pouze během
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d hlas</item>
<item quantity="few">%d hlasy</item>

View File

@ -861,6 +861,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="zero">%d votes</item>
<item quantity="one">%d vote</item>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -689,7 +689,7 @@ Sobald Sie die ersten Buchstaben eintippen, werden Namensvorschläge angezeigt\n
<string name="some_words_any">Eines dieser Wörter (durch Leerzeichen getrennt)</string>
<string name="some_words_all">Alle diese Wörter (durch Leerzeichen getrennt)</string>
<string name="some_words_none">Keines dieser Wörter (durch Leerzeichen getrennt)</string>
<string name="some_tags">Fügen Sie einige Wörter zum Filter hinzu (durch Leerzeichen getrennt)</string>
<string name="some_tags">Wörter zum Filter hinzufügen (durch Leerzeichen getrennt)</string>
<string name="change_tag_column">Spaltenname ändern</string>
<string name="no_misskey_instance">Keine Misskey-Instanzen</string>
<string name="misskey_instance">Misskey Instanz</string>
@ -826,17 +826,27 @@ Sobald Sie die ersten Buchstaben eintippen, werden Namensvorschläge angezeigt\n
<string name="warning_main_timeline">Hauptzeitleisten können nur ausgeblendet werden!</string>
<string name="action_bbcode">BBCode</string>
<string name="add_timeline">Zeitleiste hinzufügen</string>
<string name="set_sensitive_content">Medien immer als vertraulich kennzeichnen</string>
<string name="set_sensitive_content">Medien immer als sensibel kennzeichnen</string>
<string name="gnu_instance">GNU-Instanz</string>
<string name="cached_status">Gespeicherter Status</string>
<string name="set_forward_tags">Schlagwörter in Antworten weiterleiten</string>
<string name="set_forward_tags">Schlagwörter in Antworten übernehmen</string>
<string name="set_long_press_media">Lange Drücken, um Medien zu speichern</string>
<string name="set_blur_sensitive">Blur sensitive media</string>
<string name="set_blur_sensitive">Sensible Medien unscharf darstellen</string>
<string name="set_display_timeline_in_list">Zeitleisten in einer Liste anzeigen</string>
<string name="display_timeline">Zeitleisten anzeigen</string>
<string name="set_display_bot_icon">Bot-Konten als Toots kennzeichnen</string>
<string name="set_display_bot_icon">Toots von Bot-Konten kennzeichnen</string>
<string name="add_tags">Schlagwörter verwalten</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_remember_position">Position der Startseiten-Zeitleiste merken</string>
<string name="history">Verlauf</string>
<string name="playlists">Wiedergabelisten</string>
<string name="display_name">Anzeigenamen</string>
<string name="privacy">Datenschutz</string>
<string name="create">Erstellen</string>
<string name="action_playlist_add">Sie haben noch keine Wiedergabelisten. Klicken Sie auf das Symbol „➕”, um eine neue Wiedergabeliste hinzuzufügen.</string>
<string name="error_display_name">Sie müssen einen Anzeigenamen angeben!</string>
<string name="error_channel_mandatory">Der Kanal wird benötigt, wenn es sich um eine öffentliche Wiedergabeliste handelt.</string>
<string name="action_playlist_create">Wiedergabeliste erstellen</string>
<string name="action_playlist_empty_content">Diese Wiedergabeliste ist leer.</string>
<plurals name="number_of_vote">
<item quantity="one">%d Stimme</item>
<item quantity="other">%d Stimmen</item>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d ψήφοι</item>

View File

@ -840,6 +840,16 @@ https://yandex.ru/legal/confidential/?lang=en </string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -845,6 +845,16 @@
<string name="set_display_bot_icon">Markatu bot kontuak toot-etan</string>
<string name="add_tags">Kudeatu etiketak</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">boto %d</item>
<item quantity="other">%d boto</item>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -843,6 +843,16 @@ Le bouton de connexion sactivera une fois quun domaine valide sera renseig
<string name="set_display_bot_icon">Marquer les comptes bot dans les pouets</string>
<string name="add_tags">Gestion des étiquettes</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Confidentialité</string>
<string name="create">Créer</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d voix</item>
<item quantity="other">%d voix</item>

View File

@ -699,7 +699,7 @@
<string name="some_words_any">Calquera de estas palabras (separadas por espazos)</string>
<string name="some_words_all">Todas estas palabras (separadas por espazos)</string>
<string name="some_words_none">Ningunha de estas palabras (separadas por espazos)</string>
<string name="some_tags">Add some words to filter (space-separated)</string>
<string name="some_tags">Engade algunha palabra para filtrar (separadas por espazos)</string>
<string name="change_tag_column">Cambiar o nome da columna</string>
<string name="no_misskey_instance">Sen instancias Misskey</string>
<string name="misskey_instance">Instancia Misskey</string>
@ -844,9 +844,19 @@
<string name="set_blur_sensitive">Difuminar medios sensibles</string>
<string name="set_display_timeline_in_list">Mostrar liñas temporais nunha lista</string>
<string name="display_timeline">Mostrar liñas temporais</string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_display_bot_icon">Marcar contas de bots nos toots</string>
<string name="add_tags">Xestionar etiquetas</string>
<string name="set_remember_position">Lembrar posición da liña temporal de Inicio</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d voto</item>
<item quantity="other">%d votos</item>

View File

@ -854,6 +854,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="two">%d votes</item>

View File

@ -836,6 +836,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -844,6 +844,16 @@ A Yandexnek megvan a saját adatvédelmi szabályzata, ami itt található: http
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d szavazat</item>
<item quantity="other">%d szavazat</item>

View File

@ -845,6 +845,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -844,6 +844,16 @@ https://yandex.ru/legal/confidential/?lang=en
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="other">%d votes</item>
</plurals>

View File

@ -698,7 +698,7 @@
<string name="some_words_any">Qualsiasi tra queste parole (separate da spazi)</string>
<string name="some_words_all">Tutte queste parole (separate da spazi)</string>
<string name="some_words_none">Nessuna di queste parole (separate da spazi)</string>
<string name="some_tags">Add some words to filter (space-separated)</string>
<string name="some_tags">Aggiungi alcune parole da filtrare (separate da spazio)</string>
<string name="change_tag_column">Rinomina colonna</string>
<string name="no_misskey_instance">Nessuna istanza Misskey</string>
<string name="misskey_instance">Istanza Misskey</string>
@ -843,9 +843,19 @@
<string name="set_blur_sensitive">Sfoca media con contenuti sensibili</string>
<string name="set_display_timeline_in_list">Mostra le timeline in un elenco</string>
<string name="display_timeline">Mostra timeline</string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_display_bot_icon">Segna account bot nei toot</string>
<string name="add_tags">Gestisci etichette</string>
<string name="set_remember_position">Ricorda la posizione nella timeline principale</string>
<string name="history">Cronologia</string>
<string name="playlists">Playlist</string>
<string name="display_name">Nome visualizzato</string>
<string name="privacy">Privacy</string>
<string name="create">Crea</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d voto</item>
<item quantity="other">%d voti</item>

View File

@ -834,6 +834,16 @@
<string name="set_display_bot_icon">トゥート内でbotアカウントをマークする</string>
<string name="add_tags">タグ管理</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="other">%d票</item>
</plurals>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -842,6 +842,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="other">%d votes</item>
</plurals>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -843,6 +843,16 @@ Je kunt beginnen met typen en er zullen namen gesuggereerd worden.\n\n
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d stem</item>
<item quantity="other">%d stemmen</item>

View File

@ -835,6 +835,16 @@ Adresser vil bli foreslått når du begynner å skrive.\n\n
<string name="set_display_bot_icon">Marker robot-kontoer i toots</string>
<string name="add_tags">Administrer stikkord</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d stemme</item>
<item quantity="other">%d stemmer</item>

View File

@ -846,6 +846,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -849,6 +849,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="few">%d votes</item>

View File

@ -46,7 +46,7 @@
<string name="delete_all">Excluir tudo</string>
<string name="translate_toot">Traduzir este toot.</string>
<string name="schedule">Agendar</string>
<string name="text_size">Tamanho dos textos e dos ícones</string>
<string name="text_size">Tamanho dos textos e ícones</string>
<string name="text_size_change">Mudar o tamanho atual dos textos:</string>
<string name="icon_size_change">Mudar o tamanho atual dos ícones:</string>
<string name="next">Próximo</string>
@ -311,7 +311,7 @@
<string name="set_notify">Notificar?</string>
<string name="set_notif_silent">Silenciar notificações</string>
<string name="set_night_mode">Modo noturno</string>
<string name="set_nsfw_timeout">Segundos para expirar a visualização de NSFW, 0 para desativar.</string>
<string name="set_nsfw_timeout">Segundos para expirar a prévia de mídia sensível, 0 para desativar.</string>
<string name="settings_title_profile">Editar perfil</string>
<string name="settings_title_custom_sharing">Compartilhamento externo personalizado</string>
<string name="settings_custom_sharing_url">Seu link de compartilhamento externo&#8230;</string>
@ -332,7 +332,7 @@
<string name="embedded_browser">Usar navegador interno</string>
<string name="custom_tabs">Abas personalizadas</string>
<string name="use_javascript">Ativar Javascript</string>
<string name="expand_cw">Expandir ac automaticamente</string>
<string name="expand_cw">Expandir AC automaticamente</string>
<string name="use_cookies">Permitir cookies de terceiros</string>
<string name="settings_ui_layout">Modelo das timelines: </string>
<string-array name="battery_profiles">
@ -563,7 +563,7 @@
<string name="adding_account_list_trunk">Adicionando contas à lista</string>
<string name="action_list_add">Sem listas. Você pode criar uma tocando no botão \"+\".</string>
<string name="action_remote_instance_add">Sem instâncias. Você pode seguir uma tocando no botão \"+\".</string>
<string name="about_trunk">Quem seguir</string>
<string name="about_trunk">Quem seguir:</string>
<string name="about_trunk_action">API Trunk</string>
<string name="toast_impossible_to_follow">Desculpe, é impossível seguir</string>
<string name="retrieve_remote_account">Carregando conta remota!</string>
@ -638,7 +638,7 @@
<string name="set_display_art">Mostrar timeline de Arte</string>
<string name="schedule_boost">Agendar boost</string>
<string name="boost_scheduled">Boost agendado!</string>
<string name="no_scheduled_boosts">Sem boost agendado!</string>
<string name="no_scheduled_boosts">Sem boosts agendados!</string>
<string name="no_scheduled_boosts_indications"><![CDATA[Abra o menu de um toot e escolha <b>Agendar boost</b>.]]></string>
<string name="art_menu">Timeline de Arte</string>
<string name="open_menu">Abrir menu</string>
@ -683,7 +683,7 @@
<!-- languages not translated -->
<string name="languages">Idiomas</string>
<string name="show_media_only">Apenas mídia</string>
<string name="show_media_nsfw">Mostrar NSFW</string>
<string name="show_media_nsfw">Mostrar mídia sensível</string>
<string name="crowdin_translations">Traduções no Crowdin</string>
<string name="crowdin_manager">Administrador do Crowdin</string>
<string name="translation_app">Tradução do aplicativo</string>
@ -698,7 +698,7 @@
<string name="some_words_any">Qualquer uma destas palavras (separadas por espaço)</string>
<string name="some_words_all">Todas estas palavras (separadas por espaço)</string>
<string name="some_words_none">Nenhuma destas palavras (separadas por espaço)</string>
<string name="some_tags">Add some words to filter (space-separated)</string>
<string name="some_tags">Adicione palavras para filtrar (separadas por espaço)</string>
<string name="change_tag_column">Mudar nome da aba</string>
<string name="no_misskey_instance">Sem instâncias Misskey</string>
<string name="misskey_instance">Instância Misskey</string>
@ -733,7 +733,7 @@
<string name="delete_video">Excluir vídeo</string>
<string name="delete_video_confirmation">Tem certeza de que deseja excluir este vídeo?</string>
<string name="no_video_uploaded">Sem vídeos enviados!</string>
<string name="display_nsfw_videos">Mostrar vídeos NSFW</string>
<string name="display_nsfw_videos">Mostrar vídeos sensíveis</string>
<string name="default_channel_of">Canal %s por padrão</string>
<string name="no_video_to_display">Sem vídeos!</string>
<string name="add_image_to_favorite">Favoritar mídia</string>
@ -839,13 +839,23 @@
<string name="gnu_instance">Instância GNU</string>
<string name="cached_status">Toots em cache</string>
<string name="set_forward_tags">Encaminhar tags nas respostas</string>
<string name="set_long_press_media">Long press to store media</string>
<string name="set_blur_sensitive">Blur sensitive media</string>
<string name="set_display_timeline_in_list">Display timelines in a list</string>
<string name="display_timeline">Display timelines</string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_long_press_media">Toque longo para salvar mídia</string>
<string name="set_blur_sensitive">Blur na mídia sensível</string>
<string name="set_display_timeline_in_list">Mostrar timelines em uma lista</string>
<string name="display_timeline">Mostrar timelines</string>
<string name="set_display_bot_icon">Marcar robôs em toots</string>
<string name="add_tags">Gerenciar tags</string>
<string name="set_remember_position">Lembrar a posição na página inicial</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d voto</item>
<item quantity="other">%d votos</item>
@ -865,6 +875,6 @@
</string-array>
<string-array name="settings_video_mode">
<item>Webview</item>
<item>Stream direto</item>
<item>Transmissão direta</item>
</string-array>
</resources>

View File

@ -848,6 +848,16 @@ Aceste date sunt strict confidențiale și pot fi folosite doar de aplicație.
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="few">%d votes</item>

View File

@ -853,7 +853,17 @@
<string name="display_timeline">Отображать ленты</string>
<string name="set_display_bot_icon">Отметить аккаунты ботов в тутах</string>
<string name="add_tags">Управлять тегами</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_remember_position">Запоминать положение в домашней ленте</string>
<string name="history">История</string>
<string name="playlists">Плейлисты</string>
<string name="display_name">Отображаемое имя</string>
<string name="privacy">Конфиденциальность</string>
<string name="create">Создать</string>
<string name="action_playlist_add">У вас нет плейлистов. Нажмите на \"+\", чтобы добавить новый</string>
<string name="error_display_name">Вы должны указать отображаемое имя!</string>
<string name="error_channel_mandatory">Этот канал необходим, когда плейлист общедоступен.</string>
<string name="action_playlist_create">Создать плейлист</string>
<string name="action_playlist_empty_content">В этом плейлисте пока ничего нет.</string>
<plurals name="number_of_vote">
<item quantity="one">%d голос</item>
<item quantity="few">%d голоса</item>

View File

@ -845,6 +845,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -854,6 +854,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="two">%d votes</item>

View File

@ -850,6 +850,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="few">%d votes</item>

View File

@ -845,7 +845,17 @@
<string name="display_timeline">Visa tidslinjer</string>
<string name="set_display_bot_icon">Markera robotkonton i toot</string>
<string name="add_tags">Hantera taggar</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_remember_position">Kom ihåg positionen i hemtidslinjen</string>
<string name="history">Historik</string>
<string name="playlists">Spellistor</string>
<string name="display_name">Visningsnamn</string>
<string name="privacy">Sekretess</string>
<string name="create">Skapa</string>
<string name="action_playlist_add">Du har inte några spellistor. Klicka på \"+\"-ikonen för att lägga till en ny spellista</string>
<string name="error_display_name">Du måste ange ett visningsnamn!</string>
<string name="error_channel_mandatory">Kanalen krävs när spellistan är offentliga.</string>
<string name="action_playlist_create">Skapa spellista</string>
<string name="action_playlist_empty_content">Det finns ingenting i denna spellista ännu.</string>
<plurals name="number_of_vote">
<item quantity="one">%d röst</item>
<item quantity="other">%d röster</item>

View File

@ -840,6 +840,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="other">%d votes</item>

View File

@ -844,6 +844,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>
<item quantity="few">%d votes</item>

View File

@ -840,6 +840,16 @@ và %d toots khác để khám phá</item>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="other">%d votes</item>
</plurals>

View File

@ -842,6 +842,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="other">%d 票</item>
</plurals>

View File

@ -694,7 +694,7 @@ Yandex 有適當的隱私權政策可以在這裡找到https://yandex.ru/l
<string name="some_words_any">這些字詞的其中一個(以空格分開)</string>
<string name="some_words_all">這些字詞全部(以空格分開)</string>
<string name="some_words_none">這些字詞都不要(以空格分開)</string>
<string name="some_tags">Add some words to filter (space-separated)</string>
<string name="some_tags">新增要過濾的字詞(以空格分隔)</string>
<string name="change_tag_column">變更欄位名稱</string>
<string name="no_misskey_instance">沒有 Misskey 站台</string>
<string name="misskey_instance">Misskey 站台</string>
@ -839,9 +839,19 @@ Yandex 有適當的隱私權政策可以在這裡找到https://yandex.ru/l
<string name="set_blur_sensitive">將敏感內容模糊化</string>
<string name="set_display_timeline_in_list">以清單顯示時間軸</string>
<string name="display_timeline">顯示時間軸</string>
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="set_display_bot_icon">在嘟文中標記機器人帳號</string>
<string name="add_tags">管理標籤</string>
<string name="set_remember_position">記住家時間軸的位置</string>
<string name="history">歷史紀錄</string>
<string name="playlists">播放清單</string>
<string name="display_name">顯示名稱</string>
<string name="privacy">隱私</string>
<string name="create">建立</string>
<string name="action_playlist_add">您沒有任何播放清單。在「+」圖示上按一下以新增播放清單</string>
<string name="error_display_name">您必須提供顯示名稱!</string>
<string name="error_channel_mandatory">當播放清單公開時,頻道為必填。</string>
<string name="action_playlist_create">建立播放清單</string>
<string name="action_playlist_empty_content">目前播放清單內還沒有東西。</string>
<plurals name="number_of_vote">
<item quantity="other">%d 人投票</item>
</plurals>

View File

@ -950,6 +950,16 @@
<string name="set_display_bot_icon">Mark bot accounts in toots</string>
<string name="add_tags">Manage tags</string>
<string name="set_remember_position">Remember the position in Home timeline</string>
<string name="history">History</string>
<string name="playlists">Playlists</string>
<string name="display_name">Display name</string>
<string name="privacy">Privacy</string>
<string name="create">Create</string>
<string name="action_playlist_add">You don\'t have any playlists. Click on the \"+\" icon to add a new playlist</string>
<string name="error_display_name">You must provide a display name!</string>
<string name="error_channel_mandatory">The channel is required when the playlist is public.</string>
<string name="action_playlist_create">Create a playlist</string>
<string name="action_playlist_empty_content">There is nothing in this playlist yet.</string>
<plurals name="number_of_vote">
<item quantity="one">%d vote</item>

View File

@ -0,0 +1,13 @@
Added
- Peertube playlists
- Video history timeline for Peertube
- URLs shortened in toots
Changed
- Emoji count one char
- URL length is 23 max in counter
- Improve split feature
Fixed
- App name not clickable in toots
- Custom emoji with cross-account actions