diff --git a/app/build.gradle b/app/build.gradle index f49868243..d45c2b562 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 728ce12f7..b8b38b663 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -157,6 +157,10 @@ android:label="@string/app_name" android:configChanges="keyboardHidden|orientation|screenSize" /> + playlistForVideo; + private List 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(); + } + } } diff --git a/app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java b/app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java new file mode 100644 index 000000000..e2abd241c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/activities/PlaylistsActivity.java @@ -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 . */ +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 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 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; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/activities/TootActivity.java b/app/src/main/java/app/fedilab/android/activities/TootActivity.java index 86250b562..5d10fdd72 100644 --- a/app/src/main/java/app/fedilab/android/activities/TootActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/TootActivity.java @@ -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 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 = new CustomEmojiDAO(getApplicationContext(), db).getAllEmojis(); + final List 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; + } + } diff --git a/app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java new file mode 100644 index 000000000..da23e5dc5 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/asynctasks/ManagePlaylistsAsyncTask.java @@ -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 . */ +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 { + + 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 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); + } + +} diff --git a/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java index 17135b964..58af89331 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/RetrieveFeedsAsyncTask.java @@ -94,6 +94,7 @@ public class RetrieveFeedsAsyncTask extends AsyncTask { PLOCAL, CHANNEL, MYVIDEOS, + PEERTUBE_HISTORY, PIXELFED, PF_HOME, @@ -311,6 +312,10 @@ public class RetrieveFeedsAsyncTask extends AsyncTask { 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); diff --git a/app/src/main/java/app/fedilab/android/client/APIResponse.java b/app/src/main/java/app/fedilab/android/client/APIResponse.java index 54cdd954e..228382c48 100644 --- a/app/src/main/java/app/fedilab/android/client/APIResponse.java +++ b/app/src/main/java/app/fedilab/android/client/APIResponse.java @@ -28,6 +28,7 @@ import app.fedilab.android.client.Entities.Instance; import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.Entities.Peertube; import app.fedilab.android.client.Entities.PeertubeNotification; +import app.fedilab.android.client.Entities.Playlist; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Results; import app.fedilab.android.client.Entities.Status; @@ -51,6 +52,7 @@ public class APIResponse { private List peertubes = null; private List peertubeNotifications = null; private List filters = null; + private List playlists = null; private List domains = null; private List lists = null; private List emojis = null; @@ -59,6 +61,7 @@ public class APIResponse { private Instance instance; private List storedStatuses; private boolean fetchmore = false; + private List playlistForVideos; public List getAccounts() { return accounts; @@ -219,4 +222,20 @@ public class APIResponse { public void setFetchmore(boolean fetchmore) { this.fetchmore = fetchmore; } + + public List getPlaylists() { + return playlists; + } + + public void setPlaylists(List playlists) { + this.playlists = playlists; + } + + public List getPlaylistForVideos() { + return playlistForVideos; + } + + public void setPlaylistForVideos(List playlistForVideos) { + this.playlistForVideos = playlistForVideos; + } } diff --git a/app/src/main/java/app/fedilab/android/client/Entities/PeertubeInformation.java b/app/src/main/java/app/fedilab/android/client/Entities/PeertubeInformation.java index 67d3a6f7e..504a971a1 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/PeertubeInformation.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/PeertubeInformation.java @@ -25,8 +25,10 @@ public class PeertubeInformation { private LinkedHashMap languages; private LinkedHashMap licences; private LinkedHashMap privacies; + private LinkedHashMap playlistPrivacies; private LinkedHashMap translations; + public static final LinkedHashMap langueMapped; static { LinkedHashMap aMap = new LinkedHashMap<>(); @@ -86,4 +88,12 @@ public class PeertubeInformation { public void setPrivacies(LinkedHashMap privacies) { this.privacies = privacies; } + + public LinkedHashMap getPlaylistPrivacies() { + return playlistPrivacies; + } + + public void setPlaylistPrivacies(LinkedHashMap playlistPrivacies) { + this.playlistPrivacies = playlistPrivacies; + } } diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Playlist.java b/app/src/main/java/app/fedilab/android/client/Entities/Playlist.java new file mode 100644 index 000000000..362409afe --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/Entities/Playlist.java @@ -0,0 +1,204 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +package app.fedilab.android.client.Entities; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Date; +import java.util.HashMap; + + +/** + * Created by Thomas on 26/05/2019. + * Manage List + */ + +public class Playlist implements Parcelable { + + private String id; + private String uuid; + private String displayName; + private String description; + private String videoChannelId; + private Date createdAt; + private boolean isLocal; + private Account ownerAccount; + private HashMap privacy; + private String thumbnailPath; + private HashMap type; + private Date updatedAt; + private int videosLength; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getVideoChannelId() { + return videoChannelId; + } + + public void setVideoChannelId(String videoChannelId) { + this.videoChannelId = videoChannelId; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public boolean isLocal() { + return isLocal; + } + + public void setLocal(boolean local) { + isLocal = local; + } + + public Account getOwnerAccount() { + return ownerAccount; + } + + public void setOwnerAccount(Account ownerAccount) { + this.ownerAccount = ownerAccount; + } + + public HashMap getPrivacy() { + return privacy; + } + + public void setPrivacy(HashMap privacy) { + this.privacy = privacy; + } + + public String getThumbnailPath() { + return thumbnailPath; + } + + public void setThumbnailPath(String thumbnailPath) { + this.thumbnailPath = thumbnailPath; + } + + public HashMap getType() { + return type; + } + + public void setType(HashMap type) { + this.type = type; + } + + public Date getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Date updatedAt) { + this.updatedAt = updatedAt; + } + + public int getVideosLength() { + return videosLength; + } + + public void setVideosLength(int videosLength) { + this.videosLength = videosLength; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.id); + dest.writeString(this.uuid); + dest.writeString(this.displayName); + dest.writeString(this.description); + dest.writeString(this.videoChannelId); + dest.writeLong(this.createdAt != null ? this.createdAt.getTime() : -1); + dest.writeByte(this.isLocal ? (byte) 1 : (byte) 0); + dest.writeParcelable(this.ownerAccount, flags); + dest.writeSerializable(this.privacy); + dest.writeString(this.thumbnailPath); + dest.writeSerializable(this.type); + dest.writeLong(this.updatedAt != null ? this.updatedAt.getTime() : -1); + dest.writeInt(this.videosLength); + } + + public Playlist() { + } + + protected Playlist(Parcel in) { + this.id = in.readString(); + this.uuid = in.readString(); + this.displayName = in.readString(); + this.description = in.readString(); + this.videoChannelId = in.readString(); + long tmpCreatedAt = in.readLong(); + this.createdAt = tmpCreatedAt == -1 ? null : new Date(tmpCreatedAt); + this.isLocal = in.readByte() != 0; + this.ownerAccount = in.readParcelable(Account.class.getClassLoader()); + this.privacy = (HashMap) in.readSerializable(); + this.thumbnailPath = in.readString(); + this.type = (HashMap) in.readSerializable(); + long tmpUpdatedAt = in.readLong(); + this.updatedAt = tmpUpdatedAt == -1 ? null : new Date(tmpUpdatedAt); + this.videosLength = in.readInt(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Playlist createFromParcel(Parcel source) { + return new Playlist(source); + } + + @Override + public Playlist[] newArray(int size) { + return new Playlist[size]; + } + }; +} diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index dd9dca448..eca1acad4 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -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).)*)<\\/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(); diff --git a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java index 15819ce9c..04c2a3a23 100644 --- a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java +++ b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java @@ -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 customFields) { - - HashMap 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 _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 _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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 = 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 = 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 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 = new ArrayList<>(); + params.put("videoIds",videoId); + List 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 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 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 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 = 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 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 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 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 of lists + * @return List of lists */ - private List parseLists(JSONArray jsonArray){ - List lists = new ArrayList<>(); + private List parsePlaylists(Context context, JSONArray jsonArray){ + List playlists = new ArrayList<>(); try { int i = 0; while (i < jsonArray.length() ) { JSONObject resobj = jsonArray.getJSONObject(i); - app.fedilab.android.client.Entities.List list = parseList(resobj); - lists.add(list); + Playlist playlist = parsePlaylist(context, resobj); + playlists.add(playlist); i++; } } catch (JSONException e) { setDefaultError(e); } - return lists; + return playlists; } @@ -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 type = new LinkedHashMap<>(); + LinkedHashMap privacy = new LinkedHashMap<>(); + privacy.put(resobj.getJSONObject("privacy").getInt("id"), resobj.getJSONObject("privacy").get("label").toString()); + type.put(resobj.getJSONObject("type").getInt("id"), resobj.getJSONObject("type").get("label").toString()); + playlist.setType(type); + playlist.setPrivacy(privacy); + }catch (Exception ignored){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 parseAccountResponsePeertube(Context context, String instance, JSONArray jsonArray){ diff --git a/app/src/main/java/app/fedilab/android/drawers/EmojisSearchAdapter.java b/app/src/main/java/app/fedilab/android/drawers/EmojisSearchAdapter.java index bd8c16860..42625b7b1 100644 --- a/app/src/main/java/app/fedilab/android/drawers/EmojisSearchAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/EmojisSearchAdapter.java @@ -85,12 +85,13 @@ public class EmojisSearchAdapter extends ArrayAdapter 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 implements Filtera @Override protected void publishResults(CharSequence constraint, FilterResults results) { - ArrayList c = (ArrayList) results.values; - if (results.count > 0) { - clear(); - addAll(c); - notifyDataSetChanged(); - } else{ - clear(); - notifyDataSetChanged(); - } + try{ + ArrayList c = (ArrayList) results.values; + if (results.count > 0) { + clear(); + addAll(c); + notifyDataSetChanged(); + } else{ + clear(); + notifyDataSetChanged(); + } + }catch (Exception ignored){} + } }; diff --git a/app/src/main/java/app/fedilab/android/drawers/PlaylistAdapter.java b/app/src/main/java/app/fedilab/android/drawers/PlaylistAdapter.java new file mode 100644 index 000000000..7419961bb --- /dev/null +++ b/app/src/main/java/app/fedilab/android/drawers/PlaylistAdapter.java @@ -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 . */ + + +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 playlists; + private LayoutInflater layoutInflater; + private Context context; + private PlaylistAdapter playlistAdapter; + private RelativeLayout textviewNoAction; + + public PlaylistAdapter(Context context, List 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; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java index e9870a4a4..3fd5a9308 100644 --- a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java @@ -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) { diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java new file mode 100644 index 000000000..08376006e --- /dev/null +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayPlaylistsFragment.java @@ -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 . */ + +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 asyncTask; + private List playlists; + private RelativeLayout mainLoader; + private FloatingActionButton add_new; + private PlaylistAdapter playlistAdapter; + private RelativeLayout textviewNoAction; + private HashMap privacyToSend; + private HashMap channelToSend; + private MaterialSpinner set_upload_channel; + private MaterialSpinner set_upload_privacy; + private HashMap 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 translations = null; + if( peertubeInformation != null && peertubeInformation.getTranslations() != null) + translations = new LinkedHashMap<>(peertubeInformation.getTranslations()); + + LinkedHashMap privaciesInit = new LinkedHashMap<>(peertubeInformation.getPrivacies()); + Map.Entry entryInt = privaciesInit.entrySet().iterator().next(); + privacyToSend = new HashMap<>(); + privacyToSend.put(entryInt.getKey(), entryInt.getValue()); + LinkedHashMap 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 channelM = channelToSend.entrySet().iterator().next(); + idChannel = channelM.getValue(); + if( idChannel.length() > 0) + playlist.setVideoChannelId(idChannel); + } + Map.Entry 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 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 adapterChannel = new ArrayAdapter<>(context, + android.R.layout.simple_spinner_dropdown_item, channelName); + set_upload_channel.setAdapter(adapterChannel); + + LinkedHashMap translations = null; + if (peertubeInformation.getTranslations() != null) + translations = new LinkedHashMap<>(peertubeInformation.getTranslations()); + + LinkedHashMap privaciesInit = new LinkedHashMap<>(peertubeInformation.getPlaylistPrivacies()); + Map.Entry entryInt = privaciesInit.entrySet().iterator().next(); + privacyToSend = new HashMap<>(); + privacyToSend.put(entryInt.getKey(), entryInt.getValue()); + LinkedHashMap 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 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() { + @Override + public void onItemSelected(MaterialSpinner view, int position, long id, String item) { + LinkedHashMap 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() { + @Override + public void onItemSelected(MaterialSpinner view, int position, long id, String item) { + LinkedHashMap 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++; + } + } + }); + } +} diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java index e8ab3296c..22609f1d3 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java @@ -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 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); diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 0568d54cf..90e1d2654 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -3288,7 +3288,7 @@ public class Helper { * @return ArrayList split toot */ public static ArrayList splitToots(String content, int maxChars){ - String[] splitContent = content.split("(\\.\\s){1}"); + String[] splitContent = content.split("((\\.\\s)|(,\\s)|(;\\s)|(\\?\\s)|(!\\s)){1}"); ArrayList splitToot = new ArrayList<>(); StringBuilder tempContent = new StringBuilder(splitContent[0]); ArrayList 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 timedMute, RetrieveFeedsAsyncTask.Type type){ String filter; if( status == null) diff --git a/app/src/main/java/app/fedilab/android/helper/MastalabAutoCompleteTextView.java b/app/src/main/java/app/fedilab/android/helper/MastalabAutoCompleteTextView.java index 50fd0cda1..cc5032d8c 100644 --- a/app/src/main/java/app/fedilab/android/helper/MastalabAutoCompleteTextView.java +++ b/app/src/main/java/app/fedilab/android/helper/MastalabAutoCompleteTextView.java @@ -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); } } diff --git a/app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java b/app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java new file mode 100644 index 000000000..4a2843d30 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/interfaces/OnPlaylistActionInterface.java @@ -0,0 +1,27 @@ +/* Copyright 2019 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +package app.fedilab.android.interfaces; + + +import app.fedilab.android.asynctasks.ManagePlaylistsAsyncTask; +import app.fedilab.android.client.APIResponse; + +/** + * Created by Thomas on 26/05/2019 + * Interface when actions have been done with playlists + */ +public interface OnPlaylistActionInterface { + void onActionDone(ManagePlaylistsAsyncTask.action actionType, APIResponse apiResponse, int statusCode); +} diff --git a/app/src/main/java/app/fedilab/android/sqlite/CustomEmojiDAO.java b/app/src/main/java/app/fedilab/android/sqlite/CustomEmojiDAO.java index e86010ddd..b7f106c0d 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/CustomEmojiDAO.java +++ b/app/src/main/java/app/fedilab/android/sqlite/CustomEmojiDAO.java @@ -117,6 +117,18 @@ public class CustomEmojiDAO { } } + /** + * Returns all emojis in db for an instance + * @return emojis List + */ + public List 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 diff --git a/app/src/main/res/drawable/ic_history_black.xml b/app/src/main/res/drawable/ic_history_black.xml new file mode 100644 index 000000000..a61de1bc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_history_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_peertube.xml b/app/src/main/res/drawable/ic_list_peertube.xml new file mode 100644 index 000000000..4c2fb8834 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_peertube.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_peertube_activity.xml b/app/src/main/res/drawable/ic_list_peertube_activity.xml new file mode 100644 index 000000000..fd7111d5d --- /dev/null +++ b/app/src/main/res/drawable/ic_list_peertube_activity.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_peertube.xml b/app/src/main/res/layout/activity_peertube.xml index 243e94311..6eed34f91 100644 --- a/app/src/main/res/layout/activity_peertube.xml +++ b/app/src/main/res/layout/activity_peertube.xml @@ -58,7 +58,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/add_playlist.xml b/app/src/main/res/layout/add_playlist.xml new file mode 100644 index 000000000..bebcece80 --- /dev/null +++ b/app/src/main/res/layout/add_playlist.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playlists.xml b/app/src/main/res/layout/fragment_playlists.xml new file mode 100644 index 000000000..0a063b4bb --- /dev/null +++ b/app/src/main/res/layout/fragment_playlists.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index 12f32670a..de6f5baf4 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -63,13 +63,18 @@ + android:id="@+id/nav_peertube_playlists" + android:icon="@drawable/ic_list_peertube" + android:title="@string/playlists" /> + + Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 8a94c3bb1..84ef9e493 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -860,7 +860,17 @@ عرض الخيوط الزمنية Mark bot accounts in toots إدارة الوسوم - Remember the position in Home timeline + تذكّر وضعيتي على الخيط الرئيسي + التاريخ + قوائم التشغيل + الاسم العلني + الخصوصية + إنشاء + ليس لديك أي قوائم تشغيل. انقر على أيقونة \"+\" لإضافة قائمة تشغيل جديدة + يجب عليك إدخال اسم علني! + The channel is required when the playlist is public. + إنشاء قائمة تشغيل + قائمة التشغيل هذه فارغة حاليا. لا صوت صوت واحد diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 396bf4f2f..536c57452 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -690,7 +690,7 @@ Quan s\'esborra l\'aplicació s\'eliminen les dades immediatament.\n Qualsevol d\'aquestes paraules (separades-per-espais) Totes aquestes paraules (separades-per-espais) Cap d\'aquestes paraules (separades per espais) - Add some words to filter (space-separated) + Afegir paraules al filtre (separades per espais) Canvi de nom de columna No hi ha instàncies de Misskey Instància Misskey @@ -835,9 +835,19 @@ Quan s\'esborra l\'aplicació s\'eliminen les dades immediatament.\n Tornar borrosos els mèdia sensibles Mostra pissarres com a llista Mostra pissarres - Mark bot accounts in toots - Manage tags - Remember the position in Home timeline + Marcar els brams fets per bots + Gestionar etiquetes + Recordar la posició en la pissarra principal + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vot %d vots diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 657fc840c..5f881ea72 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -851,6 +851,16 @@ Uživatelské jméno a heslo nejsou nikdy ukládány. Jsou použity pouze během Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d hlas %d hlasy diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index 3cb5f202d..a4ebc860a 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -861,6 +861,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d votes %d vote diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index f694223f0..db76624be 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1c1714c30..cb3a5e416 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -689,7 +689,7 @@ Sobald Sie die ersten Buchstaben eintippen, werden Namensvorschläge angezeigt\n Eines dieser Wörter (durch Leerzeichen getrennt) Alle diese Wörter (durch Leerzeichen getrennt) Keines dieser Wörter (durch Leerzeichen getrennt) - Fügen Sie einige Wörter zum Filter hinzu (durch Leerzeichen getrennt) + Wörter zum Filter hinzufügen (durch Leerzeichen getrennt) Spaltenname ändern Keine Misskey-Instanzen Misskey Instanz @@ -826,17 +826,27 @@ Sobald Sie die ersten Buchstaben eintippen, werden Namensvorschläge angezeigt\n Hauptzeitleisten können nur ausgeblendet werden! BBCode Zeitleiste hinzufügen - Medien immer als vertraulich kennzeichnen + Medien immer als sensibel kennzeichnen GNU-Instanz Gespeicherter Status - Schlagwörter in Antworten weiterleiten + Schlagwörter in Antworten übernehmen Lange Drücken, um Medien zu speichern - Blur sensitive media + Sensible Medien unscharf darstellen Zeitleisten in einer Liste anzeigen Zeitleisten anzeigen - Bot-Konten als Toots kennzeichnen + Toots von Bot-Konten kennzeichnen Schlagwörter verwalten - Remember the position in Home timeline + Position der Startseiten-Zeitleiste merken + Verlauf + Wiedergabelisten + Anzeigenamen + Datenschutz + Erstellen + Sie haben noch keine Wiedergabelisten. Klicken Sie auf das Symbol „➕”, um eine neue Wiedergabeliste hinzuzufügen. + Sie müssen einen Anzeigenamen angeben! + Der Kanal wird benötigt, wenn es sich um eine öffentliche Wiedergabeliste handelt. + Wiedergabeliste erstellen + Diese Wiedergabeliste ist leer. %d Stimme %d Stimmen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 04ec54bf3..bbc55014d 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d ψήφοι diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index afa20dfbb..6941652b2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -840,6 +840,16 @@ https://yandex.ru/legal/confidential/?lang=en Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 4e081389f..01deee0eb 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -845,6 +845,16 @@ Markatu bot kontuak toot-etan Kudeatu etiketak Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. boto %d %d boto diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index cd0802c95..2f99dcf36 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 0f9af7455..bcae817da 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 94e617324..b6e1effab 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -843,6 +843,16 @@ Le bouton de connexion s’activera une fois qu’un domaine valide sera renseig Marquer les comptes bot dans les pouets Gestion des étiquettes Remember the position in Home timeline + History + Playlists + Display name + Confidentialité + Créer + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d voix %d voix diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 70acb429a..d4f134d91 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -699,7 +699,7 @@ Calquera de estas palabras (separadas por espazos) Todas estas palabras (separadas por espazos) Ningunha de estas palabras (separadas por espazos) - Add some words to filter (space-separated) + Engade algunha palabra para filtrar (separadas por espazos) Cambiar o nome da columna Sen instancias Misskey Instancia Misskey @@ -844,9 +844,19 @@ Difuminar medios sensibles Mostrar liñas temporais nunha lista Mostrar liñas temporais - Mark bot accounts in toots - Manage tags - Remember the position in Home timeline + Marcar contas de bots nos toots + Xestionar etiquetas + Lembrar posición da liña temporal de Inicio + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d voto %d votos diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 72373d031..dccfc350d 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -854,6 +854,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 2842d158e..67d6fb182 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -836,6 +836,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e889d5a2f..19454fa26 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -844,6 +844,16 @@ A Yandexnek megvan a saját adatvédelmi szabályzata, ami itt található: http Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d szavazat %d szavazat diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index d56b10fb1..4d204f123 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -845,6 +845,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 04338338f..bf8469f9c 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -844,6 +844,16 @@ https://yandex.ru/legal/confidential/?lang=en Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d votes diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index eb9170262..c6828872d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -698,7 +698,7 @@ Qualsiasi tra queste parole (separate da spazi) Tutte queste parole (separate da spazi) Nessuna di queste parole (separate da spazi) - Add some words to filter (space-separated) + Aggiungi alcune parole da filtrare (separate da spazio) Rinomina colonna Nessuna istanza Misskey Istanza Misskey @@ -843,9 +843,19 @@ Sfoca media con contenuti sensibili Mostra le timeline in un elenco Mostra timeline - Mark bot accounts in toots - Manage tags - Remember the position in Home timeline + Segna account bot nei toot + Gestisci etichette + Ricorda la posizione nella timeline principale + Cronologia + Playlist + Nome visualizzato + Privacy + Crea + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d voto %d voti diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 06da80e6c..fb8099f6d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -834,6 +834,16 @@ トゥート内でbotアカウントをマークする タグ管理 Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d票 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 970d32c1a..adc3610fd 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 81beea55d..d1e83df2f 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -842,6 +842,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d votes diff --git a/app/src/main/res/values-lmo/strings.xml b/app/src/main/res/values-lmo/strings.xml index 0f9af7455..bcae817da 100644 --- a/app/src/main/res/values-lmo/strings.xml +++ b/app/src/main/res/values-lmo/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 0f9af7455..bcae817da 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index aad51968b..8ed635dc1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -843,6 +843,16 @@ Je kunt beginnen met typen en er zullen namen gesuggereerd worden.\n\n Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d stem %d stemmen diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 566d3a97b..60bdd04ff 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -835,6 +835,16 @@ Adresser vil bli foreslått når du begynner å skrive.\n\n Marker robot-kontoer i toots Administrer stikkord Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d stemme %d stemmer diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index 0f9af7455..bcae817da 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -846,6 +846,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 96a717d40..bcf6c67ba 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -849,6 +849,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index c9e37843f..8e760f849 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -46,7 +46,7 @@ Excluir tudo Traduzir este toot. Agendar - Tamanho dos textos e dos ícones + Tamanho dos textos e ícones Mudar o tamanho atual dos textos: Mudar o tamanho atual dos ícones: Próximo @@ -311,7 +311,7 @@ Notificar? Silenciar notificações Modo noturno - Segundos para expirar a visualização de NSFW, 0 para desativar. + Segundos para expirar a prévia de mídia sensível, 0 para desativar. Editar perfil Compartilhamento externo personalizado Seu link de compartilhamento externo… @@ -332,7 +332,7 @@ Usar navegador interno Abas personalizadas Ativar Javascript - Expandir ac automaticamente + Expandir AC automaticamente Permitir cookies de terceiros Modelo das timelines: @@ -563,7 +563,7 @@ Adicionando contas à lista Sem listas. Você pode criar uma tocando no botão \"+\". Sem instâncias. Você pode seguir uma tocando no botão \"+\". - Quem seguir + Quem seguir: API Trunk Desculpe, é impossível seguir Carregando conta remota! @@ -638,7 +638,7 @@ Mostrar timeline de Arte Agendar boost Boost agendado! - Sem boost agendado! + Sem boosts agendados! Agendar boost.]]> Timeline de Arte Abrir menu @@ -683,7 +683,7 @@ Idiomas Apenas mídia - Mostrar NSFW + Mostrar mídia sensível Traduções no Crowdin Administrador do Crowdin Tradução do aplicativo @@ -698,7 +698,7 @@ Qualquer uma destas palavras (separadas por espaço) Todas estas palavras (separadas por espaço) Nenhuma destas palavras (separadas por espaço) - Add some words to filter (space-separated) + Adicione palavras para filtrar (separadas por espaço) Mudar nome da aba Sem instâncias Misskey Instância Misskey @@ -733,7 +733,7 @@ Excluir vídeo Tem certeza de que deseja excluir este vídeo? Sem vídeos enviados! - Mostrar vídeos NSFW + Mostrar vídeos sensíveis Canal %s por padrão Sem vídeos! Favoritar mídia @@ -839,13 +839,23 @@ Instância GNU Toots em cache Encaminhar tags nas respostas - Long press to store media - Blur sensitive media - Display timelines in a list - Display timelines - Mark bot accounts in toots - Manage tags - Remember the position in Home timeline + Toque longo para salvar mídia + Blur na mídia sensível + Mostrar timelines em uma lista + Mostrar timelines + Marcar robôs em toots + Gerenciar tags + Lembrar a posição na página inicial + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d voto %d votos @@ -865,6 +875,6 @@ Webview - Stream direto + Transmissão direta diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ea8bf47bb..1c1458c3e 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -848,6 +848,16 @@ Aceste date sunt strict confidențiale și pot fi folosite doar de aplicație. Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b72f5a188..3bc736c36 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -853,7 +853,17 @@ Отображать ленты Отметить аккаунты ботов в тутах Управлять тегами - Remember the position in Home timeline + Запоминать положение в домашней ленте + История + Плейлисты + Отображаемое имя + Конфиденциальность + Создать + У вас нет плейлистов. Нажмите на \"+\", чтобы добавить новый + Вы должны указать отображаемое имя! + Этот канал необходим, когда плейлист общедоступен. + Создать плейлист + В этом плейлисте пока ничего нет. %d голос %d голоса diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index d0160380b..98230b98d 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -845,6 +845,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index cf46547a2..78d81f309 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -854,6 +854,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 388cff8c2..e4123ac70 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -850,6 +850,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 1e85aa505..95e6fd074 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -845,7 +845,17 @@ Visa tidslinjer Markera robotkonton i toot Hantera taggar - Remember the position in Home timeline + Kom ihåg positionen i hemtidslinjen + Historik + Spellistor + Visningsnamn + Sekretess + Skapa + Du har inte några spellistor. Klicka på \"+\"-ikonen för att lägga till en ny spellista + Du måste ange ett visningsnamn! + Kanalen krävs när spellistan är offentliga. + Skapa spellista + Det finns ingenting i denna spellista ännu. %d röst %d röster diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 6e2a97d56..281cec689 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -840,6 +840,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e80bf8c05..28a78923f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -844,6 +844,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote %d votes diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 4e04e642c..462485f1f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -840,6 +840,16 @@ và %d toots khác để khám phá Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d votes diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index b73c27249..80eb31731 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -842,6 +842,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d 票 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index dc9214f83..e267c948a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -694,7 +694,7 @@ Yandex 有適當的隱私權政策,可以在這裡找到:https://yandex.ru/l 這些字詞的其中一個(以空格分開) 這些字詞全部(以空格分開) 這些字詞都不要(以空格分開) - Add some words to filter (space-separated) + 新增要過濾的字詞(以空格分隔) 變更欄位名稱 沒有 Misskey 站台 Misskey 站台 @@ -839,9 +839,19 @@ Yandex 有適當的隱私權政策,可以在這裡找到:https://yandex.ru/l 將敏感內容模糊化 以清單顯示時間軸 顯示時間軸 - Mark bot accounts in toots - Manage tags - Remember the position in Home timeline + 在嘟文中標記機器人帳號 + 管理標籤 + 記住家時間軸的位置 + 歷史紀錄 + 播放清單 + 顯示名稱 + 隱私 + 建立 + 您沒有任何播放清單。在「+」圖示上按一下以新增播放清單 + 您必須提供顯示名稱! + 當播放清單公開時,頻道為必填。 + 建立播放清單 + 目前播放清單內還沒有東西。 %d 人投票 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff27585dd..3df62c503 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -950,6 +950,16 @@ Mark bot accounts in toots Manage tags Remember the position in Home timeline + History + Playlists + Display name + Privacy + Create + You don\'t have any playlists. Click on the \"+\" icon to add a new playlist + You must provide a display name! + The channel is required when the playlist is public. + Create a playlist + There is nothing in this playlist yet. %d vote diff --git a/fastlane/metadata/android/en-US/changelogs/274.txt b/fastlane/metadata/android/en-US/changelogs/274.txt new file mode 100644 index 000000000..65ff04d9a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/274.txt @@ -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 \ No newline at end of file